Distributed transactions: 2PC, Sagas e compensação

1. O Desafio da Consistência em Sistemas Distribuídos

1.1. Transações ACID em bancos monolíticos vs. ambientes distribuídos

Em um banco de dados monolítico, uma transação ACID (Atomicidade, Consistência, Isolamento, Durabilidade) garante que múltiplas operações sejam executadas como uma unidade atômica. O banco gerencia locks, logs de undo/redo e isolamento entre transações concorrentes. Em sistemas distribuídos, porém, os dados residem em diferentes serviços, cada um com seu próprio banco de dados. Não há um único gerenciador de transações capaz de coordenar atomicidade entre fronteiras de rede.

1.2. O Teorema CAP e a escolha entre consistência forte e disponibilidade

O Teorema CAP (Consistency, Availability, Partition Tolerance) afirma que um sistema distribuído pode oferecer no máximo duas das três propriedades simultaneamente. Em redes particionadas (situação inevitável), precisamos escolher entre consistência forte (todos os nós veem os mesmos dados ao mesmo tempo) ou disponibilidade (o sistema responde mesmo com dados potencialmente inconsistentes). Essa escolha fundamental orienta a decisão entre protocolos como 2PC (consistência forte) e Sagas (consistência eventual).

1.3. Cenários típicos: microsserviços, bancos de dados sharded e sistemas legados

Transações distribuídas surgem em:
- Microsserviços: pedido de e-commerce que envolve serviço de pagamento, estoque e envio
- Sharding: atualização que afeta dados em múltiplos shards de um banco distribuído
- Sistemas legados: integração entre sistemas heterogêneos via mensageria

2. Two-Phase Commit (2PC): Consistência Forte com Coordenação Centralizada

2.1. Fase de preparação (votação) e fase de confirmação (commit/abort)

O 2PC possui duas fases:
- Fase 1 (Prepare): O coordenador pergunta a cada participante se ele pode confirmar a transação. Cada participante registra o estado "preparado" em log e responde "sim" ou "não".
- Fase 2 (Commit/Abort): Se todos responderam "sim", o coordenador envia "commit". Caso contrário, envia "abort". Cada participante executa a ação e libera os locks.

2.2. Papel do coordenador e dos participantes no protocolo

O coordenador mantém o estado da transação e decide o resultado global. Os participantes executam as operações locais e seguem as instruções do coordenador.

2.3. Exemplo de código: fluxo de mensagens entre coordenador e participantes

Coordenador -> Participante_A: PREPARE (transacao_id=123)
Participante_A -> Coordenador: VOTE_COMMIT (transacao_id=123)
Coordenador -> Participante_B: PREPARE (transacao_id=123)
Participante_B -> Coordenador: VOTE_ABORT (transacao_id=123)
Coordenador -> Participante_A: ABORT (transacao_id=123)
Coordenador -> Participante_B: ABORT (transacao_id=123)

3. Limitações e Problemas do 2PC na Prática

3.1. Bloqueio de recursos e perda de disponibilidade durante a fase de preparação

Durante a fase de preparação, os participantes mantêm locks nos recursos (linhas de banco, arquivos). Se um participante demorar a responder, todos os recursos ficam bloqueados, reduzindo a concorrência e a disponibilidade do sistema.

3.2. Ponto único de falha (coordenador) e o problema do split-brain

Se o coordenador falha após a fase de preparação, os participantes ficam em estado indeterminado: não sabem se devem commit ou abort. Protocolos de eleição de novo coordenador (como Paxos) podem resolver, mas adicionam complexidade. Em cenários de split-brain, partições da rede podem levar a decisões conflitantes.

3.3. Cenário de falha: bloqueio indefinido e necessidade de heurísticas de resolução

Cenário:
1. Coordenador envia PREPARE para A e B
2. A e B respondem VOTE_COMMIT
3. Coordenador falha antes de enviar COMMIT
4. A e B ficam bloqueados, sem saber se devem liberar recursos
5. Solução heurística: timeout expira, A decide COMMIT, B decide ABORT
6. Resultado: inconsistência (A confirma, B desfaz)

4. Sagas: Consistência Eventual através de Transações Compensáveis

4.1. Definição de Saga como sequência de transações locais com ações compensatórias

Uma Saga é uma sequência de transações locais. Cada transação possui uma ação compensatória que desfaz seus efeitos. Se uma transação falha, a Saga executa as compensações de todas as transações anteriores, em ordem reversa.

4.2. Coreografia: cada serviço publica eventos e reage a eventos de outros serviços

Na coreografia, não há coordenador central. Cada serviço, ao completar sua transação, publica um evento. Outros serviços escutam esses eventos e executam suas ações.

Serviço_Pedido: cria pedido, publica evento "PedidoCriado"
Serviço_Pagamento: escuta "PedidoCriado", processa pagamento, publica "PagamentoAprovado"
Serviço_Estoque: escuta "PagamentoAprovado", reserva item, publica "ItemReservado"
Serviço_Envio: escuta "ItemReservado", agenda envio, publica "EnvioAgendado"

4.3. Orquestração: um coordenador central (orquestrador) gerencia o fluxo da Saga

Na orquestração, um orquestrador central (serviço dedicado) coordena a Saga, enviando comandos para cada serviço e tratando falhas.

Orquestrador -> Serviço_Pedido: criarPedido()
Orquestrador -> Serviço_Pagamento: processarPagamento()
Orquestrador -> Serviço_Estoque: reservarItem()
Orquestrador -> Serviço_Envio: agendarEnvio()

5. Compensação: O Coração das Sagas

5.1. Transações compensáveis vs. pivot vs. retriáveis: classificação de passos

  • Compensáveis: podem ser desfeitas (ex: reserva de hotel)
  • Pivot: ponto sem retorno, não pode ser desfeito (ex: confirmação de pagamento)
  • Retriáveis: podem ser repetidas até sucesso (ex: envio de email)

5.2. Implementação de ações compensatórias: idempotência e reversão de efeitos colaterais

Ações compensatórias devem ser idempotentes (executadas múltiplas vezes sem efeitos colaterais adicionais). Por exemplo, "cancelarReservaHotel" deve ser seguro de chamar repetidamente.

5.3. Exemplo de código: saga de reserva de viagem com compensação de hotel e voo

Saga: Reserva de Viagem (Orquestração)

Passo 1: ReservarHotel(quarto=101, data=2025-06-01)
  Compensação: CancelarReservaHotel(quarto=101, data=2025-06-01)
Passo 2: ReservarVoo(assento=12A, voo=LA1234)
  Compensação: CancelarReservaVoo(assento=12A, voo=LA1234)
Passo 3: ConfirmarPagamento(valor=1500.00)
  (Pivot - sem compensação)

Cenário de falha no Passo 2:
1. Hotel reservado (Passo 1 OK)
2. Falha na reserva do voo (Passo 2 falha)
3. Orquestrador executa compensação do Passo 1: CancelarReservaHotel
4. Saga termina em estado "abortada"

6. Comparando 2PC e Sagas: Quando Usar Cada Abordagem

6.1. Trade-offs: consistência forte vs. eventual, bloqueio vs. latência

Característica 2PC Sagas
Consistência Forte (imediata) Eventual
Disponibilidade Reduzida (locks) Alta (sem locks)
Latência Maior (sincronização) Menor (assíncrona)
Complexidade Média (protocolo) Alta (compensações)
Tolerância a falhas Baixa (ponto único) Alta (descentralizada)

6.2. Casos de uso típicos: sistemas financeiros (2PC) vs. e-commerce (Sagas)

  • 2PC: transferências bancárias, sistemas de reserva crítica, operações que exigem consistência forte imediata
  • Sagas: e-commerce (pedidos, estoque, pagamento), reservas de viagem, workflows de onboarding

6.3. Combinação híbrida: uso de 2PC em sub-sistemas críticos com Sagas no nível macro

Um sistema pode usar 2PC dentro de um subsistema crítico (ex: atualização de saldo bancário) e Sagas para coordenar entre subsistemas (ex: workflow de abertura de conta).

7. Padrões Avançados e Boas Práticas

7.1. Timeouts, retrys e circuit breakers em transações distribuídas

  • Timeouts: definir tempo máximo de espera por resposta de participantes
  • Retrys: repetir operações falhas (com backoff exponencial)
  • Circuit breakers: interromper tentativas após falhas consecutivas para evitar sobrecarga

7.2. Logging e monitoramento de Sagas: rastreamento de estado e dead letter queues

Cada passo da Saga deve registrar seu estado (iniciado, sucesso, falha, compensado). Dead letter queues capturam mensagens que não puderam ser processadas, permitindo intervenção manual ou reprocessamento.

7.3. Versionamento de Sagas e evolução de contratos entre serviços

Sagas evoluem com o tempo. Versionar Sagas permite que versões antigas continuem funcionando enquanto novas são implantadas. Contratos entre serviços (eventos, comandos) devem ser versionados para evitar quebras.


Referências