Marcio Cunha

Gestão de Concorrência Otimista em Sistemas Distribuídos Baseados em Event Sourcing

Descubra como lidar com colisões de dados em arquiteturas baseadas em eventos usando controle otimista. Entenda o papel das versões, os trade-offs operacionais e estratégias práticas para manter a consistência sem travar sua aplicação.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • O controle otimista pressupõe que conflitos de escrita são raros e valida a versão dos dados apenas no momento de salvar o novo estado.
  • Sistemas de Event Sourcing gravam cada mudança como um evento imutável, o que simplifica o rastreamento do número de versão de cada entidade.
  • O uso incorreto de chaves de idempotência em fluxos assíncronos pode mascarar falhas de concorrência e duplicar efeitos colaterais.
  • Estratégias de compensação e reprocessamento evitam a perda de dados quando duas transações modificam a mesma origem simultaneamente.
  • A escolha entre bloqueios pessimistas e otimistas define se o sistema prioriza a segurança absoluta ou a alta disponibilidade para múltiplos usuários.

O Desafio da Concorrência em Arquiteturas Descentralizadas

Imagine que duas pessoas tentam editar o mesmo documento ao mesmo tempo em um editor colaborativo na nuvem. Em sistemas tradicionais de banco de dados, o sistema costuma trancar a linha da tabela até que a primeira pessoa termine, impedindo a segunda de continuar. Na prática, esse travamento reduz a velocidade do software e gera gargalos difíceis de escalar quando milhares de usuários acessam o sistema simultaneamente. Em arquiteturas baseadas em Event Sourcing, onde o estado do sistema é derivado de uma sequência de eventos imutáveis em vez de atualizações destrutivas, esse problema ganha uma nova dimensão. A concorrência deixa de ser um mero detalhe de banco de dados e passa a ser um desafio fundamental de coordenação entre serviços distribuídos que operam de forma assíncrona.

Como Funciona a Concorrência Otimista na Prática

A gestão de concorrência otimista parte de uma premissa simples e otimista: na imensa maioria das vezes, dois usuários ou processos não vão alterar exatamente a mesma informação ao mesmo tempo. Em vez de trancar o registro antecipadamente, o sistema permite que qualquer um leia e modifique os dados livremente. No entanto, o mecanismo de salvamento exige a apresentação de um número de versão ou carimbo de data/hora que acompanhava o registro no momento da leitura. Na prática, se o banco de dados percebe que a versão atual é diferente daquela que o usuário trouxe, significa que outra pessoa alterou o registro no meio do caminho. O sistema rejeita a alteração atual, notificando o aplicativo para que ele recarregue os dados atualizados e tente novamente a operação.

O Papel dos Streams de Eventos na Validação de Versões

No coração de um sistema de Event Sourcing, cada entidade de negócio possui um fluxo exclusivo de eventos, muitas vezes chamado de stream. Cada evento adicionado a esse fluxo recebe um número sequencial, que funciona como a versão exata da entidade naquele momento específico. Quando um comando chega para ser processado, o manipulador de comandos carrega todos os eventos anteriores, reconstrói o estado atual da entidade e verifica qual é o número da versão esperada. Ao anexar um novo evento ao stream, a infraestrutura exige que o número de versão seja exatamente o próximo na fila. Se outro processo gravou um evento no mesmo segundo, a versão esperada muda, o banco de dados recusa a transação e o sistema evita corrupções silenciosas de dados.

Implementando o Controle de Versão com Código Funcional

Para ilustrar como esse mecanismo opera no código, podemos observar uma estrutura típica em linguagens modernas que aplicam o padrão de verificação de versão baseada em expectativa. O exemplo abaixo demonstra uma função que tenta anexar um evento a um armazenamento de eventos apenas se o número da versão coincidir com o estado atual armazenado.

interface EventRecord {
streamId: string;
version: number;
payload: any;
}

async function appendEventsOptimistically(
streamId: string,
expectedVersion: number,
newEvents: any[] const currentVersion = await eventStore.getLatestVersion(streamId);

if (currentVersion !== expectedVersion) {
throw new Error('Conflito de concorrência detectado: o estado foi modificado.');
}

const eventsToSave: EventRecord[] = newEvents.map((event, index) => ({
streamId,
version: expectedVersion + index + 1,
payload: event
}));

await eventStore.save(eventsToSave);
return true;
}

Esse trecho de código ilustra a checagem essencial antes de qualquer escrita persistente. Caso a versão atual difira da esperada, o sistema aborta a operação de salvamento de forma segura, permitindo que a camada de aplicação decida se deve reexecutar a lógica de negócio ou solicitar nova intervenção do usuário.

Estratégias para Lidar com Conflitos e Reintentos

Detectar o conflito é apenas a metade do caminho; o sistema precisa saber o que fazer quando a rejeição acontece. Na prática, existem duas abordagens principais para resolver colisões sem frustrar o usuário final. A primeira é a política de nova tentativa automática, onde o aplicativo captura o erro de concorrência otimista, lê novamente os novos eventos do stream, reaplica a intenção de negócio sobre o estado atualizado e tenta salvar de novo. A segunda abordagem envolve delegar a resolução para o usuário ou para uma fila de revisão manual, útil em cenários complexos onde decisões automáticas podem sobrescrever dados críticos de negócios. A escolha depende diretamente do domínio da aplicação e do nível de tolerância a atrasos no processamento.

Trade-offs e Cuidados Operacionais em Ambientes Distribuídos

Adotar a concorrência otimista elimina a lentidão dos bloqueios tradicionais, mas introduz novos desafios operacionais que exigem atenção redobrada. Se a taxa de colisões for muito alta — por exemplo, centenas de processos tentando atualizar o mesmo recurso central ao mesmo tempo —, o número de reintentos explodirá, sobrecarregando a CPU e a rede com leituras e gravações repetidas. Nesses raros casos de contenção extrema, redesenhar o modelo de domínio para dividir o agregado em partes menores ou utilizar filas de processamento serializado pode ser muito mais eficiente do que insistir na concorrência otimista. Compreender os limites da arquitetura garante que a escolha tecnológica sirva ao negócio, e não o contrário.

Considerações Finais

A gestão de concorrência otimista em sistemas baseados em Event Sourcing é uma ferramenta poderosa para garantir a integridade dos dados sem sacrificar a escalabilidade horizontal. Ao tratar versões de streams de eventos como a única fonte de verdade para validações de escrita, as equipes de engenharia conseguem construir sistemas resilientes e capazes de lidar com múltiplos fluxos assíncronos simultâneos. O segredo do sucesso reside em monitorar a taxa de conflitos e planejar estratégias inteligentes de nova tentativa ou particionamento de dados quando o volume de acessos crescer além do esperado.