Estratégias de observabilidade em serviços backend com métricas RED

1. Fundamentos das métricas RED e sua aplicação em backend

1.1. Origem do modelo RED e sua relação com o método USE

O modelo RED foi proposto por Tom Wilkie, engenheiro da Grafana Labs, como uma adaptação do método USE (Utilization, Saturation, Errors) criado por Brendan Gregg para infraestrutura. Enquanto o USE foca em recursos de sistema (CPU, memória, disco), o RED é voltado para serviços: Rate (taxa de requisições), Errors (erros) e Duration (duração). Essa mudança de paradigma reflete a necessidade de monitorar o comportamento de software distribuído, não apenas hardware.

1.2. Por que métricas RED são essenciais para microsserviços

Em arquiteturas de microsserviços, um único fluxo de usuário pode atravessar dezenas de serviços. Métricas RED oferecem três sinais vitais por serviço:

  • Rate: quantas requisições estão sendo processadas por segundo
  • Errors: quantas falhas estão ocorrendo (absolutas ou percentuais)
  • Duration: quanto tempo cada requisição leva para ser concluída

Esses três indicadores permitem identificar rapidamente se um serviço está saudável, degradado ou falho, sem precisar analisar dezenas de métricas diferentes.

1.3. Diferenças entre RED, USE e os quatro sinais dourados

Os quatro sinais dourados da Google SRE (Latência, Tráfego, Erros, Saturação) são mais abrangentes, incluindo saturação de recursos. O RED simplifica isso para três métricas focadas em serviço, sendo complementar ao USE. Na prática:

  • USE → infraestrutura (CPU, memória, disco)
  • RED → serviços (requests, errors, latency)
  • Quatro sinais dourados → visão combinada

2. Instrumentação de serviços para coleta de métricas RED

2.1. Escolha de bibliotecas e frameworks

Para implementar métricas RED, as opções mais comuns são:

  • Prometheus client (Go, Java, Python, Ruby): biblioteca madura e leve
  • OpenTelemetry SDKs: padrão moderno que permite exportar para múltiplos backends
  • Micrometer (Java): abstração que suporta Prometheus, Datadog, Graphite

Exemplo de inicialização com Prometheus client em Go:

package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    requestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "endpoint", "status"},
    )
    requestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request latency in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "endpoint"},
    )
)

func init() {
    prometheus.MustRegister(requestsTotal)
    prometheus.MustRegister(requestDuration)
}

2.2. Implementação de métricas de Rate

Rate é medida como requisições por segundo. Com Prometheus, usa-se a função rate() sobre contadores. Exemplo de middleware que incrementa o contador:

func metricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        wrapped := &responseWriter{ResponseWriter: w}
        next.ServeHTTP(wrapped, r)
        duration := time.Since(start).Seconds()
        requestsTotal.With(prometheus.Labels{
            "method":   r.Method,
            "endpoint": r.URL.Path,
            "status":   strconv.Itoa(wrapped.status),
        }).Inc()
        requestDuration.With(prometheus.Labels{
            "method":   r.Method,
            "endpoint": r.URL.Path,
        }).Observe(duration)
    })
}

2.3. Boas práticas para labels, cardinalidade e naming

  • Labels moderadas: evite labels com alta cardinalidade (ex: user_id, session_id). Limite a 5-10 labels por métrica.
  • Naming consistente: use snake_case com prefixo do domínio (http_requests_total, db_query_duration_seconds).
  • Unidades no nome: inclua _seconds, _bytes, _total para clareza.

3. Métricas de Errors: identificação e categorização de falhas

3.1. Definição de erro

Nem todo HTTP 4xx é erro de serviço. Defina claramente:

  • Erros de infraestrutura: HTTP 5xx, timeouts, conexões recusadas
  • Erros de negócio: HTTP 4xx específicos (ex: 422 para validação) podem ser monitorados separadamente
  • Erros downstream: falhas em chamadas a outros serviços

3.2. Criação de métricas de erro por categoria

# Métrica de erro por status code
http_requests_errors_total{method="GET", endpoint="/users", status="500"} 42

# Métrica de erro por exceção (ex: Java)
application_errors_total{exception="NullPointerException", service="user-service"} 15

# Métrica de erro downstream
downstream_errors_total{target="payment-service", error_type="timeout"} 8

3.3. Alertas baseados em taxa de erro

Para SLOs, use burn rates. Exemplo de alerta no Prometheus:

# Alerta para burn rate de 2 horas (SLO de 99.9%)
- alert: HighErrorRate
  expr: |
    (rate(http_requests_errors_total[5m]) / rate(http_requests_total[5m])) > 0.001
  for: 2h
  labels:
    severity: critical
  annotations:
    summary: "Error rate above SLO threshold for 2 hours"

4. Métricas de Duration: latência e distribuição de tempos de resposta

4.1. Uso de histogramas e quantis

Histogramas permitem calcular percentis. Exemplo de configuração:

requestDuration := prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "http_request_duration_seconds",
        Help:    "Request latency distribution",
        Buckets: []float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
    },
    []string{"endpoint"},
)

Para calcular p99 no PromQL:

histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

4.2. Separando latência do servidor vs. rede

Use instrumentação em múltiplos pontos:

  • Latência de rede: métrica no cliente HTTP (antes de enviar requisição)
  • Latência de fila: tempo entre recebimento e início do processamento
  • Latência de processamento: tempo efetivo de execução

4.3. Configuração de buckets adequados

Buckets devem cobrir a faixa esperada de latência:

  • Serviços críticos (API gateway): buckets de 1ms a 500ms
  • Serviços de batch: buckets de 100ms a 30s
  • Serviços de streaming: buckets de 10ms a 100ms

5. Dashboards e visualização de métricas RED

5.1. Estrutura de dashboard por serviço

Um dashboard RED típico tem três linhas:

  1. Linha superior: Rate (req/s), Error Rate (%), Duration (p50, p95, p99)
  2. Linha do meio: gráficos de séries temporais para cada métrica
  3. Linha inferior: breakdown por endpoint e status code

5.2. Drill-down por endpoint e versão

Use variáveis de template no Grafana:

# Variável: endpoint
label_values(http_requests_total, endpoint)

# Variável: version
label_values(http_requests_total, version)

5.3. Heatmaps e séries temporais

Heatmaps de latência mostram distribuição ao longo do tempo:

# No Grafana: visualization type = Heatmap
# Query: rate(http_request_duration_seconds_bucket[5m])

6. Alertas inteligentes baseados em métricas RED

6.1. Alertas multicondição

Combine métricas para maior precisão:

# Alerta composto: alta latência + aumento de erros
- alert: ServiceDegraded
  expr: |
    (rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m]) > 1)
    and
    (rate(http_requests_errors_total[5m]) > 0.1)
  for: 5m

6.2. Redução de falsos positivos

Use janelas deslizantes e thresholds adaptativos:

  • Janela de 5 minutos para detecção rápida
  • Janela de 1 hora para confirmação
  • Threshold baseado em desvio padrão (3 sigma)

6.3. Integração com sistemas de incidente

Exemplo de webhook para PagerDuty:

receivers:
- name: 'pagerduty'
  pagerduty_configs:
  - routing_key: 'YOUR_INTEGRATION_KEY'
    severity: 'critical'
    description: 'Error rate exceeded SLO threshold'

7. Integração de métricas RED com tracing e logging

7.1. Correlação via exemplars

Exemplars no Prometheus permitem ligar métricas a traces específicos:

# Configuração no Prometheus
exemplars:
  max_exemplars: 10000

No código, adicione trace_id como exemplar:

requestDuration.With(prometheus.Labels{"endpoint": "/users"}).
    Observe(duration, prometheus.Labels{"trace_id": traceID})

7.2. Enriquecimento de logs

Inclua métricas no contexto dos logs estruturados:

log.WithFields(log.Fields{
    "endpoint": "/users",
    "duration_ms": duration*1000,
    "error": err != nil,
}).Info("Request processed")

7.3. Unificação com Prometheus + Jaeger + Loki

Use o Grafana como camada de unificação:

  • Prometheus: métricas RED
  • Jaeger: tracing distribuído
  • Loki: logs centralizados

Com o Grafana Explore, é possível navegar de um alerta de métrica para o trace correspondente e depois para os logs daquele request específico.

Referências