Dead letter queues: lidando com falhas no processamento assíncrono
1. Fundamentos das Dead Letter Queues (DLQ)
1.1. Definição e propósito
Em sistemas assíncronos baseados em filas, nem toda mensagem é processada com sucesso. Uma Dead Letter Queue (DLQ) é uma fila secundária que armazena mensagens que falharam repetidamente no processamento ou expiraram antes de serem consumidas. Seu propósito é evitar que mensagens problemáticas bloqueiem o fluxo principal, permitindo que o sistema continue operando enquanto falhas específicas são isoladas para análise posterior.
Sem uma DLQ, mensagens que falham podem ser reenfileiradas indefinidamente, consumindo recursos e atrasando mensagens válidas. A DLQ atua como um "hospital" de mensagens: elas não são descartadas, mas removidas do fluxo produtivo para diagnóstico e eventual recuperação.
1.2. Diferença entre fila principal e DLQ
A fila principal segue um ciclo de vida típico: publicação, enfileiramento, consumo e confirmação (ack). Quando um consumidor falha ao processar uma mensagem, ela pode ser devolvida à fila para nova tentativa. A DLQ entra em cena quando esse ciclo se esgota.
| Característica | Fila Principal | DLQ |
|---|---|---|
| Propósito | Processamento normal | Isolamento de falhas |
| Ciclo de vida | Consumo → ack/rejeição | Armazenamento para análise |
| Consumidores | Múltiplos, concorrentes | Dedicados ou manuais |
| Prioridade | Alta | Baixa |
1.3. Gatilhos comuns de envio para DLQ
- Falhas de processamento: exceções não recuperáveis (ex: violação de regra de negócio)
- Timeouts: mensagem não processada dentro do prazo configurado (TTL)
- Mensagens mal formatadas: payloads que não correspondem ao schema esperado
- Excesso de redeliveries: número máximo de tentativas excedido
2. Estratégias de Roteamento e Critérios para DLQ
2.1. Políticas de redelivery
Uma política típica define:
- Número máximo de tentativas (ex: 3)
- Intervalo de backoff (ex: 10s, 30s, 60s)
- Critério de descarte: após a última tentativa, a mensagem vai para DLQ
Exemplo de configuração em SQS:
RedrivePolicy:
deadLetterTargetArn: arn:aws:sqs:us-east-1:123456789012:minha-dlq
maxReceiveCount: 3
2.2. Critérios de elegibilidade
Nem toda falha deve ir para DLQ. São elegíveis:
- Mensagens expiradas (TTL atingido)
- Exceções não recuperáveis (erro de schema, negócio inválido)
- Violações de contrato (formato inesperado)
Erros transitórios (rede, banco indisponível) devem ser tratados com retry simples.
2.3. Roteamento baseado em conteúdo
Em sistemas complexos, diferentes tipos de falha podem ser roteados para DLQs distintas:
Fila principal: pedidos
├── DLQ negocial: falhas de validação de pedido
├── DLQ técnica: falhas de integração com gateway
└── DLQ infraestrutura: timeouts de banco de dados
Isso permite times especializados tratarem cada categoria de erro.
3. Arquitetura e Implementação de DLQ em Filas
3.1. DLQ com RabbitMQ
RabbitMQ utiliza o conceito de Dead Letter Exchange (DLX). Quando uma mensagem é rejeitada ou expira, ela é roteada para uma exchange especial que a encaminha para a DLQ.
Configuração de fila com DLX:
Declaração da fila principal:
arguments:
x-dead-letter-exchange: "dlx-exchange"
x-message-ttl: 60000
Declaração da DLQ:
queue: "pedidos-dlq"
bind: "dlx-exchange" -> routing-key: "pedidos-falhos"
3.2. DLQ com Kafka
No Kafka, a DLQ é implementada como um tópico separado. Consumidores produzem mensagens falhas nesse tópico após esgotarem as tentativas.
Exemplo de consumidor com DLQ:
Consumer Group: processador-pedidos
Tópico principal: pedidos
Tópico DLQ: pedidos-dlq
Lógica do consumidor:
1. Consumir mensagem do tópico "pedidos"
2. Tentar processar até 3 vezes
3. Se falhar, produzir mensagem em "pedidos-dlq" com cabeçalho de erro
4. Commitar offset normalmente
3.3. DLQ com SQS
O Amazon SQS oferece DLQ gerenciada com redrive policy. É possível configurar uma fila como DLQ de outra e definir o número máximo de recebimentos.
Exemplo de configuração via CloudFormation:
MinhaFila:
Type: AWS::SQS::Queue
Properties:
RedrivePolicy:
deadLetterTargetArn: !GetAtt MinhaDLQ.Arn
maxReceiveCount: 3
MinhaDLQ:
Type: AWS::SQS::Queue
4. Tratamento e Reprocessamento de Mensagens em DLQ
4.1. Consumidores de DLQ
Um consumidor dedicado escuta a DLQ e aplica lógica de reprocessamento:
Consumer DLQ:
loop:
mensagem = consumir("pedidos-dlq")
if tentativas < 3:
if corrigir(mensagem):
publicar("pedidos", mensagem.corrigida)
deletar(mensagem)
else:
armazenar para inspeção manual
else:
alertar("Mensagem não recuperável: " + mensagem.id)
4.2. Inspeção manual e observabilidade
Ferramentas essenciais:
- Logs estruturados com ID da mensagem e motivo da falha
- Dashboards com contagem de mensagens na DLQ por hora
- Alertas quando o volume excede limiar (ex: > 10 mensagens em 5 minutos)
4.3. Reprocessamento automático vs. manual
- Automático: para erros corrigíveis (ex: schema atualizado, dependência restaurada)
- Manual: para erros que exigem decisão humana (ex: dados corrompidos, fraude)
5. Padrões de Resiliência e Integração com DLQ
5.1. DLQ como complemento ao Outbox pattern
O Outbox pattern garante que mensagens sejam persistidas em banco antes da publicação. Se a publicação falha, a mensagem permanece na tabela outbox. A DLQ entra quando a mensagem é publicada mas o consumo falha repetidamente.
5.2. DLQ e Idempotent Consumers
Consumidores idempotentes evitam que falhas gerem duplicatas na DLQ. Se uma mensagem é processada duas vezes, o resultado deve ser o mesmo. Isso reduz falsos positivos.
5.3. DLQ como último recurso
Hierarquia de tratamento de falhas:
1. Circuit Breaker: evita chamadas a serviços instáveis
2. Retry com backoff: tenta novamente com intervalo crescente
3. DLQ: isola a mensagem após esgotar tentativas
6. Monitoramento, Alertas e Governança de DLQ
6.1. Métricas essenciais
- Taxa de entrada na DLQ (mensagens/hora)
- Tempo médio de resolução
- Volume acumulado vs. capacidade da fila
- Distribuição por tipo de erro
6.2. Alertas proativos
Alerta crítico:
- DLQ cresce > 50 mensagens em 10 minutos
- Mensagem crítica (alta prioridade) entra na DLQ
Alerta informativo:
- Volume diário de DLQ > 0.1% do volume total
- Mensagem na DLQ há mais de 24 horas
6.3. Governança de dados
- Política de retenção: mensagens na DLQ expiram após 14 dias
- Expurgo automático de mensagens mais antigas que o período de retenção
- Versionamento de schemas para evitar mensagens irrecuperáveis
7. Armadilhas e Boas Práticas ao Usar DLQ
7.1. Evitar DLQ como solução única
DLQ não substitui monitoramento, testes ou tratamento adequado de erros. Dependência excessiva leva a:
- Acúmulo de mensagens não tratadas
- Degradação da confiabilidade percebida
7.2. Cuidados com serialização e versionamento
Mensagens com schema antigo podem nunca ser recuperadas se o consumidor da DLQ espera o schema mais recente. Sempre inclua metadados de versão no payload.
7.3. Boas práticas
- Documentar causas comuns de entrada na DLQ e procedimentos de resolução
- Automatizar reprocessamento sempre que possível
- Realizar testes de caos simulando falhas para validar o fluxo de DLQ
Referências
- AWS Documentation: Amazon SQS Dead-Letter Queues — Documentação oficial da AWS sobre configuração e gerenciamento de DLQ no SQS, incluindo redrive policy e boas práticas.
- RabbitMQ Documentation: Dead Letter Exchanges — Explicação detalhada sobre como configurar dead letter exchanges e TTL no RabbitMQ.
- Confluent Blog: Dead Letter Queues with Kafka — Artigo técnico sobre implementação de DLQ em tópicos Kafka, incluindo consumo e reprocessamento.
- Microsoft Docs: Dead-letter queues in Azure Service Bus — Guia oficial sobre DLQ no Azure Service Bus, com exemplos de políticas de roteamento e monitoramento.
- Martin Fowler: Dead Letter Queue Pattern — Descrição do padrão arquitetural de Dead Letter Queue por Martin Fowler, com contexto de sistemas distribuídos.
- Uber Engineering Blog: Reliable Message Processing with Dead Letter Queues — Estudo de caso da Uber sobre como utilizam DLQ para garantir confiabilidade em processamento assíncrono em larga escala.