Há algum tempo, escrevi sobre o Padrão Outbox. Agora, chegou a hora de falar sobre sua contraparte essencial: o Padrão Inbox.
Entendendo as Garantias de Entrega
Antes de mergulhar no Padrão Inbox, vamos esclarecer as três garantias fundamentais de entrega de mensagens usadas em sistemas distribuídos:
No máximo uma vez (At-most-once)
Nesse modelo, as mensagens são entregues zero ou uma vez — nunca mais que isso. Não há mecanismo de confirmação (acknowledgement ou ACK). Se uma mensagem falha ao ser processada (por causa de um crash, timeout ou problema de rede), ela simplesmente se perde.
Esse comportamento é comum em sistemas de mensageria leves ou em memória. Por exemplo, o NATS (em seu modo básico) oferece entrega at-most-once por padrão.
Compromisso: A simplicidade vem à custa da confiabilidade — mensagens perdidas são possíveis, a menos que o remetente tente reenviá-las por conta própria.
Pelo menos uma vez (At-least-once)
Aqui, o sistema garante que uma mensagem será entregue pelo menos uma vez. Se o consumidor não enviar um ACK, o broker tentará entregar novamente — possivelmente várias vezes.
Esse é o comportamento padrão na maioria dos brokers de mensageria usados em produção, como RabbitMQ, AWS SQS, RocketMQ e Kafka (quando o auto-commit está desativado).
Compromisso: Você precisa projetar seus manipuladores de mensagens para serem idempotentes, pois duplicatas são inevitáveis — especialmente se o processamento demorar mais que o timeout de visibilidade ou confirmação.
Exatamente uma vez (Exactly-once)
Essa é a “santa graal”: cada mensagem é processada exatamente uma vez, sem perdas nem duplicações.
Embora o Kafka (com produtores idempotentes e consumidores transacionais) e alguns outros sistemas afirmem oferecer semântica exactly-once, garantir isso de ponta a ponta entre serviços heterogêneos é extremamente difícil. Normalmente, exige coordenação rigorosa entre a camada de mensageria e a lógica da aplicação (por exemplo, por meio de escritas transacionais e deduplicação).
Na prática: A maioria dos sistemas simula exactly-once combinando entrega at-least-once com processamento idempotente — e é justamente aqui que o Padrão Inbox brilha.
O Problema: Consumo Confiável
Vamos focar na garantia amplamente adotada de entrega at-least-once. Dois desafios comuns surgem:
- Processamento lento e duplicatas: Você tem uma mensagem que demora muito para ser processada. Aumentar o timeout de visibilidade nem sempre é viável. Se o processamento ultrapassar esse limite, o broker reentrega a mensagem, fazendo com que sua lógica seja executada duas vezes.
- “Pílulas envenenadas” em *streams: Ao consumir de um *stream como o Kafka, uma única mensagem problemática (uma “pílula envenenada”) pode bloquear toda a partição. Você não consegue avançar para a próxima mensagem até resolver o problema, paralisando sua aplicação.
Como garantir um processamento idempotente e resiliente nesses cenários? A resposta é o Padrão Inbox.
A Solução: O Padrão Inbox
O Padrão Inbox fornece um mecanismo para consumo idempotente de mensagens. A ideia central é tratar seu banco de dados como o registro primário e confiável das mensagens recebidas.
Veja como funciona:
- Ao receber uma mensagem, a primeira ação é armazená-la em uma tabela dedicada chamada inbox, dentro do mesmo banco de dados usado pela sua aplicação (por exemplo, PostgreSQL ou MySQL), na mesma transação que manipula quaisquer dados de negócio iniciais.
- O registro inclui um identificador único da mensagem (como
message_id). - Antes de processar a mensagem, o sistema verifica a tabela inbox para ver se já existe um registro com aquele ID.
- Se for uma duplicata, a mensagem é confirmada (ACK) e ignorada.
Isso resolve elegantemente nossos problemas:
- Para o Problema #1 (duplicatas): O inbox atua como um filtro de idempotência. Mesmo que a mesma mensagem seja entregue várias vezes, ela será processada apenas uma vez.
- Para o Problema #2 (pílulas envenenadas): Em sistemas baseados em stream, você pode confirmar uma mensagem problemática para desbloquear a fila. Depois, quando o problema for corrigido, basta redefinir o offset ou replay do stream. O padrão inbox automaticamente ignorará as mensagens já processadas antes da falha.
Compromissos e Considerações
Nenhum padrão é uma bala de prata. O Padrão Inbox introduz seus próprios trade-offs:
- Sobrecarga de desempenho: Cada mensagem exige uma escrita no banco e uma verificação, o que adiciona latência.
- Complexidade arquitetural: Exige gerenciar uma tabela inbox e, muitas vezes, processadores em segundo plano.
- Banco de dados como gargalo: Sob alta taxa de mensagens, o banco pode se tornar um ponto de estrangulamento.
- Custo de armazenamento: As tabelas inbox podem crescer muito, exigindo políticas de retenção ou arquivamento.
Ferramentas e Implementação
A boa notícia é que o Padrão Inbox é amplamente suportado. É provável que exista uma biblioteca ou framework para sua linguagem de programação que já implemente esse padrão. Caso contrário, a lógica básica é simples de construir.
Para desenvolvedores .NET, o projeto Brighter oferece suporte excelente e integrado tanto para os padrões Inbox quanto o Outbox.
Conclusão
O Padrão Inbox é uma ferramenta poderosa para construir sistemas orientados a eventos e resilientes. Ao usar o banco de dados como uma camada protetora, ele garante processamento idempotente e ajuda a lidar com falhas de forma elegante. Embora introduza alguma complexidade, o benefício de um processamento garantido e livre de duplicatas é inestimável em fluxos de negócio críticos.
Quando você adota o Padrão Outbox para envio confiável, lembre-se de combiná-lo com o Padrão Inbox para consumo igualmente confiável.
Referências
https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/
https://newsletter.systemdesignclassroom.com/p/every-outbox-needs-an-inbox
Top comments (0)