Marcio Cunha

Sistemas fora do ar? Como usar outbox transacional

Descubra como garantir a consistência de dados entre seu banco relacional e brokers de mensagens usando o padrão Transactional Outbox.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • A perda de sincronia entre banco de dados e mensageria ocorre quando falhas de rede impedem o envio de eventos após o commit.
  • O padrão Transactional Outbox grava a intenção do evento na mesma tabela da transação principal para garantir atomicidade.
  • Um processo de escuta independente, chamado poller ou CDC, lê a tabela de outbox e despacha as mensagens de forma resiliente.
  • Garantir entregas idempotentes no consumidor evita efeitos colaterais indesejados quando mensagens duplicadas chegam.
  • A escolha entre polling do banco e Change Data Capture define a complexidade operacional e a latência de entrega dos eventos.

O dilema clássico da mensageria distribuída

Quando construímos aplicações modernas, é comum dividirmos as responsabilidades em vários sistemas que conversam entre si por meio de corretores de mensagens, como o RabbitMQ ou o Kafka. Na prática, isso significa que um sistema de e-commerce precisa salvar o pedido no banco de dados relacional e, logo em seguida, avisar o estoque para separar o produto. O problema invisível surge quando o banco de dados aceita a gravação, mas o broker de mensagens fica fora do ar logo em seguida. Nesse cenário de falha parcial, o seu banco fica com o registro de um pedido pago, mas o resto da empresa nunca fica sabendo que o evento aconteceu, gerando furos graves de consistência operacional.

Para entender a gravidade do problema, imagine que você tentou enviar uma carta de transferência bancária pelo correio, mas a agência pegou fogo logo após você assinar o documento interno, sem carimbar o envio. Na engenharia de software, tentar executar duas operações independentes em sistemas diferentes — gravar no banco e publicar na rede — é um convite para o caos. Se uma das pontas falhar, os dados ficam desalinhados e recuperar isso manualmente exige esforço hercúleo de auditoria e correção de registros. Para evitar que sua aplicação sofra com esses gargalos, é essencial estruturar estratégias semelhantes àquelas usadas quando você otimiza o acesso a dados, como visto em banco de dados lento? Como otimizar índices no PostgreSQL para garantir agilidade nas consultas transacionais.

O que é e como funciona o Transactional Outbox

A solução elegante para esse dilema se chama Transactional Outbox Pattern, ou padrão de caixa de saída transacional. Em termos simples, em vez de enviar a mensagem diretamente para a rede no momento em que o usuário clica em comprar, sua aplicação grava o dado da entidade principal e, na exata mesma transação de banco de dados, insere um registro em uma tabela auxiliar chamada outbox. Na prática, isso significa que a mensagem nasce dentro do próprio banco relacional, aproveitando as garantias rígidas de atomicidade que o banco oferece há décadas. Se a transação falhar por qualquer motivo, tanto o dado principal quanto o evento de notificação são desfeitos juntos, sem deixar rastros inconsistentes.

Com essa abordagem, você transforma um problema complexo de comunicação distribuída em um simples problema de leitura de tabela interna. Como a mensagem já está segura dentro do banco de dados, você ganha tempo para resolver instabilidades na rede ou quedas temporárias no broker sem perder nenhum evento crítico de negócio. Para manter a arquitetura ainda mais robusta contra quedas de infraestrutura, vale a pena combinar essa estratégia com mecanismos de proteção de fluxo, conceitos amplamente aplicados quando projetamos sistema fora do ar? Como desenhar circuit breakers resilientes para isolar falhas em cascata.

Arquitetura do processo de leitura e despacho

Depois que o evento está guardado na tabela de outbox, precisamos de um mecanismo separado para ler esse conteúdo e entregá-lo ao corretor de mensagens. Esse papel costuma ser feito por um processo em segundo plano, muitas vezes chamado de poller, que consulta periodicamente a tabela em busca de registros pendentes de envio. Assim que o poller encontra novas linhas, ele dispara o evento para o broker de mensagens e, logo após receber a confirmação de sucesso, atualiza o status do registro no banco para processado ou simplesmente o remove da tabela. Esse ciclo garante que nenhum evento fique para trás, mesmo se o serviço de envio reiniciar no meio do expediente.

Contudo, delegar essa tarefa a um simples leitor cíclico exige atenção redobrada ao volume de dados e ao desempenho das consultas. Se a tabela de outbox crescer muito rapidamente, as buscas por registros não enviados podem causar lentidão generalizada no banco de dados. É por isso que muitos engenheiros migram do polling tradicional para ferramentas de captura de dados modificados, conhecidas como Change Data Capture ou CDC, que monitoram o log de transações do banco em tempo real sem sobrecarregar as tabelas operacionais. Essa mesma preocupação com estabilidade e desempenho lembra os cuidados necessários quando migramos estruturas legadas, conforme explicamos no artigo sobre como mudar banco de dados em produção sem derrubar o sistema.

Tratando duplicidade e entregas at-least-once

No universo dos sistemas distribuídos, a garantia de entrega absoluta costuma seguir o modelo conhecido como pelo menos uma vez, ou at-least-once. Na prática, isso significa que o seu sistema de outbox fará o possível para entregar a mensagem, mas se houver uma oscilação de rede logo após o envio, o processo pode tentar despachar o mesmo evento novamente por precaução. Para o consumidor da mensagem, isso quer dizer que ele pode receber o mesmo aviso de pedido criado duas vezes. Ignorar essa realidade é abrir brecha para duplicação de cobranças ou envio duplo de mercadorias para o cliente.

Para blindar sua aplicação contra esse comportamento, o segredo é projetar os consumidores para serem idempotentes, ou seja, capazes de processar a mesma mensagem várias vezes sem alterar o resultado final do sistema. Na prática, você consegue isso armazenando o identificador único de cada evento processado em uma tabela de controle e rejeitando qualquer duplicata que chegue com o mesmo ID. Essa disciplina operacional transforma uma falha potencial de mensageria em um fluxo perfeitamente seguro e previsível para o negócio.

Considerações finais sobre consistência eventual

Adotar o padrão Transactional Outbox muda radicalmente a forma como encaramos a resiliência em arquiteturas orientadas a eventos. Ao abdicar da ilusão de que podemos atualizar múltiplos sistemas de forma instantânea, aceitamos a consistência eventual como um princípio saudável e realista de engenharia. Na prática, isso significa que seus serviços podem oscilar, reiniciar ou passar por manutenções sem corromper o estado das informações de negócio. Desenhar sistemas robustos exige planejar o pior cenário desde o primeiro dia, garantindo que a tecnologia trabalhe a favor da estabilidade da empresa e da tranquilidade de quem opera a infraestrutura.