Erros e DLQ
Política de retry, circuit breaker, dead letter queue e procedimentos de recuperação para o obs-normalizer.
Erros e DLQ
O obs-normalizer trata falhas em duas camadas: erros de ingestão (payload inválido, autenticação) que retornam imediatamente ao webhook caller, e erros de publicação no RabbitMQ que passam por retry com backoff exponencial e circuit breaker. Eventos que não roteiam para nenhuma fila ficam descartados pelo broker — clientes downstream lidam com sua própria DLQ.
Tipos de falha
O serviço classifica falhas em quatro categorias, cada uma com tratamento distinto.
Validação de entrada
Payload mal formado, header de assinatura ausente ou inválido. Resposta imediata 400 ou 401, sem retry.
Schema canônico inválido
O normalizer produziu um ObsEvent que não passa na validação Zod. Resposta 500, evento descartado.
Falha transitória de rede
ECONNREFUSED, ETIMEDOUT, fetch failed ou 5xx do RabbitMQ Management API. Retry com backoff.
Falha permanente
4xx do RabbitMQ, exchange inexistente, autenticação inválida. Sem retry, propaga 503 ao caller.
A classificação de erro retryable é feita por regex sobre a mensagem:
function isRetryableError(err: unknown): boolean {
if (!(err instanceof Error)) return false
const msg = err.message
if (/ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EPIPE/i.test(msg)) return true
if (/fetch failed|network/i.test(msg)) return true
if (/RabbitMQ publish failed: [5]\d\d/.test(msg)) return true
return false
}Erros 4xx do broker (rota inexistente, vhost errado, credencial revogada) não são retryable — falham rápido para evitar amplificar problemas de configuração.
Retry com backoff
Toda publicação passa pelo ResilientPublisher, que aplica retry com backoff exponencial e jitter antes de propagar o erro ao handler do webhook.
Parâmetros padrão:
const DEFAULT_RETRY = {
maxRetries: 3,
baseDelayMs: 200,
maxDelayMs: 5000,
}O delay entre tentativas é calculado por:
function computeBackoff(attempt: number, opts: RetryOpts): number {
const exponential = opts.baseDelayMs * Math.pow(2, attempt)
const capped = Math.min(exponential, opts.maxDelayMs)
const jitter = capped * 0.2 * Math.random()
return Math.floor(capped + jitter)
}Sequência típica de espera com defaults: 200 ms, 400 ms, 800 ms, totalizando até 1.4 s mais jitter antes do erro final. Após esgotar maxRetries, a tentativa final registra falha no circuit breaker e propaga a exceção.
O backoff só roda para erros classificados como transitórios. Erros 4xx ou de validação interrompem o loop imediatamente, mesmo no primeiro attempt.
Circuit breaker
Em cima do retry há um CircuitBreaker com três estados — closed, open e half_open. Defaults:
const DEFAULT_CB = {
failureThreshold: 5,
resetTimeoutMs: 30_000,
}Comportamento:
- closed: publicações fluem normalmente. Cada falha incrementa o contador.
- open: atingido o limite de 5 falhas consecutivas, o breaker abre. Novas chamadas falham imediatamente com
RabbitMQ circuit breaker is open, sem tentar publicar. - half_open: após 30 segundos sem novas tentativas, o estado transita para
half_open. A próxima chamada é um probe — sucesso fecha o breaker, falha o reabre por mais 30 s.
O breaker protege o RabbitMQ de tempestades de retries quando o broker está down e devolve resposta rápida ao webhook caller, evitando timeouts em GlitchTip/Alertmanager.
DLQ e inspeção
O obs-normalizer publica diretamente em uma exchange do RabbitMQ via Management API; ele não possui uma DLQ própria nem persiste eventos. A política de mensagens não roteadas e dead-lettering depende da configuração das filas downstream no broker.
Comportamento atual no publish:
const data = (await res.json()) as { routed: boolean }
if (!data.routed) {
throw new Error(`RabbitMQ publish not routed (no matching binding): ${...}`)
}Se nenhum binding casa com a routing key {event_type}.{severity}.{service}, o broker responde routed: false e o normalizer propaga erro 503 — o webhook caller pode tentar novamente conforme sua própria política.
Eventos perdidos por bug de roteamento não são recuperáveis a partir do normalizer. Garanta bindings completos na exchange obs.events antes do rollout.
Inspecionando filas downstream
Para verificar mensagens acumuladas, profundidade de fila ou conteúdo da DLQ de um consumer:
# Listar filas do vhost
kubectl exec -n simplafy-prd-2 rabbitmq-0 -- \
rabbitmqctl list_queues -p obs name messages messages_ready
# Inspecionar uma mensagem da DLQ (peek, sem ack)
kubectl exec -n simplafy-prd-2 rabbitmq-0 -- \
rabbitmqctl get_message_count obs.dlqAlternativamente, use o Management UI em https://rabbitmq.simplafy.com.br para inspecionar payloads, headers x-death e republish manual.
Recuperação manual
Identificar a janela do incidente
Consulte as métricas Prometheus do normalizer e o GlitchTip do próprio serviço para localizar quando o publish começou a falhar:
curl -s http://obs-normalizer.simplafy-prd-2:3000/metrics \
| grep -E 'obs_normalizer_(publish|circuit)'Confirmar o estado do broker
Verifique conectividade, vhost e bindings da exchange obs.events:
kubectl exec -n simplafy-prd-2 rabbitmq-0 -- \
rabbitmqctl list_bindings -p obsSe o estado do circuit breaker está open, aguarde os 30 s de reset ou force restart do pod do normalizer para resetar o contador.
Replay a partir da origem
Como o normalizer não persiste eventos, recuperação significa reenviar pela origem:
- GlitchTip: reentregar webhook via UI do projeto em Settings > Webhooks > Recent deliveries.
- Alertmanager: alertas firing são reenviados automaticamente conforme
repeat_interval. Para forçar, use a API/api/v2/alerts.
Republish manual de mensagens em DLQ downstream
Para consumers que acumularam mensagens em sua própria DLQ durante o incidente, use o Management UI para mover mensagens de volta à fila principal, ou aplique um shovel temporário.
Métricas de erro
O serviço expõe métricas Prometheus em /metrics com prefixo obs_normalizer_. Para falhas, as séries relevantes são:
Erros HTTP nos endpoints de ingestão aparecem nas métricas padrão do Fastify e nos logs estruturados com error: invalid_payload, unauthorized, internal_validation_failure ou publish_failed.
curl -s http://localhost:3000/metrics | grep http_request_durationFalhas finais (após esgotar retries) propagam exceção e geram log publish failed no logger Fastify. Use Loki para agregar:
{namespace="simplafy-prd-2", app="obs-normalizer"}
| json
| err != ""
| line_format "{{.err}}"O estado atual do breaker é exposto via ResilientPublisher.circuitBreakerState. O callback onStateChange permite emitir métricas customizadas a cada transição closed -> open -> half_open.
Alertas críticos sobre falha persistente do normalizer devem chegar via GlitchTip do próprio serviço — exceções no handler do webhook são capturadas pelo SDK e roteadas para a issue tracker do projeto obs-normalizer.
Para investigação aprofundada, consulte Operations e Configuração.