Pular para conteúdo

Tratamento de Falhas com Pub/Sub Dead-Letter Queue

Guia para gerenciar mensagens falhadas e estratégias de retry no DestaquesGovbr.


Visão Geral

O sistema usa Dead-Letter Queues (DLQ) para isolar mensagens que falharam após múltiplas tentativas de processamento, permitindo análise e reprocessamento manual sem bloquear o fluxo principal.

Arquitetura

graph TB
    A[Topic: dgb.news.scraped] -->|Push| B[Enrichment Worker]
    B -->|Success 200| C[ACK]
    B -->|Error 5xx| D[NACK + Retry]
    D -->|Retry 1| B
    D -->|Retry 2| B
    D -->|Retry 3| B
    D -->|Max Attempts| E[DLQ Topic]
    E --> F[DLQ Subscription]
    F -->|Pull Manual| G[Análise/Reprocessamento]

    B -->|Error 4xx| H[ACK Permanente]

Configuração de DLQ

1. Criar DLQ Topic

# Topic principal
gcloud pubsub topics create dgb.news.scraped

# Topic DLQ
gcloud pubsub topics create dgb.news.scraped-dlq

2. Criar Subscription com DLQ

gcloud pubsub subscriptions create enrichment-worker-sub \
  --topic=dgb.news.scraped \
  --push-endpoint=https://enrichment-worker-xxx.a.run.app \
  --ack-deadline=600 \
  --retry-policy-minimum-backoff=10s \
  --retry-policy-maximum-backoff=600s \
  --dead-letter-topic=dgb.news.scraped-dlq \
  --max-delivery-attempts=5

Parâmetros importantes: - --ack-deadline=600: Tempo máximo de processamento (10 minutos) - --retry-policy-minimum-backoff=10s: Delay inicial entre retries - --retry-policy-maximum-backoff=600s: Delay máximo (10 minutos) - --max-delivery-attempts=5: Tentativas antes de enviar para DLQ

3. Criar Subscription Pull para DLQ

gcloud pubsub subscriptions create enrichment-worker-dlq-sub \
  --topic=dgb.news.scraped-dlq \
  --ack-deadline=300

Retry Policy (Exponential Backoff)

Comportamento de Retry

Tentativa Backoff Tempo Acumulado Status
0s 0s Processamento inicial
10s 10s Após 1ª falha
20s 30s Após 2ª falha
40s 70s Após 3ª falha
80s 150s Após 4ª falha
→ DLQ - - Após 5ª falha

Cálculo: backoff_time = min(max_backoff, min_backoff * 2^(attempt-1))

HTTP Status Codes

Status Comportamento Descrição
200-299 ACK Sucesso, mensagem removida da fila
400-499 ACK Erro do cliente (payload inválido), não retenta
500-503 NACK + Retry Erro do servidor, retenta com backoff
504 NACK + Retry Timeout, retenta

Monitoramento de DLQ

1. Métricas Cloud Monitoring

# Monitorar quantidade de mensagens na DLQ
gcloud monitoring time-series list \
  --filter='metric.type="pubsub.googleapis.com/subscription/num_undelivered_messages" AND resource.labels.subscription_id="enrichment-worker-dlq-sub"' \
  --format="table(metric.labels.subscription_id, points[0].value.int64_value)" \
  --limit=10

2. Dashboard Grafana

# Query Prometheus para Grafana
- expr: |
    pubsub_subscription_num_undelivered_messages{subscription_id="enrichment-worker-dlq-sub"}
  legendFormat: "DLQ Messages"

3. Alertas

# Cloud Monitoring Alert Policy
displayName: "DLQ Messages Threshold"
conditions:
  - conditionThreshold:
      filter: |
        metric.type="pubsub.googleapis.com/subscription/num_undelivered_messages"
        resource.type="pubsub_subscription"
        resource.labels.subscription_id="enrichment-worker-dlq-sub"
      comparison: COMPARISON_GT
      thresholdValue: 10
      duration: 300s
notificationChannels:
  - projects/destaques-govbr/notificationChannels/email-oncall

Análise de Falhas

1. Listar Mensagens na DLQ

# Pull 10 mensagens (sem ACK)
gcloud pubsub subscriptions pull enrichment-worker-dlq-sub \
  --limit=10 \
  --format=json | jq '.'

Output:

[
  {
    "ackId": "...",
    "message": {
      "data": "base64-encoded-payload",
      "attributes": {
        "trace_id": "abc123",
        "news_id": "xyz789",
        "delivery_attempt": "5"
      },
      "messageId": "1234567890",
      "publishTime": "2026-05-05T10:00:00Z"
    }
  }
]

2. Decodificar Mensagem

# Decodificar payload base64
gcloud pubsub subscriptions pull enrichment-worker-dlq-sub --limit=1 --format=json | \
  jq -r '.[0].message.data' | \
  base64 --decode | \
  jq '.'

3. Analisar Causa Raiz

Causas comuns:

Erro Causa Solução
ValidationException Payload inválido, modelo ID incorreto Corrigir payload ou atualizar modelo
ThrottlingException Rate limit AWS Bedrock excedido Reduzir paralelização, aumentar backoff
TimeoutError Worker timeout (>600s) Otimizar processamento, aumentar ack-deadline
DatabaseError PostgreSQL connection timeout Aumentar connection pool, verificar DB
ModelNotFound Modelo Claude não disponível na região Verificar região, usar modelo alternativo

4. Inspecionar Logs do Worker

# Buscar logs relacionados ao trace_id
gcloud logging read \
  "resource.type=cloud_run_revision AND \
   resource.labels.service_name=enrichment-worker AND \
   jsonPayload.trace_id=abc123" \
  --limit=50 \
  --format=json

Reprocessamento

Opção 1: Replay Individual

# Pull 1 mensagem e republicar no tópico principal
MESSAGE=$(gcloud pubsub subscriptions pull enrichment-worker-dlq-sub --limit=1 --format=json)

DATA=$(echo $MESSAGE | jq -r '.[0].message.data')

gcloud pubsub topics publish dgb.news.scraped --message=$DATA

Opção 2: Replay em Batch

# Script para reprocessar todas as mensagens DLQ
#!/bin/bash

DLQ_SUB="enrichment-worker-dlq-sub"
TARGET_TOPIC="dgb.news.scraped"
BATCH_SIZE=100

while true; do
  MESSAGES=$(gcloud pubsub subscriptions pull $DLQ_SUB \
    --limit=$BATCH_SIZE \
    --format=json)

  # Verificar se há mensagens
  if [ "$(echo $MESSAGES | jq 'length')" -eq 0 ]; then
    echo "Nenhuma mensagem restante na DLQ"
    break
  fi

  # Republicar cada mensagem
  echo $MESSAGES | jq -r '.[] | .message.data' | while read DATA; do
    gcloud pubsub topics publish $TARGET_TOPIC --message=$DATA
  done

  echo "Batch de $BATCH_SIZE mensagens reprocessadas"
  sleep 2
done

Opção 3: Replay com Filtragem

# Republicar apenas mensagens de uma agência específica
gcloud pubsub subscriptions pull enrichment-worker-dlq-sub \
  --limit=100 \
  --format=json | \
  jq -r '.[] | select(.message.attributes.agency_id == "mgi") | .message.data' | \
  while read DATA; do
    gcloud pubsub topics publish dgb.news.scraped --message=$DATA
  done

Reconciliação Automática

DAG Airflow de Reconciliação

# dags/reconciliation_dlq.py
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime, timedelta

default_args = {
    'owner': 'data-platform',
    'depends_on_past': False,
    'email_on_failure': True,
    'email': ['oncall@destaquesgovbr.gov.br'],
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

with DAG(
    'reconciliation_dlq',
    default_args=default_args,
    description='Reconcilia mensagens na DLQ após correção de bugs',
    schedule_interval=None,  # Manual trigger
    start_date=datetime(2026, 1, 1),
    catchup=False,
) as dag:

    check_dlq = BashOperator(
        task_id='check_dlq',
        bash_command="""
        COUNT=$(gcloud pubsub subscriptions describe enrichment-worker-dlq-sub \
          --format="value(messageRetentionDuration)")
        echo "Mensagens na DLQ: $COUNT"
        """,
    )

    replay_messages = BashOperator(
        task_id='replay_messages',
        bash_command="""
        gcloud pubsub subscriptions pull enrichment-worker-dlq-sub \
          --limit=1000 \
          --format=json | \
          jq -r '.[].message.data' | \
          while read DATA; do
            gcloud pubsub topics publish dgb.news.scraped --message=$DATA
          done
        """,
    )

    check_dlq >> replay_messages

Best Practices

1. Ack Deadline

Recomendação: ack_deadline = max_processing_time * 1.5

# Para worker com P99 = 20s
ack_deadline = 20s * 1.5 = 30s

# Para worker com P99 = 400s (ex: embeddings)
ack_deadline = 400s * 1.5 = 600s

2. Max Delivery Attempts

Recomendação: 5 tentativas (suficiente para erros transitórios)

  • Menos de 5: Pode descartar mensagens recuperáveis
  • Mais de 5: Aumenta latência sem ganho significativo

3. Backoff Policy

Recomendação: Exponencial com limites razoáveis

--retry-policy-minimum-backoff=10s   # Evita retry imediato
--retry-policy-maximum-backoff=600s  # Evita espera excessiva

4. DLQ Retention

Recomendação: 7 dias (padrão Pub/Sub)

# Aumentar retenção se necessário
gcloud pubsub topics update dgb.news.scraped-dlq \
  --message-retention-duration=14d

Troubleshooting

Problema: DLQ crescendo rapidamente

Diagnóstico:

# Ver taxa de crescimento
gcloud pubsub subscriptions describe enrichment-worker-dlq-sub \
  --format="value(numUndeliveredMessages)"

Causas: - Bug no worker (crash loop) - Rate limit AWS Bedrock - PostgreSQL indisponível - Payload malformado sistemático

Solução: 1. Pausar publisher temporariamente 2. Corrigir bug no worker 3. Deploy nova versão 4. Replay mensagens DLQ

Problema: Mensagens voltam para DLQ após replay

Causa: Bug não corrigido ou condição ainda presente.

Solução: 1. Analisar logs do worker após replay 2. Identificar erro específico 3. Corrigir código ou configuração 4. Testar com 1 mensagem antes de replay completo

Problema: DLQ com mensagens antigas (>7 dias)

Causa: Mensagens expiradas (retention policy).

Solução:

# Verificar retenção
gcloud pubsub topics describe dgb.news.scraped-dlq \
  --format="value(messageRetentionDuration)"

# Não é possível recuperar mensagens expiradas
# Reprocessar desde a fonte (scraper)


Métricas de Sucesso

Métrica Target Atual Status
DLQ Messages < 10 - ⚠️ Monitorar
Replay Success Rate > 95% - ⚠️ Monitorar
Time to Resolution < 1 hora - ⚠️ Monitorar
Max Delivery Attempts 5 5 ✅ OK

Referências

Interna

Externa


Última atualização: 05/05/2026
Responsável: Equipe Data Platform - DestaquesGovbr