Skip to main content

Dead Letter Queue | RabbitMQ | CSharp

  • Dead Letter Queue | RabbitMQ | CSharp

info

Há 3 situações que podem ocorrer de ir para a DLQ

  • Replay automatizado Quando a falha foi transitória, como timeout, indisponibilidade de banco ou erro momentâneo de infraestrutura. A mensagem está válida, então pode ser reprocessada com critério.

  • Repair & re-drive Quando o problema está no payload ou na lógica da aplicação. Antes de reenviar, alguém precisa corrigir o dado ou o sistema, senão a mensagem só morrerá de novo.

  • Expurgo auditado Quando a mensagem não deve mais voltar ao fluxo, como em fraude confirmada ou dado irrecuperável. Em vez de apagar, o correto é arquivar com rastreabilidade para auditoria e compliance.


Por que a DLQ não se remove sozinha no RabbitMQ

warning
  1. autoDelete NÃO depende do serviço parar

    • Para filas, só remove quando o último consumer desconecta
    • DLQ normalmente não tem consumer
  2. DLQ sem consumer nunca ativa o auto-delete

    • Logo, a fila permanece
    • O binding permanece
    • A exchange DLX permanece
  3. Exchanges auto-delete só somem quando não há bindings

    • Parar serviços não remove bindings
    • Binding só some quando a fila é deletada
  4. Com múltiplas instâncias

    • Não pode usar exclusive
    • RabbitMQ assume que outro worker pode voltar a usar

Regra final

DLQ/DLX são tratadas como infraestrutura persistente, não como recursos temporários de execução.

Por isso não se autodestroem automaticamente.



O que é uma Dead Letter Queue (DLQ)?

Uma Dead Letter Queue (DLQ) é uma fila usada para armazenar mensagens que não puderam ser processadas com sucesso por uma fila principal.

No RabbitMQ, uma mensagem é encaminhada automaticamente para uma DLQ quando:

  • ❌ O consumidor rejeita a mensagem (BasicReject ou BasicNack com requeue = false)
  • ⏱️ A mensagem expira ao atingir o TTL
  • 📦 A fila atinge seu limite máximo (x-max-length)
  • 🚫 A mensagem não pode ser roteada corretamente

O principal objetivo da DLQ é evitar perda silenciosa de mensagens, permitindo diagnóstico, auditoria e reprocessamento controlado.


Por que usar DLQ no RabbitMQ?

Utilizar DLQ traz benefícios importantes em ambientes produtivos:

  • 🔍 Diagnóstico de falhas
  • 🧾 Auditoria de mensagens problemáticas
  • 🔁 Reprocessamento manual ou automático
  • 🛑 Isolamento de mensagens inválidas
  • 🧠 Separação de erro transitório vs erro definitivo

Sem DLQ, mensagens rejeitadas com requeue = false são perdidas definitivamente.


Quando uma mensagem vai para a DLQ?

SituaçãoVai para DLQ?
BasicAck❌ Não
BasicReject(requeue: true)❌ Volta para fila
BasicReject(requeue: false)✅ Sim
BasicNack(requeue: false)✅ Sim
TTL expirado✅ Sim
Fila cheia (x-max-length)✅ Sim

Como configurar Dead Letter Queue no RabbitMQ com C#

Passo 1: Instalar o pacote

dotnet add package RabbitMQ.Client

Passo 2: Conceito importante (DLX)

A DLQ não recebe mensagens diretamente.

O fluxo real é:

Fila Principal
↓ (erro)
Dead Letter Exchange (DLX)

Dead Letter Queue (DLQ)

👉 Sempre existe um Exchange intermediário (mesmo quando é o exchange default "").


danger

OBS:

No bind

Exemplo Completo e Correto

Criando Dead Letter Exchange e DLQ

channel.ExchangeDeclare(
exchange: "fila.dlx",
type: ExchangeType.Direct,
durable: true);

channel.QueueDeclare(
queue: "fila_principal.dlq",
durable: true,
exclusive: false,
autoDelete: false);

channel.QueueBind(
queue: "fila_principal.dlq",
exchange: "fila.dlx",
routingKey: "fila_principal.erro");

Criando a fila principal com DLQ configurada

channel.QueueDeclare(
queue: "fila_principal",
durable: true,
exclusive: false,
autoDelete: false,
arguments: new Dictionary<string, object>
{
{ "x-dead-letter-exchange", "fila.dlx" },
{ "x-dead-letter-routing-key", "fila_principal.erro" }
});

✔️ Agora qualquer mensagem rejeitada corretamente irá para a DLQ.


Enviando mensagens

channel.BasicPublish(
exchange: "",
routingKey: "fila_principal",
basicProperties: null,
body: Encoding.UTF8.GetBytes("Mensagem de teste"));

Consumidor da fila principal (com ACK/NACK correto)

consumer.Received += async (sender, ea) =>
{
try
{
var message = Encoding.UTF8.GetString(ea.Body.ToArray());

// Simula erro
throw new Exception("Erro de processamento");

channel.BasicAck(ea.DeliveryTag, false);
}
catch
{
// NÃO reprocessa → envia para DLQ
channel.BasicNack(
deliveryTag: ea.DeliveryTag,
multiple: false,
requeue: false);
}
};

⚠️ Nunca relance exceções sem controle, pois isso pode matar o consumer.


Consumindo mensagens da DLQ

consumer.Received += (model, ea) =>
{
var mensagem = Encoding.UTF8.GetString(ea.Body.ToArray());

Console.WriteLine($"Mensagem na DLQ: {mensagem}");

// Ack para não ficar presa na DLQ
channel.BasicAck(ea.DeliveryTag, false);
};

Headers importantes na DLQ (x-death)

O RabbitMQ adiciona automaticamente:

"x-death": [
{
"queue": "fila_principal",
"reason": "rejected",
"count": 1,
"time": "2026-01-06"
}
]

Esses headers permitem:

  • Saber quantas vezes falhou
  • Saber onde falhou
  • Criar lógica de retry controlado

Retry com TTL (Boa prática)

Arquitetura comum:

Fila Principal
↓ erro
Retry Queue (TTL 30s)

Fila Principal
↓ falhou X vezes
DLQ Final

Configuração típica:

  • x-message-ttl
  • x-dead-letter-exchange

Parâmetros importantes de DLQ

ParâmetroDescrição
x-dead-letter-exchangeExchange para erro
x-dead-letter-routing-keyRouting key da DLQ
x-message-ttlTempo máximo da mensagem
x-max-lengthLimite de mensagens
x-max-length-bytesLimite em bytes

Padrão de nomenclatura

TipoFinalidadePadrão de NomeExemplo
Queue (Principal)Processamento normal de mensagens<domínio>.<serviço>.<ação>.queuepedido.pagamento.autorizar.queue
Queue (DLQ)Mensagens rejeitadas ou expiradas<fila_principal>.dlqpedido.pagamento.autorizar.queue.dlq
Queue (Retry)Reprocessamento com atraso<fila_principal>.retrypedido.pagamento.autorizar.queue.retry
Queue (Retry com TTL)Retry com tempo definido<fila_principal>.retry.<ttl>pedido.pagamento.autorizar.queue.retry.30s
Queue (Parking Lot)Falhas definitivas<fila_principal>.parkingpedido.pagamento.autorizar.queue.parking
Exchange (Principal)Roteamento de eventos/mensagens<domínio>.<serviço>.<evento>.exchangepedido.pagamento.criado.exchange
Exchange (DLX)Exchange de erro<exchange_principal>.dlxpedido.pagamento.exchange.dlx
Exchange (Retry)Exchange para retry<exchange_principal>.retrypedido.pagamento.exchange.retry
Routing Key (Principal)Roteamento padrão<domínio>.<serviço>.<ação>pedido.pagamento.autorizar
Routing Key (Erro)Mensagens com falha<routing_key>.erropedido.pagamento.autorizar.erro
Routing Key (Retry)Mensagens em retry<routing_key>.retrypedido.pagamento.autorizar.retry
Queue com AmbienteIsolamento por ambiente<env>.<fila>prd.pedido.pagamento.autorizar.queue
Queue VersionadaEvolução de schema<fila>.vNpedido.pagamento.autorizar.queue.v2

Boas práticas em produção

✅ Use DLQ apenas para erros definitivos ✅ Separe retry de erro final ✅ Monitore DLQs (alertas!) ✅ Nunca use autoAck=true em processamento crítico ✅ Sempre dê Ack na DLQ após inspeção ❌ Não use DLQ como fila principal