Dual-Write Problem
O problema de dual-write acontece quando um sistema tenta persistir a mesma mudança em dois sistemas diferentes ao mesmo tempo, por exemplo:
- Banco de dados + broker
- Banco + cache
- Dois bancos diferentes
Por que isso acontece
Seção intitulada “Por que isso acontece”Falta de atomicidade distribuída
Seção intitulada “Falta de atomicidade distribuída”Não existe transação ACID entre banco e broker de forma confiável em sistemas distribuídos.
BEGIN TRANSACTION write DB publish eventCOMMITFalhas parciais
Seção intitulada “Falhas parciais”Entre as operações podem acontecer falha de rede, timeout, crash da aplicação e retries duplicados.
O caso mais comum é o do diagrama abaixo: um serviço de pagamentos aprova um PIX, grava no banco e só depois tenta avisar o serviço de pedidos pelo broker. O COMMIT dá certo, a rede cai antes de a mensagem chegar, e o pagamento fica aprovado sem que ninguém do lado de pedidos saiba.
sequenceDiagram
participant P as Payment Service
participant DB as PostgreSQL
participant B as RabbitMQ
participant O as Order Service
P->>DB: UPDATE pagamentos SET status = 'APROVADO'
DB-->>P: COMMIT ok ✅
P-xB: publish PagamentoAprovado (rede cai) ❌
Note over DB,O: Banco diz "aprovado", Order Service nunca recebeu o evento.<br/>O pedido fica "aguardando pagamento" para sempre.
Inverter a ordem (publicar primeiro, gravar depois) só troca o problema de lugar: se o COMMIT falhar depois da publicação, o Order Service recebe um evento de um pagamento que não existe no banco.
Retries sozinhos não resolvem
Seção intitulada “Retries sozinhos não resolvem”Tentativas de correção podem gerar eventos duplicados e efeitos colaterais inesperados.
Impacto real
Seção intitulada “Impacto real”- Dados inconsistentes entre serviços
- Eventos fantasmas
- Quebra de invariantes de negócio
- Bugs difíceis de reproduzir
Abordagens ingênuas
Seção intitulada “Abordagens ingênuas”Só fazer dois writes
save DBpublish eventNão garante consistência.
2PC
Tem alta complexidade e baixa adoção em microsserviços modernos.
Estratégias corretas
Seção intitulada “Estratégias corretas”Outbox Pattern
Seção intitulada “Outbox Pattern”Persiste dados e evento no mesmo banco, na mesma transação. Um processo separado lê a tabela outbox e publica no broker. Aprofundamento dedicado em Transactional Outbox Pattern.
Vantagens: atomicidade local, eventos não se perdem, desacoplamento.
Trade-offs: latência maior e complexidade operacional.
Ferramentas como Debezium observam mudanças direto no banco.
Vantagens: transparente para a aplicação.
Desvantagens: infraestrutura mais complexa.
Listen to Yourself
Seção intitulada “Listen to Yourself”O próprio serviço publica e consome os eventos, derivando o estado a partir deles.
Event Sourcing
Seção intitulada “Event Sourcing”Eventos se tornam a fonte de verdade:
append event -> event store -> projecoes derivadasComparação
Seção intitulada “Comparação”| Estratégia | Consistência | Complexidade | Controle |
|---|---|---|---|
| Dual-write ingênuo | Nenhuma | Baixa | Baixo |
| 2PC | Forte | Muito alta | Alto |
| Outbox | Eventual | Média | Alto |
| CDC | Eventual | Alta | Médio |
| Event sourcing | Forte | Muito alta | Muito alto |
Insight principal
Seção intitulada “Insight principal”O problema não é escrever duas vezes. É tentar garantir consistência sem um mecanismo confiável.
Boas práticas
Seção intitulada “Boas práticas”- Idempotência em consumidores
- Retry seguro
- Ordenação de eventos quando necessário
- Monitoramento de inconsistências
- Dead letter queues