MÓDULO 10 · CONCEITO 03 DE 12

Métricas e Prometheus

Counters, gauges, histograms, summaries — RED e USE methods, PromQL, cardinality explosions e exemplars

Tempo de leitura ~24 min Pré-requisito 01 · Observabilidade vs Monitoramento Próximo 04 · Distributed Tracing

Métricas são agregações numéricas do comportamento do sistema ao longo do tempo. Onde logs capturam eventos individuais e traces capturam jornadas de requisições, métricas capturam padrões agregados — a taxa de requisições por segundo, o percentil 99 de latência, o número de conexões abertas, o uso de CPU ao longo do tempo. Métricas são baratas de armazenar (séries temporais comprimidas), excelentes para alertas e dashboards, e rápidas de consultar em janelas de tempo. O Prometheus se tornou o padrão de fato para métricas em sistemas cloud-native, e seu modelo de dados e linguagem de query (PromQL) são fundamentos que todo engenheiro sênior deve dominar.

Os quatro tipos de métrica do Prometheus

Counter

Um counter só aumenta — nunca diminui (exceto ao resetar para zero quando o processo reinicia). Representa uma contagem cumulativa: total de requisições processadas, total de erros, total de bytes enviados. Sempre use rate() ou increase() sobre counters para obter taxas significativas — o valor absoluto de um counter isolado raramente é útil.

# Declaração — sufixo _total é convencional para counters
http_requests_total{method="POST", endpoint="/api/orders", status="200"} 1847

# PromQL — taxa de requisições nos últimos 5 minutos
rate(http_requests_total[5m])

# Taxa de erros (status 5xx)
rate(http_requests_total{status=~"5.."}[5m])
  /
rate(http_requests_total[5m])

Gauge

Um gauge pode aumentar ou diminuir — representa um valor instantâneo. Conexões abertas, uso de memória, tamanho de fila, temperatura. Use diretamente sem rate() — o valor atual já é o dado relevante.

# Exemplos de gauge
db_connections_open{pool="primary"}     42
queue_depth{queue="orders"}             156
memory_used_bytes{type="heap"}          1073741824
go_goroutines                           847

# PromQL — máximo de conexões abertas nas últimas 24h
max_over_time(db_connections_open[24h])

# Alerta: fila crescendo
db_connections_open > 80   # threshold simples

Histogram

Um histogram mede a distribuição de valores em buckets pré-definidos. É o tipo mais importante para latência — permite calcular percentis (P50, P95, P99) com PromQL. Gera três séries: _bucket (contagem por bucket), _sum (soma dos valores), _count (total de observações).

# Histogram com buckets para latência HTTP (em segundos)
http_request_duration_seconds_bucket{le="0.005"}  1200
http_request_duration_seconds_bucket{le="0.01"}   2100
http_request_duration_seconds_bucket{le="0.025"}  3800
http_request_duration_seconds_bucket{le="0.05"}   4200
http_request_duration_seconds_bucket{le="0.1"}    4350
http_request_duration_seconds_bucket{le="0.25"}   4390
http_request_duration_seconds_bucket{le="0.5"}    4398
http_request_duration_seconds_bucket{le="1"}      4399
http_request_duration_seconds_bucket{le="+Inf"}   4400
http_request_duration_seconds_sum    87.3
http_request_duration_seconds_count  4400

# PromQL — P99 de latência
histogram_quantile(0.99,
  rate(http_request_duration_seconds_bucket[5m])
)

# P99 por endpoint
histogram_quantile(0.99,
  sum by (endpoint, le) (
    rate(http_request_duration_seconds_bucket[5m])
  )
)
atenção Os buckets do histogram precisam ser configurados antecipadamente — você não pode mudar os buckets depois sem perder histórico. Escolha buckets que cobrem a distribuição esperada de latência com boa resolução na cauda. Para latência de API web típica: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5] (em segundos). Para operações de banco de dados: escale uma ordem de magnitude para baixo.

Summary

Summary também mede distribuições, mas calcula quantis no lado do cliente (na aplicação), não no servidor (Prometheus). Isso torna summaries não-agregáveis — você não pode combinar percentis de múltiplas instâncias. Use histograms na maioria dos casos; summary só faz sentido quando você precisa de percentis exatos e tem apenas uma instância.

RED Method — métricas para serviços

RED (Rate, Errors, Duration) é o framework de Tom Wilkie (Grafana Labs) para instrumentar serviços:

Rate: requisições por segundo. O sinal de volume — quantas coisas estão acontecendo. rate(http_requests_total[5m])

Errors: taxa de erros — proporção de requisições que falharam. O sinal de qualidade. rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m])

Duration: distribuição de latência, especialmente a cauda (P95, P99). O sinal de velocidade — quão rápido para o pior caso. histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

Esses três juntos cobrem a maioria das perguntas de "está funcionando bem?" para qualquer serviço orientado a requisições.

USE Method — métricas para recursos

USE (Utilization, Saturation, Errors) de Brendan Gregg complementa o RED para recursos do sistema:

Utilization: quanto do recurso está sendo usado (0-100%). CPU a 80%, disco a 70%, pool de conexões a 60%.

Saturation: quanto trabalho está esperando para ser atendido. Fila de I/O, tamanho da run queue de CPU, requisições esperando conexão no pool. Saturação é o sinal mais precoce de problema — você pode estar com CPU a 60% mas com run queue crescendo.

Errors: taxa de erros do recurso. Erros de I/O de disco, pacotes descartados pela NIC, conexões rejeitadas pelo pool.

Cardinality explosions — o problema mais comum

Cada combinação única de labels cria uma série temporal separada no Prometheus. Com poucos labels de baixa cardinalidade, isso é manejável. Com labels de alta cardinalidade, o Prometheus fica sem memória.

# Cardinalidade controlada — OK
http_requests_total{
  service="order-service",    # ~10 serviços
  endpoint="/api/orders",     # ~50 endpoints
  method="POST",              # ~5 métodos
  status="200"                # ~10 status codes
}
# Total: 10 × 50 × 5 × 10 = 25.000 séries — manejável

# Cardinalidade explosiva — NUNCA faça
http_requests_total{
  user_id="usr-12345678",     # 10M usuários únicos
  trace_id="4bf92f3577b...",  # 1 por requisição
  url="/api/orders?filter=..." # query params únicos
}
# Total: potencialmente bilhões de séries — Prometheus morre

Sinais de cardinalidade explosion: uso de memória do Prometheus crescendo sem parar, scrape duration aumentando, queries ficando lentas. Diagnóstico: prometheus_tsdb_head_series mostra o número de séries ativas — se estiver acima de 10M, investigar quais labels têm alta cardinalidade.

# PromQL para identificar métricas com mais séries
topk(10, count by (__name__)({__name__=~".+"}))

Exemplars — ligando métricas a traces

Exemplars são o elo entre métricas e traces. Um exemplar é um ponto de dado específico vinculado a uma métrica de histogram — normalmente o trace_id da requisição que caiu naquele bucket. No Grafana, você pode clicar em um pico de latência no dashboard e ir diretamente para um trace representativo daquele pico.

# Formato de exemplar no Prometheus (OpenMetrics)
http_request_duration_seconds_bucket{le="0.5"} 4398 # {trace_id="4bf92f357..."} 0.42 1715299200

# Instrumentação com exemplar (Go + OTel)
histogram.Record(ctx, latency,
    metric.WithAttributeSet(attrs),
)
// O OTel SDK extrai automaticamente o trace_id do contexto
// e o adiciona como exemplar ao histogram

Comparativo entre linguagens — instrumentação com Prometheus

C# — prometheus-net com exemplars
// C# — métricas RED com prometheus-net e exemplars OTel

using Prometheus;

// Declaração de métricas (singleton — declare uma vez)
public static class Metrics {
    public static readonly Counter RequestsTotal = Prometheus.Metrics
        .CreateCounter(
            "http_requests_total",
            "Total de requisições HTTP",
            new CounterConfiguration {
                LabelNames = ["method", "endpoint", "status"],
            }
        );

    public static readonly Histogram RequestDuration = Prometheus.Metrics
        .CreateHistogram(
            "http_request_duration_seconds",
            "Duração das requisições HTTP",
            new HistogramConfiguration {
                LabelNames = ["method", "endpoint"],
                Buckets    = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
            }
        );

    public static readonly Gauge QueueDepth = Prometheus.Metrics
        .CreateGauge(
            "orders_queue_depth",
            "Profundidade da fila de pedidos"
        );
}

// Middleware que instrumenta automaticamente todos os endpoints
app.UseHttpMetrics(options => {
    options.AddCustomLabel("endpoint",
        ctx => ctx.GetEndpoint()?.DisplayName ?? ctx.Request.Path);
});

// Instrumentação manual em handlers de negócio
public async Task<Order> PlaceOrderAsync(PlaceOrderCommand cmd) {
    using var timer = Metrics.RequestDuration
        .WithLabels("POST", "/api/orders")
        .NewTimer();  // para automaticamente ao sair do using

    try {
        var order = await _repo.CreateAsync(cmd);
        Metrics.RequestsTotal.WithLabels("POST", "/api/orders", "201").Inc();
        return order;
    } catch {
        Metrics.RequestsTotal.WithLabels("POST", "/api/orders", "500").Inc();
        throw;
    }
}

// Expõe /metrics para scraping do Prometheus
app.MapMetrics();  // rota padrão: /metrics

O middleware UseHttpMetrics do prometheus-net instrumenta automaticamente todos os endpoints ASP.NET Core com counters e histograms. Métricas customizadas de negócio (QueueDepth) complementam as métricas de infraestrutura. Os exemplars são adicionados automaticamente quando o OTel está configurado.

Python — prometheus-client com WSGI middleware
# Python — métricas RED com prometheus-client

from prometheus_client import (
    Counter, Histogram, Gauge,
    CollectorRegistry, make_asgi_app,
    CONTENT_TYPE_LATEST, generate_latest,
)
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
import time

# Declaração de métricas
REQUEST_COUNT = Counter(
    "http_requests_total",
    "Total de requisições HTTP",
    ["method", "endpoint", "status"],
)

REQUEST_DURATION = Histogram(
    "http_request_duration_seconds",
    "Duração das requisições HTTP",
    ["method", "endpoint"],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
)

QUEUE_DEPTH = Gauge(
    "orders_queue_depth",
    "Profundidade da fila de pedidos",
)

# Middleware de instrumentação automática
class PrometheusMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Normaliza o path para evitar alta cardinalidade
        # /api/orders/123 → /api/orders/{id}
        path = self._normalize_path(request.url.path)

        start = time.perf_counter()
        response = await call_next(request)
        elapsed = time.perf_counter() - start

        REQUEST_COUNT.labels(
            method=request.method,
            endpoint=path,
            status=str(response.status_code),
        ).inc()

        REQUEST_DURATION.labels(
            method=request.method,
            endpoint=path,
        ).observe(elapsed)

        return response

    def _normalize_path(self, path: str) -> str:
        import re
        # Substitui IDs numéricos e UUIDs por placeholder
        path = re.sub(r"/[0-9a-f]{8}-[0-9a-f-]{27}", "/{id}", path)
        path = re.sub(r"/\d+", "/{id}", path)
        return path

app = FastAPI()
app.add_middleware(PrometheusMiddleware)

# Endpoint /metrics
@app.get("/metrics")
async def metrics():
    from fastapi.responses import PlainTextResponse
    return PlainTextResponse(
        generate_latest(),
        media_type=CONTENT_TYPE_LATEST,
    )

# Instrumentação de negócio
async def place_order(cmd: PlaceOrderCommand) -> Order:
    with REQUEST_DURATION.labels("POST", "/api/orders").time():
        order = await order_service.place(cmd)
    QUEUE_DEPTH.set(await queue.depth())
    return order

A normalização de paths no middleware é crítica — sem ela, /api/orders/123 e /api/orders/456 criam séries separadas, causando cardinalidade explosion. O padrão /{id} agrupa todos os IDs na mesma série.

Go — prometheus/client_golang com OTel exemplars
// Go — métricas RED com prometheus/client_golang

package metrics

import (
    "net/http"
    "regexp"
    "strconv"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    RequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total de requisições HTTP",
        },
        []string{"method", "endpoint", "status"},
    )

    RequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "Duração das requisições HTTP",
            Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5},
        },
        []string{"method", "endpoint"},
    )

    QueueDepth = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "orders_queue_depth",
        Help: "Profundidade da fila de pedidos",
    })
)

// pathNormalizer substitui IDs dinâmicos por placeholder
var (
    uuidRe    = regexp.MustCompile(`/[0-9a-f]{8}-[0-9a-f-]{27}`)
    numericRe = regexp.MustCompile(`/\d+`)
)

func normalizePath(p string) string {
    p = uuidRe.ReplaceAllString(p, "/{id}")
    p = numericRe.ReplaceAllString(p, "/{id}")
    return p
}

// Middleware de instrumentação
func InstrumentHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        endpoint := normalizePath(r.URL.Path)
        rw := &responseWriter{ResponseWriter: w, status: 200}

        start := time.Now()
        next.ServeHTTP(rw, r)
        elapsed := time.Since(start).Seconds()

        status := strconv.Itoa(rw.status)
        RequestsTotal.WithLabelValues(r.Method, endpoint, status).Inc()
        RequestDuration.WithLabelValues(r.Method, endpoint).Observe(elapsed)
    })
}

// Handler /metrics com suporte a exemplars (OpenMetrics format)
func MetricsHandler() http.Handler {
    return promhttp.HandlerFor(
        prometheus.DefaultGatherer,
        promhttp.HandlerOpts{
            EnableOpenMetrics: true,  // habilita exemplars no formato OpenMetrics
        },
    )
}

type responseWriter struct {
    http.ResponseWriter
    status int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.status = code
    rw.ResponseWriter.WriteHeader(code)
}

promauto registra as métricas automaticamente no registry padrão — sem precisar chamar prometheus.MustRegister manualmente. EnableOpenMetrics: true é necessário para que o Prometheus colete exemplars — sem isso, os exemplars são ignorados mesmo que o OTel os gere.

Decisões de engenharia

Counter vs Gauge vs Histogram: qual tipo usar

Counter quando o valor só cresce e você quer taxas: total de requisições, total de erros, bytes enviados. Use rate() sobre o counter para obter taxa por segundo. Nunca use counter para algo que pode diminuir — se o valor cair, o Prometheus interpretará como reset e gerará spike falso.

Gauge quando o valor sobe e desce: conexões abertas, uso de memória, tamanho de fila, temperatura. Guages podem ser usados diretamente sem rate(). Use avg_over_time() ou max_over_time() para suavizar ruído.

Histogram para distribuição de latência ou tamanhos: você define os buckets antecipadamente e o Prometheus conta quantas observações caem em cada bucket. Use histogram_quantile(0.99, rate(...)) para calcular P99 do lado do servidor de queries — mais preciso para agrupamentos e mais flexível que Summary, mas requer planejamento dos buckets.

Summary: calcula percentis no lado do cliente (no serviço instrumentado) — sem necessidade de definir buckets. Mais preciso para percentis individuais, mas não pode ser agregado entre instâncias. Evite Summary quando você tem múltiplas replicas — os percentis de cada instância não podem ser combinados matematicamente.

rate() vs irate() vs increase()

rate(metric[5m]): taxa média por segundo na janela de 5 minutos. Suaviza spikes — boa para dashboards e alertas. O intervalo da janela deve ser pelo menos 4× o intervalo de scrape (para ter amostras suficientes). Padrão para a maioria dos casos.

irate(metric[5m]): taxa instantânea baseada nas últimas 2 amostras dentro da janela. Reage muito mais rápido a spikes — boa para detectar picos momentâneos. Problemática para alertas porque é volátil: um único spike alto pode disparar um alerta falso. Use irate em dashboards onde você quer ver picos, não em alertas.

increase(metric[1h]): total de aumento no counter na janela. Útil para "quantas requisições aconteceram na última hora" em vez de "quantas por segundo". Matematicamente é rate() × duração_da_janela. Use para SLO burn rate calculations e relatórios de volume.

Histogram nativo vs Histogram clássico (buckets fixos)

Histogram clássico (pré-Prometheus 2.40): você define os buckets no código de instrumentação — ex: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] segundos. O Prometheus armazena um counter por bucket. Problema: se o P99 real fica acima do maior bucket, você perde precisão. Se os buckets são granulares demais, você aumenta cardinalidade.

Native Histogram (Prometheus 2.40+, experimental): buckets gerados automaticamente com resolução exponencial. Não há configuração de buckets na instrumentação — o SDK decide a granularidade dinamicamente. Resultado: precisão consistente em qualquer faixa de valores, menor cardinalidade (1 série vs N séries por bucket clássico). É o futuro do Prometheus, mas requer Prometheus 2.40+ e suporte no SDK. Para Go: prometheus.DefBuckets ainda é o padrão; native histograms requerem prometheus.NewHistogramVec com NativeHistogramBucketFactor.

Alerting: threshold simples vs SLO burn rate

Threshold simples: rate(errors[5m]) / rate(total[5m]) > 0.01. Fácil de entender, fácil de configurar. Problemas: muitos falsos positivos (um spike de 30s dispara o alerta), muitos falsos negativos (degradação gradual que nunca excede o threshold), e não considera o impacto real ao usuário (1% de erro às 3h com 100 req/s tem impacto muito menor que às 14h com 10k req/s).

SLO burn rate: "estamos consumindo o error budget N× mais rápido que o esperado para o mês". Com SLO de 99.9% (0.1% de budget mensal = 43 minutos), burn rate 14× significa que o budget será esgotado em 3 horas — alerta crítico. Burn rate 2× = budget esgotado em 15 dias — alerta de warning. A query PromQL combina janelas curta e longa para evitar falsos positivos. É mais complexo de configurar mas produz alertas muito mais acionáveis. O Google SRE Workbook tem as queries de referência.

Como praticar

  1. Instrumente um serviço HTTP simples com as três métricas RED usando Prometheus. Exponha /metrics, configure um Prometheus local para fazer scrape, e crie um Grafana dashboard com os três painéis: rate (req/s), error rate (%), e heatmap de latência com P50/P95/P99. Use k6 para gerar carga e observe as métricas em tempo real.
    Critério: dashboard mostra as 3 métricas com dados reais; ao introduzir latência artificial no handler (sleep aleatório), o heatmap de latência muda visivelmente; ao introduzir erros (5xx em 10% dos requests), o error rate reflete corretamente.
  2. Demonstre cardinalidade explosion: adicione user_id como label em uma métrica, gere 10.000 usuários únicos e monitore o crescimento de prometheus_tsdb_head_series. Compare o uso de memória antes e depois. Corrija removendo o label e movendo user_id para atributo de span OTel.
    Critério: com label user_id: prometheus_tsdb_head_series cresce proporcionalmente ao número de usuários únicos; sem label: métrica permanece constante; diferença de uso de memória documentada com números concretos.
  3. Configure exemplars: instrumente um endpoint com OTel + Prometheus, habilite OpenMetrics no endpoint /metrics com EnableOpenMetrics: true, e configure o Grafana para mostrar exemplars no painel de latência. Em um pico de P99, clique no exemplar e navegue para o trace correspondente no Jaeger/Tempo.
    Critério: exemplars visíveis como pontos no gráfico de latência; clique em um ponto de latência alta navega diretamente para o trace; o trace mostra o span que causou a latência alta.
  4. Implemente o USE method para um pool de conexões de banco: Utilization (connections_open / connections_max), Saturation (connections_waiting), Errors (connections_failed_total). Crie um alerta Alertmanager quando Saturation > 0 por mais de 30 segundos consecutivos.
    Critério: as 3 métricas são visíveis no Grafana com valores reais; ao saturar o pool (mais goroutines que conexões disponíveis), Saturation > 0 e o alerta dispara em até 35 segundos; alerta resolve automaticamente quando o pool volta à normalidade.
  5. Escreva e valide queries PromQL: (a) taxa de erros 5xx por endpoint nas últimas 1h com topk(5, ...), (b) P99 de latência por serviço comparando com 1h atrás (offset 1h) para detectar degradação, (c) serviços com disponibilidade abaixo de 99.9% nos últimos 7 dias, (d) SLO burn rate de 1h e 6h para um SLO de 99.9%.
    Critério: as 4 queries retornam resultados corretos com dados reais; a query de burn rate identifica corretamente quando o budget está sendo consumido acima do esperado; cada query documentada com explicação da lógica PromQL.

Perguntas de entrevista

    Qual a diferença entre Counter, Gauge, Histogram e Summary no Prometheus? Quando usar cada um?

    Counter: valor monotonicamente crescente. Representa totais acumulados. Use para: total de requisições, total de erros, bytes enviados, jobs completados. Nunca use para algo que pode diminuir. Para extrair taxa: rate(counter[5m]). O sufixo _total é convencional.

    Gauge: valor que pode subir e descer. Representa estado atual. Use para: conexões abertas, uso de memória, tamanho de fila, temperatura, número de goroutines ativas. Use diretamente em queries sem rate().

    Histogram: distribui observações em buckets pré-definidos. Cria 3 séries: _bucket (contador por bucket), _sum (soma de todas as observações), _count (número de observações). Use histogram_quantile(0.99, rate(metric_bucket[5m])) para P99. Vantagem: percentis podem ser agregados entre instâncias. Use para latência e tamanhos de payload.

    Summary: calcula percentis no cliente. Não precisa de buckets. Não pode ser agregado entre instâncias (os percentis não são matematicamente combináveis). Use apenas quando você tem uma única instância e precisa de percentis muito precisos. Em ambientes com múltiplas replicas, prefira sempre Histogram.

    Por que rate() é necessário para counters? O que acontece se você usar o valor absoluto?

    O problema do valor absoluto: um counter de http_requests_total = 5.000.000 não diz nada sozinho. Você não sabe se esse valor representa 1 hora de tráfego intenso ou 6 meses de tráfego baixo. E quando o processo reinicia, o counter reseta para 0 — gerando um spike negativo aparente no gráfico se você plotar o valor bruto.

    Por que rate(): rate(http_requests_total[5m]) calcula a taxa de crescimento por segundo na janela de 5 minutos, corrigindo automaticamente para resets do processo (quando o counter diminui, o Prometheus detecta como reset e ajusta o cálculo). O resultado é sempre uma taxa significativa e comparável — "47 req/s" — independente do valor absoluto acumulado.

    Escolha da janela: a janela do range vector ([5m]) deve ser pelo menos 4× o intervalo de scrape. Com scrape a cada 15s, use no mínimo [1m]. Janelas muito pequenas podem retornar NaN se não houver amostras suficientes. Janelas muito grandes suavizam demais e ocultam spikes.

    increase() vs rate(): increase(metric[1h]) é equivalente a rate(metric[1h]) × 3600 — retorna o total de aumento na janela, não a taxa por segundo. Use increase para "quantas requisições na última hora", rate para "quantas por segundo".

    O que são exemplars no Prometheus e como eles conectam métricas a traces?

    Exemplars: amostras específicas associadas a um ponto de dado de métrica, contendo um trace_id (e opcionalmente span_id e outros labels). Permitem navegar de um ponto em um gráfico de métrica diretamente para o trace que gerou aquele ponto de dado.

    Fluxo: o SDK de instrumentação (OTel + prometheus-go-client) ao registrar uma observação de latência no histograma, inclui automaticamente o trace_id do span ativo como exemplar no bucket correspondente. O endpoint /metrics com OpenMetrics ativado (Accept: application/openmetrics-text) expõe os exemplars junto com os buckets.

    Como usar no Grafana: no painel de latência, habilite "Exemplars" na query. Os exemplars aparecem como pontos coloridos sobrepostos ao gráfico. Clicando em um ponto de P99 alto, o Grafana abre o trace correspondente no Jaeger/Tempo configurado como datasource. É a navegação mais direta de "o P99 subiu" → "qual request causou".

    Requisitos: Prometheus 2.26+ para armazenar exemplars (--enable-feature=exemplar-storage), endpoint com EnableOpenMetrics: true, e OTel instrumentando o span ativo no momento da observação de métrica.

    Como funciona o cálculo de SLO burn rate em PromQL? Por que é superior a alertas de threshold?

    Conceito de burn rate: com SLO de 99.9% mensal, você tem 0.1% de error budget = ~43 minutos de downtime por mês. Se você está errando 1% das requisições agora, está consumindo o budget 10× mais rápido que o sustentável — o budget acaba em 4.3 dias.

    Query de burn rate:

    -- Taxa de erro atual (janela curta para detectar rápido)
    rate(http_requests_total{status=~"5.."}[1h])
      /
    rate(http_requests_total[1h])
      > 14 * 0.001  -- burn rate 14×: budget esgotado em 2h
    

    O alerta combinado usa duas janelas (curta + longa) para reduzir falsos positivos: a janela curta detecta rápido, a janela longa confirma que não é um spike momentâneo.

    Por que é superior a threshold: (1) considera o volume de tráfego — 1% de erro às 3h com 100 req/s tem impacto muito menor que às 14h com 10k req/s; (2) não dispara em spikes de 30s que não ameaçam o budget; (3) captura degradações graduais que ficam abaixo do threshold mas consomem budget lentamente; (4) correlaciona diretamente com o SLO e o impacto ao usuário.

    Como você projetaria os buckets de um Histogram de latência para um serviço de pagamentos?

    O problema dos buckets ruins: buckets muito largos perdem precisão (você sabe que P99 está entre 1s e 5s, mas não onde). Buckets muito granulares ou fora da faixa relevante aumentam cardinalidade sem benefício — um bucket de 10s para uma API que nunca passa de 2s é desperdício.

    Processo de design: (1) conheça a distribuição real de latência do serviço — meça P50, P95, P99 em staging ou produção com dados históricos; (2) coloque os buckets ao redor dos percentis críticos e dos SLOs; (3) use escala logarítmica para cobrir uma faixa ampla com poucos buckets.

    Para um serviço de pagamentos com SLO de P99 < 2s:

    Buckets: [0.05, 0.1, 0.2, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0]
             -- 50ms, 100ms, 200ms (P50 esperado)
             -- 500ms (P95 esperado)
             -- 1s, 1.5s, 2s (ao redor do SLO)
             -- 3s, 5s, 10s (degradação severa)
    

    Com esses buckets, histogram_quantile(0.99, ...) tem resolução de ~50ms na faixa relevante. O bucket de +Inf captura tudo acima de 10s — se P99 = +Inf, o serviço está completamente degradado.

    Native Histograms (Prometheus 2.40+): eliminam a necessidade de planejar buckets manualmente. Considere migrar quando o SDK suportar — é o futuro do ecossistema Prometheus.

Referências

  1. docs Prometheus Data Model & Metric Types — prometheus.io. prometheus.io/docs/concepts/data_model — Referência oficial dos tipos de métrica, nomenclatura, labels e o modelo de dados TSDB. Leitura obrigatória antes de instrumentar qualquer serviço.
  2. artigo The RED Method — Tom Wilkie, Grafana Blog (2018). grafana.com/blog/2018/08/02/the-red-method — Introdução ao RED por seu criador, com comparação ao USE method de Brendan Gregg e exemplos de quando cada um se aplica.
  3. artigo USE Method — Brendan Gregg. brendangregg.com/usemethod.html — Definição completa do USE method com exemplos de métricas para CPU, memória, disco, rede e outros recursos do sistema. Inclui um checklist de métricas por tipo de recurso.
  4. docs PromQL Cheat Sheet — Prometheus. promlabs.com/promql-cheat-sheet — Referência rápida das funções PromQL mais usadas: rate, irate, increase, histogram_quantile, topk, bottomk, label_replace. Com exemplos práticos.
  5. livro Prometheus: Up & Running — Brian Brazil (O'Reilly, 2018). O livro definitivo do Prometheus — arquitetura, tipos de métrica, PromQL com profundidade, alerting, federation e storage. Escrito pelo principal committer do Prometheus fora da SoundCloud original.
  6. docs Prometheus Histograms and Summaries — prometheus.io. prometheus.io/docs/practices/histograms — Guia oficial de quando usar Histogram vs Summary, como escolher buckets, e como calcular percentis com histogram_quantile(). Inclui discussão sobre native histograms e suas vantagens sobre buckets fixos.
  7. docs Prometheus Alerting Rules — prometheus.io. prometheus.io/docs/prometheus/latest/configuration/alerting_rules — Sintaxe completa de alerting rules no Prometheus: for clause, labels, annotations, e como configurar o Alertmanager como receiver. Inclui exemplos de alertas de SLO burn rate.
  8. artigo Alerting on SLOs — Google SRE Workbook — Google. sre.google/workbook/alerting-on-slos — Capítulo do SRE Workbook com as queries de referência de SLO burn rate, explicação das janelas de alerta (1h crítico, 6h warning), e por que essa abordagem reduz drasticamente ruído de alertas comparado a thresholds simples.
  9. docs PromQL Functions Reference — prometheus.io. prometheus.io/docs/prometheus/latest/querying/functions — Referência completa de todas as funções PromQL: rate, irate, increase, histogram_quantile, predict_linear, delta, deriv, label_replace e mais. Com exemplos e notas sobre quando cada função é apropriada.
  10. blog How Does a Prometheus Counter Work? — Robust Perception. robustperception.io/how-does-a-prometheus-counter-work — Artigo técnico profundo sobre o comportamento interno de counters: como o SDK registra observações, como o Prometheus detecta resets, e por que rate() corrige automaticamente para reinicializações de processo. Esclarecedor para quem vem de sistemas de métricas baseados em gauges.
  11. docs Prometheus Exemplars — prometheus.io. prometheus.io/docs/prometheus/latest/feature_flags/#exemplars-storage — Como habilitar storage de exemplars no Prometheus, o formato OpenMetrics para exemplars no /metrics, e como configurar o Grafana para navegar de exemplar a trace. Requer Prometheus 2.26+ e EnableOpenMetrics: true no SDK.
  12. artigo Prometheus Best Practices — Writing Exporters — prometheus.io. prometheus.io/docs/instrumenting/writing_exporters — Guia oficial para escrever exporters e instrumentar serviços corretamente: convenções de nomenclatura (sufixos _total, _seconds, _bytes), labels recomendados, o que não expor como métrica, e como lidar com métricas que podem causar cardinalidade alta inadvertidamente.