simpla.fydocs
Obs Normalizer

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.

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.dlq

Alternativamente, 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 obs

Se 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_duration

Falhas 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.

On this page