Estratégias de retry com dead letter queue para dependências externas instáveis
1. Fundamentos: Por que dependências externas exigem retry e DLQ?
1.1. Natureza das falhas em chamadas externas (transientes vs. permanentes)
Dependências externas — APIs de terceiros, bancos de dados remotos, serviços de mensageria — falham de maneiras distintas. Falhas transientes (timeout, conexão recusada, throttling) são temporárias e podem ser resolvidas com repetição. Falhas permanentes (payload inválido, recurso inexistente, erro de autenticação) nunca terão sucesso, independentemente do número de tentativas. Ignorar essa distinção leva a desperdício de recursos e degradação do sistema.
1.2. O papel da resiliência: evitar cascata de falhas e degradação do sistema
Sem uma estratégia de retry controlada, uma falha em uma dependência externa pode travar threads, acumular conexões e derrubar todo o serviço. O padrão retry com dead letter queue (DLQ) isola o impacto, permitindo que o sistema continue processando mensagens válidas enquanto falhas persistentes são redirecionadas para análise posterior.
1.3. Introdução ao padrão Dead Letter Queue (DLQ) como mecanismo de isolamento
Uma DLQ é uma fila separada que armazena mensagens que falharam repetidamente. Em vez de descartar ou reter indefinidamente na fila principal, a DLQ funciona como um "hospital" para mensagens doentes — preservando o payload e o histórico de erros para depuração, reprocessamento ou notificação manual.
2. Projetando a arquitetura de retry com DLQ
2.1. Componentes essenciais: fila principal, fila de retry, DLQ e workers
A arquitetura típica envolve quatro elementos:
Fila Principal → Worker → Decisão de Falha → Fila de Retry (backoff) → DLQ
- Fila principal: recebe mensagens originais
- Worker: processa a mensagem e chama a dependência externa
- Fila de retry: armazena mensagens para reprocessamento com atraso
- DLQ: armazena mensagens que esgotaram as tentativas
2.2. Fluxo de mensagens: da fila principal até a DLQ após falhas consecutivas
text
1. Mensagem chega na fila principal
2. Worker tenta processar → falha transiente (timeout)
3. Mensagem é movida para fila de retry com contador=1
4. Após backoff, worker tenta novamente → falha transiente
5. Mensagem é movida para fila de retry com contador=2
6. Após backoff, worker tenta novamente → falha permanente
7. Mensagem é movida para DLQ com contador=3 e erro registrado
2.3. Decisões de design: número máximo de tentativas, janela de tempo e critérios de falha
Definir maxRetries = 3 e backoffBase = 2 segundos é comum, mas depende do SLA da dependência. Para APIs com rate limit, considere jitter para evitar thundering herd. Falhas 4xx (cliente) devem ir direto para DLQ; 5xx (servidor) merecem retry.
3. Estratégias de retry: configurando o comportamento de repetição
3.1. Retry imediato vs. retry atrasado (backoff exponencial e jitter)
Retry imediato é útil para falhas de rede rápidas, mas pode sobrecarregar a dependência. Backoff exponencial com jitter é preferível:
Atraso = min(base * 2^tentativa, maxDelay) + random(0, jitter)
Exemplo: base=1s, tentativa=2 → atraso=4s + jitter de até 1s.
3.2. Controle de tentativas por mensagem: contadores, cabeçalhos e metadados
Cada mensagem carrega metadados de retry:
{
"payload": { "id": 123, "acao": "processar_pagamento" },
"metadata": {
"retryCount": 2,
"maxRetries": 3,
"lastError": "HTTP 503 - Service Unavailable",
"lastAttemptAt": "2025-03-15T10:30:00Z"
}
}
3.3. Diferenciação de falhas: quando retentar vs. quando enviar diretamente para DLQ
Critérios práticos:
| Tipo de falha | Ação |
|---|---|
| Timeout (transiente) | Retry com backoff |
| HTTP 429 (rate limit) | Retry com jitter + cabeçalho Retry-After |
| HTTP 4xx (exceto 429) | DLQ imediata |
| HTTP 5xx | Retry até maxRetries, depois DLQ |
| Exceção de conexão | Retry imediato (1x), depois backoff |
4. Implementando a Dead Letter Queue: roteamento e armazenamento
4.1. Critérios de movimentação para DLQ (falha permanente, exaustão de tentativas, timeout)
A DLQ recebe mensagens quando:
- retryCount >= maxRetries
- Erro classificado como permanente (ex.: 400 Bad Request)
- Timeout excede limite configurado (ex.: 30s)
- Payload corrompido ou schema inválido
4.2. Estrutura da mensagem na DLQ: payload original, histórico de erros e metadados
{
"originalPayload": { "id": 123, "acao": "processar_pagamento" },
"errorHistory": [
{ "attempt": 1, "error": "HTTP 503", "timestamp": "2025-03-15T10:28:00Z" },
{ "attempt": 2, "error": "HTTP 503", "timestamp": "2025-03-15T10:29:00Z" },
{ "attempt": 3, "error": "HTTP 400", "timestamp": "2025-03-15T10:30:00Z" }
],
"finalDecision": "DLQ - falha permanente",
"movedAt": "2025-03-15T10:30:05Z"
}
4.3. Políticas de retenção e notificações para a DLQ (alertas e dashboards)
Configure TTL (time-to-live) na DLQ — 7 dias é comum. Alertas devem disparar quando o volume da DLQ ultrapassar um limiar (ex.: 100 mensagens em 5 minutos). Dashboards mostram taxa de entrada na DLQ por tipo de erro.
5. Monitoramento e operação do pipeline de retry
5.1. Métricas chave: taxa de sucesso, contagem de retries, volume na DLQ
Métricas essenciais:
- success_rate: porcentagem de mensagens processadas com sucesso na primeira tentativa
- retry_rate: mensagens que precisaram de retry
- dlq_rate: mensagens movidas para DLQ
- avg_retry_latency: tempo médio entre tentativas
5.2. Estratégias de reprocessamento da DLQ: manual, automático com gatilhos, ou reenvio
Três abordagens comuns:
- Manual: operador revisa a DLQ e reenvia mensagens corrigidas
- Automático com gatilho: quando a dependência se recupera, um worker varre a DLQ e tenta reprocessar
- Reenvio programado: após correção do bug, mensagens são movidas de volta para a fila principal
5.3. Tratamento de dependências externas instáveis: circuit breaker e fallback integrados
Combine retry/DLQ com circuit breaker: se a taxa de erro da dependência ultrapassar 50% em 1 minuto, o circuit breaker abre e mensagens vão direto para DLQ sem tentativas, protegendo o sistema.
6. Boas práticas e armadilhas comuns
6.1. Evitar retry infinito e loops de repetição
Sempre defina um limite máximo de tentativas. Sem maxRetries, uma falha transiente pode gerar milhões de requisições inúteis. Use contador com expiração.
6.2. Cuidados com idempotência e duplicação de mensagens
Retry pode gerar duplicatas. Garanta que o processamento seja idempotente — use identificadores únicos (ex.: idempotencyKey) para que a dependência ignore requisições repetidas.
6.3. Versionamento de esquemas e compatibilidade com DLQ
Mensagens na DLQ podem ficar obsoletas se o schema mudar. Versionar o payload (ex.: "schemaVersion": 2) permite que workers mais novos processem mensagens antigas ou as rejeitem adequadamente.
7. Exemplo prático: cenário de integração com API externa instável
7.1. Configuração da fila principal e parâmetros de retry
# Configuração do worker (pseudo-código)
QUEUE_NAME = "pedidos-processamento"
RETRY_QUEUE_NAME = "pedidos-retry"
DLQ_NAME = "pedidos-dlq"
MAX_RETRIES = 3
BACKOFF_BASE_SECONDS = 2
MAX_BACKOFF_SECONDS = 60
7.2. Lógica de tratamento de erro e movimentação para DLQ
function processarMensagem(mensagem):
try:
resposta = chamarApiExterna(mensagem.payload)
if resposta.status == 200:
return SUCESSO
elif resposta.status in [429, 503]:
throw TransientError(resposta.body)
else:
throw PermanentError(resposta.body)
catch TransientError as erro:
mensagem.retryCount += 1
if mensagem.retryCount >= MAX_RETRIES:
moverParaDLQ(mensagem, erro)
else:
delay = calcularBackoff(mensagem.retryCount)
agendarReprocessamento(mensagem, delay)
catch PermanentError as erro:
moverParaDLQ(mensagem, erro)
7.3. Fluxo de reprocessamento e recuperação de mensagens da DLQ
function reprocessarDLQ():
mensagens = lerTodasMensagensDaDLQ()
for mensagem in mensagens:
if API_externa_esta_saudavel():
mensagem.retryCount = 0
publicarNaFilaPrincipal(mensagem)
else:
manterNaDLQ(mensagem, "API ainda instável")
Referências
- Amazon SQS Dead-Letter Queues — Documentação oficial da AWS sobre configuração de DLQ, incluindo redrive policy e permissões.
- Azure Service Bus Dead-Letter Queues — Guia completo da Microsoft sobre DLQ no Service Bus, com exemplos de movimentação automática e manual.
- Retry Pattern - Microsoft Cloud Design Patterns — Padrão de retry com backoff exponencial, jitter e considerações sobre idempotência.
- Resilience4j Retry and Circuit Breaker — Biblioteca Java para resiliência, com exemplos de configuração de retry e integração com DLQ.
- RabbitMQ Dead Letter Exchanges — Documentação oficial do RabbitMQ sobre dead letter exchanges, incluindo roteamento baseado em cabeçalhos e TTL.
- Google Cloud Pub/Sub Dead Letter Topics — Guia do GCP sobre dead letter topics, com políticas de retry e monitoramento via Cloud Monitoring.
- Martin Fowler - CircuitBreaker — Artigo clássico sobre circuit breaker, essencial para complementar estratégias de retry com DLQ.