Transações Distribuídas
Visão Geral
Uma transação distribuída tenta estender a atomicidade de um banco de dados a múltiplos participantes: ou todos confirmam, ou nenhum.
O mecanismo clássico é o commit em duas fases (2PC). Ele funciona, e o preço é alto o suficiente para que a maioria dos sistemas modernos escolha outra coisa.
Este documento existe para que essa escolha seja informada, não por reflexo.
Problema
Uma operação de negócio frequentemente toca mais de um armazenamento: debitar numa conta e creditar em outra, reservar estoque e registrar pedido, criar usuário e provisionar recurso.
Numa transação local, o banco garante atomicidade. Entre serviços ou bancos, não há essa garantia — cada participante confirma ou falha independentemente.
O resultado sem coordenação é estado parcial: dinheiro debitado e não creditado, pedido registrado sem estoque reservado.
Conceitos Centrais
Como 2PC funciona
Um coordenador conduz o protocolo:
Fase 1 — preparar
coordenador → cada participante: "consegue confirmar?"
participante: persiste a intenção, trava os recursos, responde sim/não
Fase 2 — decidir
se todos disseram sim → "confirme"
se algum disse não → "cancele"
A garantia vem da fase 1: ao responder "sim", o participante se compromete a poder confirmar depois, mesmo que reinicie. Ele mantém as travas até a fase 2.
O problema de bloqueio
Entre responder "sim" e receber a decisão, o participante está preparado — com recursos travados e sem autoridade para decidir sozinho.
Se o coordenador falhar nesse intervalo, o participante fica travado esperando a decisão. Não pode confirmar (não sabe se todos concordaram) nem cancelar (pode ter sido decidido confirmar).
A espera não é necessariamente infinita: a especificação XA prevê decisão heurística — passado um tempo de incerteza, o gerenciador de recursos pode romper a espera e decidir sozinho, liberando as travas. O preço é que os participantes podem decidir diferente, e a transação termina em resultado misto: parte confirmada, parte desfeita. É a atomicidade sendo trocada por disponibilidade, sem que a aplicação participe da escolha — e o registro disso costuma sair num log que ninguém lê.
Isso é o bloqueio do 2PC, e é a razão principal para evitá-lo: a indisponibilidade do coordenador se propaga para todos os participantes, travando recursos que outras operações precisam.
Na prática, isso aparece como banco travado com transações pendentes que exigem intervenção manual.
O coordenador é ponto único
Tornar o coordenador tolerante a falhas exige consenso — o que adiciona latência e complexidade ao protocolo que já é caro.
Sistemas que fazem isso corretamente existem. A maioria das implementações usa coordenador simples, com o risco de bloqueio.
O custo de latência e acoplamento
2PC exige duas idas e voltas para todos os participantes, com persistência em cada fase.
Além disso, ele acopla a disponibilidade: a transação só sucede se todos os participantes estiverem disponíveis simultaneamente. Com cinco participantes de 99,9% cada, a disponibilidade combinada cai para 99,5%.
Ver falha parcial. Cada participante adicionado reduz a probabilidade de sucesso.
Onde 2PC ainda é razoável
Ele não é sempre errado:
- Poucos participantes, na mesma rede local.
- Transações curtas, com travas de curta duração.
- Coordenador com alta disponibilidade.
- Baixo volume.
- Um gerenciador de transações maduro cuidando dos casos de borda.
Fora dessas condições, o custo domina.
Modelo Mental
2PC troca disponibilidade por atomicidade, e a troca piora com cada participante adicionado.
Quando Usar
- Poucos participantes, próximos, com transações curtas.
- Atomicidade estrita exigida e compensação inaceitável.
- Infraestrutura de transação madura já disponível.
- Volume baixo o suficiente para que o bloqueio seja gerenciável.
Quando Não Usar
Entre serviços de times diferentes. Acopla ciclo de vida e disponibilidade — contradiz a razão de separar os serviços.
Com muitos participantes. A disponibilidade combinada despenca.
Com transações longas. Travas de longa duração matam a vazão.
Entre regiões. A latência multiplica.
Sem coordenador tolerante a falhas. O bloqueio vai acontecer.
Quando compensação é aceitável. Ver sagas — resolve o mesmo problema sem travar.
Quando o problema é modelagem. Se a operação precisa ser atômica, talvez os dados devessem estar no mesmo lugar. Frequentemente a fronteira entre serviços foi traçada no lugar errado.
A última é a observação mais valiosa: a necessidade de transação distribuída é frequentemente sintoma de decomposição equivocada.
Alternativas
- Sagas — sequência de transações locais com compensação.
- Caixa de saída transacional — grava a mudança e o evento na mesma transação local, e publica depois. Resolve o caso mais comum sem 2PC.
- Idempotência com repetição — em vez de atomicidade, garantir que a repetição converge.
- Reunir os dados — se a atomicidade é essencial, colocar no mesmo armazenamento.
- Consistência eventual com reconciliação — aceitar divergência temporária e corrigir.
A caixa de saída transacional merece destaque: o cenário mais comum de "preciso de 2PC" é "atualizar o banco e publicar um evento", e ela resolve isso com uma transação local mais um processo de publicação.
Trade-offs
| 2PC | Saga | Caixa de saída |
|---|---|---|
| Atomicidade estrita | Consistência eventual | Eventual |
| Janela de inconsistência curta e não modelada | Estados intermediários explícitos | Explícitos |
| Trava recursos | Sem travas | Sem travas |
| Bloqueia se o coordenador cair | Sem coordenador crítico | Sem coordenador |
| Disponibilidade combinada | Cada passo independente | Local |
| Sem lógica de compensação | Compensação a escrever | Sem compensação |
| Escala mal | Escala | Escala |
Modos de Falha
Transação pendente. O coordenador cai entre as fases; os participantes travam.
Resultado heurístico misto. O coordenador decide cancelar, e um participante que já tinha rompido a incerteza por conta própria já havia confirmado. A transação termina parcialmente aplicada, e reconciliar é trabalho manual.
Recuperação heurística. Um operador resolve manualmente uma pendência, possivelmente de forma inconsistente com os outros participantes.
Contenção. Travas de longa duração serializam operações não relacionadas.
Cascata de indisponibilidade. Um participante lento trava todos os outros.
Erros Comuns
Usar 2PC por reflexo de atomicidade. A pergunta que fica sem ser feita é se o negócio aceita compensação — e ele quase sempre aceita, porque já compensa fora do software: estorno, cancelamento, ajuste.
Não considerar que a fronteira do serviço está errada. Precisar de atomicidade entre dois serviços costuma ser sintoma de que aqueles dados pertencem ao mesmo dono. O 2PC resolve o sintoma e congela a fronteira errada.
Coordenador sem alta disponibilidade. Ele vira ponto único de falha de todos os participantes ao mesmo tempo — e a falha dele não derruba o sistema, o que seria visível: trava recursos, que é pior de diagnosticar.
Não medir a duração das travas. É o que aconteceu no Exemplo Real: uma consulta externa dentro da fase de preparação segurou travas por dezenas de segundos, e as operações do mesmo cliente foram enfileirando atrás.
Ignorar a caixa de saída transacional para o caso "banco + evento" — que é a maioria dos casos em que alguém cogita 2PC.
Exemplo Real
Uma plataforma de logística tinha uma operação que criava a remessa, reservava a capacidade do veículo e debitava o crédito do cliente — três serviços, três bancos.
A implementação usava 2PC com um gerenciador de transações.
Funcionou por dois anos, com incidentes recorrentes:
Travas longas. O serviço de crédito consultava um sistema externo dentro da fase preparada. Quando esse sistema ficava lento, a trava sobre o registro do cliente durava dezenas de segundos, e outras operações do mesmo cliente enfileiravam.
Pendências manuais. Cerca de duas vezes por mês, o coordenador reiniciava durante uma transação e deixava participantes travados. Havia um procedimento manual documentado.
Indisponibilidade combinada. Qualquer um dos três serviços indisponível derrubava a operação inteira, mesmo quando o passo daquele serviço não era urgente.
A migração para saga mudou o modelo.
Sequência com compensação. Criar remessa → reservar capacidade → debitar crédito. Cada passo é uma transação local. Falha em qualquer ponto dispara as compensações dos passos anteriores.
Estados intermediários explícitos. A remessa passou a ter estado "aguardando confirmação" visível na interface — no 2PC o estado intermediário existia igual, só não tinha nome nem duração declarada, e por isso ninguém o tratava.
Idempotência em todos os passos. Ver idempotência.
O que mudou operacionalmente: as pendências manuais desapareceram, e a contenção também. A operação passou a suceder mesmo com o serviço de crédito temporariamente lento — o débito acontece com atraso.
O que piorou: o estado "aguardando confirmação" precisou ser tratado em cinco telas e dois relatórios, e a compensação do débito exigiu regra de negócio nova — o que fazer se o crédito já foi consumido.
A equipe considera a troca claramente positiva, e registra que o trabalho de modelar as compensações foi maior do que a estimativa inicial, por uma margem larga.
Conceitos Relacionados
- Sagas — a alternativa principal.
- Falha Parcial — o problema de fundo.
- Consenso — o que um coordenador confiável exige.
- Idempotência — o que a alternativa exige.
Exercício Prático
Encontre no seu sistema uma operação que toca mais de um armazenamento. Pergunte: o que acontece hoje se ela falhar no meio?
Se a resposta for "não sabemos", esse é o estado real — nem 2PC, nem saga, apenas estado parcial sem tratamento.
Perguntas de Entrevista
- O que acontece se o coordenador de 2PC falhar entre as fases?
- Por que a disponibilidade piora com cada participante?
- Que problema a caixa de saída transacional resolve?
Para Aprofundar
- Gray, Jim; Reuter, Andreas. Transaction Processing: Concepts and Techniques. Morgan Kaufmann, 1992.
- Bernstein, Philip; Newcomer, Eric. Principles of Transaction Processing. Morgan Kaufmann, 2009.
- Richardson, Chris. Microservices Patterns. Manning, 2018 — capítulo 4.