Sagas e compensações: gerenciando transações distribuídas sem two-phase commit

1. Fundamentos das Transações Distribuídas e o Problema do 2PC

1.1. O que são transações distribuídas e por que o two-phase commit (2PC) é frágil

Transações distribuídas envolvem operações que afetam múltiplos bancos de dados, serviços ou sistemas de forma atômica. O protocolo two-phase commit (2PC) foi a abordagem clássica para garantir essa atomicidade: um coordenador pergunta a todos os participantes se podem confirmar (fase prepare) e, se todos responderem "sim", envia o comando de commit (fase commit). Na prática, porém, o 2PC é frágil porque depende de um coordenador central que se torna um ponto único de falha e de bloqueio.

1.2. Limitações do 2PC: bloqueio de recursos, falta de resiliência e escalabilidade

O 2PC mantém recursos bloqueados durante toda a transação — se um participante demorar a responder, todos os outros ficam esperando. Em sistemas distribuídos modernos, com alta latência de rede e falhas frequentes, isso degrada severamente o desempenho. Além disso, se o coordenador falhar após a fase prepare, os participantes podem ficar bloqueados indefinidamente (cenário de "transação órfã").

1.3. Cenário típico: microsserviços e a necessidade de consistência eventual

Em arquiteturas de microsserviços, cada serviço gerencia seu próprio banco de dados. Uma operação de negócio (como criar um pedido) pode envolver serviços de pedido, pagamento, estoque e envio. Nesse contexto, o 2PC é inviável por exigir bloqueios distribuídos. A solução é adotar consistência eventual através do padrão Saga.

2. O Padrão Saga: Conceitos e Arquitetura

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

Uma Saga é uma sequência de transações locais, cada uma executada por um serviço. Se uma transação falhar, a Saga executa ações compensatórias para desfazer as transações anteriores. Diferente do 2PC, não há bloqueio global — cada transação local é confirmada imediatamente.

2.2. Diferença entre Sagas coreografadas e orquestradas

Existem duas abordagens principais:
- Coreografada: cada serviço reage a eventos e publica novos eventos, sem coordenador central.
- Orquestrada: um orquestrador central gerencia o fluxo, enviando comandos e tratando respostas.

2.3. Exemplo prático: fluxo de pedido, pagamento e estoque em uma Saga

Considere um sistema de e-commerce com três serviços: Pedido, Pagamento e Estoque. O fluxo normal é:
1. Pedido: criar pedido (status: pendente)
2. Pagamento: processar pagamento
3. Estoque: reservar itens
4. Pedido: confirmar pedido

Se o passo 3 falhar, a Saga precisa compensar: estornar pagamento e cancelar pedido.

3. Sagas Coreografadas: Comunicação Descentralizada

3.1. Funcionamento: eventos assíncronos entre serviços (event-driven)

Cada serviço publica eventos quando completa sua transação local. Serviços inscritos reagem a esses eventos e executam sua parte. Se uma falha ocorre, um evento de falha é publicado, iniciando compensações.

Exemplo de fluxo coreografado:

Serviço Pedido:
  1. Cria pedido (status: pendente)
  2. Publica evento: PedidoCriado

Serviço Pagamento (inscrito em PedidoCriado):
  1. Processa pagamento
  2. Publica evento: PagamentoProcessado

Serviço Estoque (inscrito em PagamentoProcessado):
  1. Reserva itens
  2. Se sucesso: publica EstoqueReservado
  3. Se falha: publica EstoqueFalhou

Serviço Pedido (inscrito em EstoqueReservado):
  1. Confirma pedido (status: confirmado)

Serviço Pedido (inscrito em EstoqueFalhou):
  1. Publica evento: CompensarPagamento

3.2. Vantagens: acoplamento fraco, simplicidade de implementação

Serviços não precisam conhecer uns aos outros diretamente — apenas os eventos. Isso facilita a adição de novos serviços (ex: serviço de frete) sem modificar os existentes.

3.3. Desafios: rastreabilidade, tratamento de falhas e complexidade em loops

Em fluxos complexos, pode ser difícil rastrear o estado atual da Saga. Além disso, eventos de falha podem causar loops se não houver controle de idempotência. Por exemplo, se um serviço falha ao processar um evento de compensação, ele pode republicar o mesmo evento infinitamente.

4. Sagas Orquestradas: Controle Centralizado com um Orquestrador

4.1. Papel do orquestrador: gerenciar o fluxo, estados e decisões de compensação

O orquestrador é um serviço dedicado que controla toda a Saga. Ele envia comandos para cada serviço e decide, com base nas respostas, qual o próximo passo ou se deve iniciar compensações.

4.2. Implementação com máquina de estados (State Machine)

O orquestrador mantém uma máquina de estados que define os estados possíveis e as transições. Exemplo:

Estado Inicial: PEDIDO_CRIADO
Transições:
  - PEDIDO_CRIADO + pagamento_ok → PAGAMENTO_OK
  - PEDIDO_CRIADO + pagamento_falhou → COMPENSAR_PEDIDO
  - PAGAMENTO_OK + estoque_ok → ESTOQUE_OK
  - PAGAMENTO_OK + estoque_falhou → COMPENSAR_PAGAMENTO
  - ESTOQUE_OK + envio_ok → CONCLUIDO
  - ESTOQUE_OK + envio_falhou → COMPENSAR_ESTOQUE
Estados de compensação:
  - COMPENSAR_PEDIDO → cancelar pedido → FALHA
  - COMPENSAR_PAGAMENTO → estornar pagamento → FALHA
  - COMPENSAR_ESTOQUE → liberar estoque → FALHA

4.3. Comparação com coreografada: quando escolher cada abordagem

Característica Coreografada Orquestrada
Acoplamento Fraco Moderado
Complexidade de fluxos Alta em fluxos complexos Gerenciável
Rastreabilidade Difícil Fácil (estados centralizados)
Performance Boa (assíncrona) Pode ter latência adicional
Manutenção Difícil com muitos eventos Mais simples

Escolha coreografada para fluxos simples e times pequenos; orquestrada para fluxos críticos com muitos serviços.

5. Ações Compensatórias: Como Desfazer Transações Parciais

5.1. Princípios de compensação: idempotência, reversibilidade e efeitos colaterais

Uma ação compensatória deve ser:
- Idempotente: executar a compensação múltiplas vezes produz o mesmo resultado.
- Reversível: deve desfazer exatamente o que a transação original fez.
- Consciente de efeitos colaterais: se a transação original gerou um e-mail, a compensação pode precisar enviar outro e-mail de cancelamento.

5.2. Exemplos de compensações: estorno de pagamento, reversão de estoque, cancelamento de envio

Transação original: ProcessarPagamento(100.00)
Compensação: EstornarPagamento(100.00) — idempotente se o sistema já verificar se o pagamento foi estornado

Transação original: ReservarEstoque(produto_X, 5)
Compensação: LiberarEstoque(produto_X, 5) — verificar se a reserva ainda existe

Transação original: EnviarPedido(pedido_123)
Compensação: CancelarEnvio(pedido_123) — pode falhar se já foi entregue

5.3. Tratamento de falhas nas próprias compensações (compensation failure)

Se uma compensação falha, o sistema precisa de estratégias adicionais:
- Retry: tentar novamente com backoff exponencial.
- Dead letter queue: registrar a falha para intervenção manual.
- Compensação alternativa: se estornar cartão de crédito falha, tentar reembolso em voucher.

6. Garantindo Consistência e Resiliência em Sagas

6.1. Estratégias de persistência de estado (logs de saga, event sourcing)

Cada passo da Saga deve ser registrado em um log persistente. No caso de falha do orquestrador, ele pode consultar o log e retomar de onde parou. Event sourcing é uma técnica complementar: armazena todos os eventos como fonte da verdade.

6.2. Mecanismos de retry, timeouts e dead letter queues

  • Retry: tentar novamente operações que falharam por causas transitórias (rede, timeout).
  • Timeouts: definir um tempo máximo para cada passo; se excedido, tratar como falha e iniciar compensação.
  • Dead letter queues: mensagens que não puderam ser processadas após múltiplas tentativas são enviadas para uma fila separada para análise manual.

6.3. Monitoramento e recuperação: como lidar com sagas pendentes ou inconsistentes

Monitore métricas como:
- Número de Sagas em andamento
- Tempo médio de conclusão
- Sagas em estado de compensação
- Sagas que excederam timeout

Crie dashboards e alertas para Sagas que ficam pendentes por muito tempo. Considere um job de reconciliação que verifica periodicamente Sagas inconsistentes e tenta completá-las ou compensá-las.

7. Comparação Final: Sagas vs. Two-Phase Commit

7.1. Tabela comparativa: disponibilidade, desempenho, complexidade e consistência

Característica Sagas Two-Phase Commit
Disponibilidade Alta (falhas parciais são tratadas) Baixa (coordenador é ponto único)
Desempenho Alto (sem bloqueios) Baixo (bloqueio de recursos)
Complexidade Média (compensações) Alta (protocolo complexo)
Consistência Eventual Forte (imediata)
Escalabilidade Alta (assíncrona) Baixa (síncrona)
Tolerância a falhas Alta (compensações) Baixa (falha do coordenador)

7.2. Quando usar Sagas (alta escalabilidade, sistemas fracamente acoplados) vs. 2PC (consistência forte, sistemas legados)

Use Sagas quando:
- Seu sistema tem alta escalabilidade e disponibilidade
- Você pode aceitar consistência eventual (por alguns segundos/minutos)
- Os serviços são fracamente acoplados e independentes

Use 2PC (ou variantes como XA) quando:
- A consistência forte é obrigatória (ex: sistemas financeiros tradicionais)
- O sistema é monolítico ou tem poucos nós
- A latência não é crítica e o volume de transações é baixo

7.3. Padrões híbridos: combinação de Sagas com compensações e transações locais

Na prática, muitos sistemas combinam abordagens:
- Use transações locais dentro de um serviço (ex: ACID em um banco relacional).
- Use Sagas para coordenar entre serviços.
- Se necessário, use 2PC apenas para pares críticos (ex: pagamento e contabilidade) enquanto o restante usa Sagas.

Exemplo híbrido:

Serviço de Pedido:
  1. Transação local: criar pedido no banco (ACID)
  2. Publica evento: PedidoCriado

Serviço de Pagamento:
  1. Transação local: debitar cartão (ACID)
  2. Publica evento: PagamentoProcessado

Serviço de Estoque:
  1. Transação local: reservar itens (ACID)
  2. Se falha: publica EstoqueFalhou

Serviço de Pagamento (inscrito em EstoqueFalhou):
  1. Transação local: estornar cartão (ACID)
  2. Publica evento: PagamentoEstornado

Referências