Dead Letter Queue

Poison messages e quando usar DLQ

Classificando erros no payment-service para decidir entre retry e DLQ.

Avançado 30 min 25 pontos Leitura 0%

Nesta aula você vai

  • Detectar mensagens envenenadas no consumo de payment
  • Separar erro transitório de erro irrecuperável
  • Definir critérios de envio para DLQ

Poison messages e quando usar DLQ

Objetivos

  • Classificar falhas no payment-service.
  • Evitar travar partição do Kafka por mensagem inválida.
  • Preservar payload para investigação e reprocessamento.

Pré-requisitos

  • payment-service em Go com consumidor Kafka.
  • Tópico payment.events já existente.
  • Conhecimento básico de erros de parse e validação.

Conceito

Poison message é mensagem que sempre falha, mesmo após retry. Exemplo: schema inválido, valor obrigatório ausente ou regra de negócio impossível. Ela deve sair do fluxo principal para uma DLQ.

Estrutura de arquivos

services/payment-service/internal/
├── consumer/payment_consumer.go
├── errors/classifier.go
└── dlq/policy.go

Passo a passo

  1. Crie classificador de erros em errors/classifier.go:
package errors

import "errors"

var (
  ErrInvalidPayload = errors.New("invalid payload")
  ErrBusinessRule   = errors.New("business rule violation")
)

func IsPoison(err error) bool {
  return errors.Is(err, ErrInvalidPayload) || errors.Is(err, ErrBusinessRule)
}

func IsRetryable(err error) bool {
  return !IsPoison(err)
}
  1. Use no consumer (consumer/payment_consumer.go):
func (c *Consumer) handleMessage(ctx context.Context, msg kafka.Message) error {
  event, err := decodePaymentRequested(msg.Value)
  if err != nil {
    return errors.ErrInvalidPayload
  }

  if event.AmountCents <= 0 {
    return errors.ErrBusinessRule
  }

  return c.gateway.Authorize(ctx, event.OrderID, event.AmountCents)
}
  1. Aplique política de decisão:
if err != nil {
  if errors.IsPoison(err) {
    c.logger.Error("mensagem envenenada", "key", string(msg.Key), "error", err)
    return c.dlqPublisher.Publish(ctx, msg, err)
  }
  return err // transitório, será tratado por retry
}

Como testar

Envie payload inválido:

docker compose up -d kafka payment-service
echo '{"type":"payment.requested","payload":{"orderId":"o-1","amountCents":0}}' \
  | kcat -P -b localhost:9092 -t payment.events
docker compose logs -f payment-service

Você deve ver log de erro classificado como poison (não transitório).

Dicas de projeto

  • Classificação de erro deve ser determinística.
  • Guarde motivo da falha em header da DLQ.
  • Não confunda timeout de rede com erro de domínio.

Erros comuns

  • Mandar todo erro para DLQ sem retry.
  • Repetir indefinidamente payload inválido.
  • Não ter métrica por tipo de erro.

Resumo

Você implementou a base de classificação para identificar poison messages e preparar o envio correto para DLQ.