ResiliênciaIntermediário7 min de leitura5 seçõesanos 1990, Data aproximada

Dead Letter Queue

Uma mensagem que nunca vai dar certo não pode ficar voltando para a fila: ela bloqueia a ordem, consome CPU e esconde o problema. A DLQ é o desvio para onde vai o que falhou demais — com contexto suficiente para diagnosticar e reprocessar.

Em 10 segundos

Depois de N tentativas, a mensagem sai da fila principal e vai para uma fila de desvio, com o erro e o contexto anexados. Isso desbloqueia quem estava atrás dela, para de queimar CPU com o que nunca vai dar certo, e preserva o dado para investigação e reprocessamento.

Antes de ter nome

É o equivalente digital da caixa de 'correspondência não entregável' dos Correios: o que não pôde ser entregue vai para um canto separado, sem travar a fila.

  • #resiliencia
  • #fila
  • #mensageria
  • #poison-message
  • #producao
Analogia

A caixa de devolvidos

Uma carta com endereço inexistente não fica circulando pelo país para sempre. Depois de algumas tentativas, ela vai para o setor de devolvidos — que guarda a carta, anota o motivo, e permite que alguém decida o que fazer. O que ninguém faz é deixá-la no meio do malote, atrasando as outras entregas por tempo indeterminado.

01Conceito

A mensagem envenenada

O consumidor falha, devolve a mensagem para a fila, pega a mesma mensagem de novo, falha de novo. Se a causa for de conteúdo — e não de infraestrutura —, esse ciclo não tem fim.

Em fila com ordenação por chave, o efeito é pior: nada atrás daquela mensagem é processado enquanto ela não sair da frente.

A distinção que resolve isto é entre falha transitória e falha permanente. Banco indisponível, timeout de rede, deploy em andamento: tentar de novo daqui a pouco funciona. JSON malformado, campo obrigatório ausente, referência a um pedido que foi apagado: tentar de novo daqui a mil anos dá o mesmo resultado. Retry serve à primeira; DLQ serve à segunda.

Na prática nem sempre dá para classificar com segurança, e por isso a heurística padrão é por contagem: tenta N vezes com backoff e, se ainda assim falhar, trata como permanente e desvia. Não é perfeito — um incidente de infraestrutura longo o bastante manda mensagens boas para a DLQ —, e é justamente por isso que a DLQ precisa permitir reprocessamento em massa.

O que separa uma DLQ útil de um cemitério é o contexto. Uma fila que guarda só o corpo original obriga quem investiga a reproduzir o erro do zero. Guardar a exceção, o número de tentativas, o momento da primeira falha e a fila de origem transforma o desvio em material de diagnóstico. E, principalmente: alguém precisa ser avisado. Um alerta simples em "profundidade da DLQ maior que zero" é o que impede que ela vire um lugar onde dados somem em silêncio.

Como se encaixa
  1. Fila principal
  2. entregaConsumidor
  3. falhouTransitória ou permanente?
  4. permanente, ou passou de N tentativasDead Letter Queue
  5. alguém precisa olharAlerta e reprocessamento
O desvio só resolve metade do problema. A outra metade é o alerta: DLQ sem ninguém observando é um jeito mais caro de perder mensagem.
02No mundo real

Como isso aparece na prática

O contrato que mudou sem avisar

Cenário

Um produtor passa a enviar valor como string em vez de número. O consumidor lança exceção ao desserializar e devolve a mensagem para a fila. Em uma hora há 40 mil mensagens circulando e o consumo de CPU do consumidor está no teto.

Como o padrão entra

Com DLQ configurada em 5 tentativas, as mensagens quebradas saem da fila principal e se acumulam no desvio. O alerta dispara, a fila principal volta ao normal, e as mensagens desviadas são reprocessadas depois da correção.

Trade-off: As mensagens ficam paradas até alguém agir — o processamento delas deixa de ser em tempo real. É uma perda real, e ainda assim melhor que travar a fila inteira.

O webhook do parceiro que some

Cenário

Um serviço entrega notificações para URLs cadastradas por clientes. Um cliente desativa o endpoint sem remover o cadastro, e as entregas passam a falhar indefinidamente para aquele destino.

Como o padrão entra

Após o limite de tentativas com backoff, a notificação vai para a DLQ e o endpoint é marcado como suspeito. O cliente recebe um aviso de que as entregas estão falhando.

Trade-off: Um endpoint com instabilidade longa mas legítima pode ser marcado indevidamente, exigindo um caminho de reativação. Sem o limite, porém, um único cliente inativo consumiria capacidade de entrega para sempre.

03Cuidado

Onde isto dá errado

  1. DLQ sem alerta

    Uma fila de desvio que ninguém observa é um lugar onde mensagens desaparecem em silêncio — com a agravante de todo mundo achar que existe uma rede de segurança. O alerta em profundidade maior que zero é parte do padrão, não um extra.

  2. Guardar só o corpo original

    Sem a exceção, o número de tentativas e o momento da falha, quem investiga precisa reproduzir o erro do zero, muitas vezes semanas depois. O contexto custa alguns campos e é o que torna a DLQ diagnosticável.

  3. Não ter caminho de reprocessamento

    Corrigido o bug, as mensagens desviadas precisam voltar. Se a única forma for um script escrito às pressas durante o incidente, a DLQ vira arquivo morto. O caminho de volta se constrói antes de precisar dele.

  4. Reprocessar sem idempotência

    Boa parte do que cai na DLQ falhou depois de ter efeito parcial. Reprocessar em massa sem chave de idempotência é a forma mais eficiente já inventada de cobrar todo mundo duas vezes.

  5. Limite de tentativas alto demais

    Cinquenta tentativas com backoff longo mantêm a mensagem envenenada circulando por horas antes de sair — que é exatamente o problema que a DLQ deveria resolver. Poucas tentativas e um desvio rápido servem melhor.

  6. Uma DLQ só para todas as filas

    Misturar desvios de origens diferentes dificulta o diagnóstico e impede reprocessar uma origem sem mexer nas outras. Uma DLQ por fila mantém a investigação e a correção com escopo pequeno.

04Implementação

Classificar, contar e desviar

// ERRADO: a mensagem envenenada volta para a fila para sempre.
// Ela bloqueia a ordem, queima CPU e enche o log — e nunca vai dar certo.
async function consumir(msg: Mensagem) {
  try {
    await processar(msg);
    await msg.confirmar();
  } catch {
    await msg.devolver(); // volta pro topo... e recomeca o ciclo
  }
}

// CERTO: contar tentativas e desviar o que nao tem conserto.
const MAX_TENTATIVAS = 5;

async function consumirComDLQ(msg: Mensagem) {
  try {
    await processar(msg);
    await msg.confirmar();
    return;
  } catch (erro) {
    // Falha de infra (banco fora) merece nova tentativa.
    // Falha de conteudo (JSON invalido) nao melhora tentando.
    const repetivel = ehTransitoria(erro);

    if (repetivel && msg.tentativas < MAX_TENTATIVAS) {
      await msg.devolverComAtraso(backoff(msg.tentativas));
      return;
    }

    // O contexto e o que torna a DLQ util: sem o erro e o
    // momento, ela vira um cemiterio que ninguem sabe ler.
    await dlq.publicar({
      original: msg.corpo,
      erro: String(erro),
      tentativas: msg.tentativas,
      falhouEm: msg.recebidoEm,
      origem: msg.fila,
    });
    await msg.confirmar(); // tira da fila principal: ja esta guardada
  }
}
05Decisão

Quando usar × quando evitar

Use quando

3
  • Em qualquer consumo de fila em produção.
  • Quando o insucesso precisa ser investigado depois, não perdido.
  • Quando a ordem importa e uma mensagem travada bloqueia as seguintes.

Evite quando

2
  • Quando a mensagem perde valor e reprocessar não faz sentido — descartar com métrica é mais honesto.
  • Como depósito permanente sem alerta e sem caminho de volta.

Testar o que ficou

6 armadilhas de Dead Letter Queue viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • redrive policy do SQSVocê declara o número máximo de recebimentos e a fila de destino; o broker faz o desvio sozinho.
  • Dead letter exchange do RabbitMQMensagem rejeitada, expirada ou com fila cheia é roteada para uma exchange separada.
  • A caixa de devolvidos do correioO nome vem daí: carta sem destinatário válido não circula para sempre — vai para um setor próprio.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Depois de N falhas, desvia — não trava a fila.
if (tentativas > 5) await dlq.send(msg);

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Uma fila a mais para monitorar, com alarme e processo de reprocessamento
  • Mensagens paradas na DLQ são trabalho não feito que exige alguém para investigar

Não vale se: a falha de uma mensagem pode ser simplesmente descartada sem consequência — aí desviar para uma fila é guardar lixo.

Onde isto aparece

Continue por aqui