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