Monitoramento e observabilidade não são sinônimos nem são excludentes — são abordagens com objetivos diferentes para um problema diferente. Monitoramento responde à pergunta "o sistema está funcionando?". Observabilidade responde à pergunta "por que o sistema está se comportando assim?". A primeira é verificação; a segunda é investigação. Em sistemas simples, monitoramento é suficiente — você sabe com antecedência quais falhas podem acontecer e prepara checks para detectá-las. Em sistemas distribuídos complexos, as falhas mais importantes são exatamente aquelas que você não antecipou. Para essas, só observabilidade funciona.
A distinção foi articulada com precisão por Cindy Sridharan em "Distributed Systems Observability" (O'Reilly, 2018). A formulação: um sistema é observável se você consegue entender seu estado interno a partir das saídas externas que ele produz — sem precisar modificar o código, sem precisar parar o sistema, e sem precisar saber de antemão qual pergunta vai fazer. Observabilidade é uma propriedade do sistema, não de uma ferramenta. Você pode ter Datadog, Grafana, Jaeger e Elastic e ainda não ter observabilidade se os dados que esses sistemas coletam não permitem investigação livre.
O problema com monitoramento tradicional
Monitoramento tradicional funciona assim: você conhece os modos de falha do sistema, escreve checks para cada um, configura alertas quando os checks falham. CPU > 80% por 5 minutos → alerta. Latência média > 500ms → alerta. Taxa de erro > 1% → alerta. Isso funciona bem para falhas conhecidas e previsíveis.
O problema é que em sistemas distribuídos, as falhas mais interessantes são combinações de condições que ninguém previu: um cliente específico com um payload específico atinge um serviço que está no limite de memória porque outro tenant fez uma query grande ao mesmo tempo, e o garbage collector entrou em ciclo completo, causando latência de 3 segundos apenas para aquele segmento de usuários — enquanto a latência média do sistema inteiro permanece em 120ms. Nenhum threshold de CPU, memória ou latência média vai detectar isso. E sem observabilidade, você vai levar horas investigando com logs grep e suposições.
Sridharan resume: monitoramento diz que algo está errado; observabilidade diz o quê, onde, por quê, e para quem.
Os três pilares clássicos
A forma tradicional de estruturar observabilidade é pelos três pilares: logs, métricas e traces. Cada um captura um aspecto diferente do comportamento do sistema, e os três juntos cobrem a maioria das questões de investigação.
Logs são registros discretos de eventos que aconteceram no sistema. Cada log captura um fato: "requisição recebida", "query executada", "erro ao conectar ao banco". Logs são o instrumento mais detalhado — podem conter qualquer informação — mas são caros de armazenar e difíceis de agregar. A força dos logs é na investigação de um incidente específico: "o que exatamente aconteceu com a requisição X?" responde com um log detalhado.
Métricas são agregações numéricas do comportamento do sistema ao longo do tempo. Taxa de requisições, percentil 99 de latência, uso de memória, número de conexões abertas. Métricas são baratas de armazenar (séries temporais comprimidas), excelentes para visualização e alertas, mas perdem o contexto individual — você sabe que o P99 subiu, mas não sabe qual requisição está no P99.
Traces são o registro da jornada de uma requisição pelo sistema distribuído — o caminho completo, com cada serviço, cada chamada de banco, cada operação de cache, com timings. Traces conectam o que as métricas mostram (latência alta) com o contexto que os logs têm (qual operação específica). O trace é o fio condutor que une os outros dois pilares.
O quarto pilar: profiling contínuo
Desde 2018, um quarto pilar emergiu: profiling contínuo. Profiling pontual (executar um profiler por 30 segundos para investigar) sempre existiu, mas profiling contínuo — coletar dados de CPU, heap e goroutines em produção, continuamente, com overhead baixo o suficiente para ser sempre ligado — é novo. Ferramentas como Pyroscope, Parca e o Google Cloud Profiler tornam isso viável.
O profiling contínuo responde perguntas que nenhum dos três pilares clássicos responde: "qual função está consumindo 40% do CPU neste serviço?" Traces mostram que o span X é lento; profiling mostra qual linha de código dentro desse span é responsável. A combinação trace → profile é o estado da arte em debugging de performance em produção.
Cardinalidade — o problema central
Cardinalidade é o número de valores únicos que uma dimensão pode ter. O user_id de um sistema com 10 milhões de usuários tem cardinalidade 10M. O campo url_path com query params únicos pode ter cardinalidade praticamente infinita. Cardinalidade alta é o inimigo das métricas em escala.
Sistemas de métricas como Prometheus armazenam uma série temporal por combinação de labels. Uma métrica com labels service (10 valores) × endpoint (100 valores) × status_code (20 valores) tem 20.000 séries temporais — ainda manejável. Adicione user_id com cardinalidade 10M: agora são 2 trilhões de séries. O Prometheus explode. Este é o "cardinalidade explosion" — e é o motivo pelo qual user_id, order_id e qualquer identificador único de negócio não devem ser labels de métricas.
Alta cardinalidade pertence aos logs e traces, não às métricas. A regra: se o valor pode ter cardinalidade > 1000, não coloque em label de métrica — coloque no log ou como atributo de span.
// ERRADO: user_id como label → cardinalidade explosiva
counter.Add(1, new TagList {
{ "user_id", userId }, // 10M valores únicos
{ "endpoint", "/api/orders" },
});
// CORRETO: user_id no span (trace) ou log, não na métrica
counter.Add(1, new TagList {
{ "endpoint", "/api/orders" }, // cardinalidade controlada
{ "status", "success" },
});
// user_id vai no span como atributo — rastreável via trace
span.SetAttribute("user.id", userId);
Observabilidade como propriedade de sistema
Observabilidade não é algo que você adiciona depois — é algo que você projeta. Um sistema é observável quando:
Emite eventos ricos: cada operação significativa gera um registro com contexto suficiente para investigação (IDs correlacionados, usuário afetado, parâmetros relevantes, timing). Não "erro 500", mas "erro 500 ao processar pedido ord-123 do usuário usr-456 com payload de 15KB — tempo decorrido 2.3s — causa: timeout na chamada ao inventory-service".
Permite exploração livre: você consegue fazer perguntas que não antecipou. "Qual é a latência do endpoint /checkout para usuários na região sul com mais de 5 itens no carrinho?" — se você não pode responder isso sem mudar o código, o sistema não é suficientemente observável.
Correlaciona pilares: trace IDs nos logs, exemplars nas métricas, links entre spans e profiles. Os dados de observabilidade se referenciam mutuamente.
É proporcional: não instrumentar tudo com o mesmo nível de detalhe — instrumentação tem custo (CPU, memória, storage, egress). Paths críticos têm instrumentação completa; operações rotineiras de baixo risco têm sampling reduzido.
Monitoramento e observabilidade são complementares
A conclusão prática: você precisa dos dois. Monitoramento (com bons alertas baseados em SLO) diz quando agir. Observabilidade diz o que fazer quando você age. O alerta "burn rate do SLO de latência está 14x acima do normal" é monitoramento. A investigação que se segue — abrir o trace do request P99, correlacionar com o log do serviço afetado, olhar o flame graph de CPU — é observabilidade.
Times que têm só monitoramento ficam presos em dashboards de threshold, gerando alerta após alerta sem entender causas raiz. Times que têm só observabilidade sem alertas descobrem problemas tarde — quando o usuário reclama, não quando o sistema começa a degradar. O estado da arte é um sistema de monitoramento baseado em SLO que dispara alertas precocemente, alimentado por dados de observabilidade que tornam a investigação possível.
user_id específico nos traces? Consegue correlacionar esse trace com os logs daquele request e com a métrica de saturação do recurso no mesmo instante? Se não, é monitoramento com interface nova, não observabilidade.
Comparativo entre linguagens — emissão de eventos ricos
// C# — evento rico com contexto completo
// Serilog com enrichers + OTel trace context automático
// Program.cs — configuração
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithEnvironmentName()
.WriteTo.Console(new JsonFormatter())
.WriteTo.OpenTelemetry(opts => {
opts.Endpoint = "http://otel-collector:4318/v1/logs";
opts.ResourceAttributes = new Dictionary<string, object> {
["service.name"] = "order-service",
["service.version"] = "2.1.0",
};
})
.CreateLogger();
// Handler — evento rico com contexto de negócio
public async Task<IResult> PlaceOrder(
PlaceOrderCommand cmd,
ILogger<OrderEndpoint> log,
IOrderService svc)
{
// O trace ID é injetado automaticamente pelo OTel log bridge
// Enrichers adicionam machine name, environment, etc.
using var scope = log.BeginScope(new Dictionary<string, object> {
["CustomerId"] = cmd.CustomerId,
["ItemCount"] = cmd.Items.Count,
["TotalValue"] = cmd.Items.Sum(i => i.Price),
});
var sw = Stopwatch.StartNew();
try {
var order = await svc.PlaceAsync(cmd);
// Evento rico: não "pedido criado", mas tudo que importa para investigação
log.LogInformation(
"Pedido {OrderId} criado para cliente {CustomerId} " +
"com {ItemCount} itens totalizando {TotalValue:C} em {ElapsedMs}ms",
order.Id, cmd.CustomerId, cmd.Items.Count,
cmd.Items.Sum(i => i.Price), sw.ElapsedMilliseconds
);
return Results.Created($"/orders/{order.Id}", order);
} catch (InsufficientInventoryException ex) {
// Contexto de negócio no log de erro — não apenas o stack trace
log.LogWarning(
"Estoque insuficiente para pedido do cliente {CustomerId}: " +
"{MissingItem} tem apenas {Available} unidades, necessário {Requested}",
cmd.CustomerId, ex.ItemSku, ex.Available, ex.Requested
);
return Results.Conflict(new { error = "insufficient_inventory", sku = ex.ItemSku });
}
}
// Output JSON (linha única por evento — indexável):
// {
// "timestamp": "2026-05-09T14:23:01.123Z",
// "level": "Information",
// "message": "Pedido ord-123 criado para cliente cust-456...",
// "OrderId": "ord-123",
// "CustomerId": "cust-456",
// "ItemCount": 3,
// "TotalValue": 299.90,
// "ElapsedMs": 87,
// "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
// "span_id": "00f067aa0ba902b7",
// "MachineName": "pod-order-service-7d9f8",
// "Environment": "production"
// }
Serilog com BeginScope adiciona campos estruturados a todos os logs dentro do escopo — sem repetir o CustomerId em cada linha. O bridge OTel injeta trace_id e span_id automaticamente. O output JSON é indexável por qualquer sistema de log aggregation.
# Python — structlog com injeção automática de trace context
import structlog
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
import time
# Processor que injeta trace context em cada log
def inject_trace_context(logger, method, event_dict):
span = trace.get_current_span()
if span.is_recording():
ctx = span.get_span_context()
event_dict["trace_id"] = format(ctx.trace_id, "032x")
event_dict["span_id"] = format(ctx.span_id, "016x")
event_dict["sampled"] = bool(ctx.trace_flags & 0x01)
return event_dict
def add_service_context(logger, method, event_dict):
event_dict["service"] = "order-service"
event_dict["service_ver"] = "2.1.0"
return event_dict
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars, # contexto por request
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
inject_trace_context,
add_service_context,
structlog.processors.JSONRenderer(),
],
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
async def place_order(cmd: PlaceOrderCommand) -> Order:
# Adiciona contexto de negócio a todos os logs deste request
structlog.contextvars.bind_contextvars(
customer_id=cmd.customer_id,
item_count=len(cmd.items),
total_value=sum(i.price for i in cmd.items),
)
start = time.perf_counter()
try:
order = await order_service.place(cmd)
elapsed_ms = int((time.perf_counter() - start) * 1000)
log.info(
"pedido_criado",
order_id=order.id,
elapsed_ms=elapsed_ms,
)
return order
except InsufficientInventoryError as e:
log.warning(
"estoque_insuficiente",
missing_sku=e.sku,
available=e.available,
requested=e.requested,
)
raise
finally:
structlog.contextvars.clear_contextvars()
# Output JSON:
# {"event": "pedido_criado", "level": "info", "timestamp": "2026-05-09T14:23:01Z",
# "order_id": "ord-123", "customer_id": "cust-456", "item_count": 3,
# "total_value": 299.90, "elapsed_ms": 87,
# "trace_id": "4bf92f3577b34da6...", "span_id": "00f067aa0ba902b7",
# "service": "order-service", "service_ver": "2.1.0"}
structlog.contextvars.bind_contextvars é thread-safe e async-safe — vincula campos ao contexto de execução atual sem afetar outras coroutines. Cada evento de log herda automaticamente os campos vinculados, sem precisar passá-los explicitamente.
// Go — log/slog com trace context e campos de negócio
package main
import (
"context"
"log/slog"
"os"
"time"
"go.opentelemetry.io/otel/trace"
)
// Handler que injeta trace context em cada record
type traceHandler struct {
slog.Handler
}
func (h traceHandler) Handle(ctx context.Context, r slog.Record) error {
span := trace.SpanFromContext(ctx)
if span.IsRecording() {
sCtx := span.SpanContext()
r.AddAttrs(
slog.String("trace_id", sCtx.TraceID().String()),
slog.String("span_id", sCtx.SpanID().String()),
)
}
return h.Handler.Handle(ctx, r)
}
func newLogger() *slog.Logger {
base := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
})
return slog.New(traceHandler{base}).With(
slog.String("service", "order-service"),
slog.String("service_ver", "2.1.0"),
)
}
var logger = newLogger()
func PlaceOrder(ctx context.Context, cmd PlaceOrderCmd) (Order, error) {
// Logger com contexto de negócio para este request
reqLog := logger.With(
slog.String("customer_id", cmd.CustomerID),
slog.Int("item_count", len(cmd.Items)),
slog.Float64("total_value", cmd.TotalValue()),
)
start := time.Now()
order, err := orderService.Place(ctx, cmd)
elapsed := time.Since(start).Milliseconds()
if err != nil {
var invErr *InsufficientInventoryError
if errors.As(err, &invErr) {
reqLog.WarnContext(ctx, "estoque_insuficiente",
slog.String("missing_sku", invErr.SKU),
slog.Int("available", invErr.Available),
slog.Int("requested", invErr.Requested),
)
}
return Order{}, err
}
reqLog.InfoContext(ctx, "pedido_criado",
slog.String("order_id", order.ID),
slog.Int64("elapsed_ms", elapsed),
)
return order, nil
}
// Output JSON (slog.NewJSONHandler):
// {"time":"2026-05-09T14:23:01Z","level":"INFO","msg":"pedido_criado",
// "service":"order-service","service_ver":"2.1.0",
// "customer_id":"cust-456","item_count":3,"total_value":299.90,
// "order_id":"ord-123","elapsed_ms":87,
// "trace_id":"4bf92f3577b34da6...","span_id":"00f067aa0ba902b7"}
log/slog é a solução de logging estruturado da stdlib Go desde 1.21 — sem dependências externas. O traceHandler que envolve qualquer slog.Handler é o padrão de composição do slog. InfoContext/WarnContext passam o contexto para o handler, permitindo extrair o trace span sem acoplar o código de negócio à instrumentação.
Decisões de engenharia
Monitoramento é suficiente quando: você conhece os modos de falha do sistema com antecedência, o sistema é simples e seus componentes têm comportamento previsível, e as perguntas de debugging são do tipo "está funcionando ou não?". Infraestrutura básica (CPU, disco, rede) é bem atendida por monitoramento com thresholds. O custo é baixo: qualquer equipe consegue configurar alertas de CPU e latência média.
Observabilidade é necessária quando: falhas emergem de interações entre serviços que ninguém previu, diferentes segmentos de usuários têm experiências diferentes (o P99 geral é bom mas 0.1% dos usuários VIP têm latência de 10s), ou você precisa debugar em produção sem reprodução. O investimento em observabilidade é maior (instrumentação, coleta, correlação), mas é o único caminho para "unknown unknowns". Em sistemas distribuídos com mais de 5 serviços, observabilidade é mandatória.
Métricas: para alertas e monitoramento contínuo. Baratas de armazenar (séries temporais comprimidas), excelentes para dashboards e SLO alerting. Não revelam contexto individual — você sabe que o P99 subiu mas não qual requisição. Use para "algo está errado" e para SLOs.
Traces: para "o que aconteceu com esta requisição específica" em sistemas distribuídos. Mostram causalidade entre serviços, latência por operação, e a jornada completa de um request. Mais caros (alto volume de dados), requerem sampling. Use para debugging de latência e falhas cross-service.
Logs: para contexto detalhado dentro de um serviço específico. São o instrumento mais rico mas o mais caro de armazenar e mais difícil de correlacionar. Com trace_id nos logs, você navega do trace para os logs do span problemático. Use para o "porquê" depois que traces mostram o "onde".
Pull (Prometheus): o servidor de métricas scrape endpoints /metrics nos serviços. Vantagens: simples de debugar (você mesmo pode fazer curl no endpoint), autodiscovery via service discovery, sem ponto de coleta centralizado obrigatório. O serviço não precisa saber onde está o Prometheus. Adequado para Kubernetes onde o Prometheus usa ServiceMonitor/PodMonitor para descobrir targets. Limitação: não funciona bem para jobs de curta duração (o Prometheus pode não scrapar antes do job terminar).
Push (StatsD, Datadog Agent, OTLP push): o serviço envia métricas para um aggregator. Vantagens: funciona com jobs de curta duração (um job de 5s pode empurrar métricas ao terminar), mais simples para arquiteturas não-Kubernetes, e não requer que o serviço exponha uma porta HTTP. Limitação: cria dependência do aggregator — se ele cai, você perde métricas. O Prometheus Pushgateway é a solução pull-para-jobs-curtos: o job empurra para o gateway, o Prometheus scrapa o gateway.
Self-hosted (Prometheus + Grafana + Jaeger/Tempo + Loki): custo de infraestrutura menor, controle total dos dados (compliance, LGPD), sem vendor lock-in. Custo real: operação contínua, configuração de retenção, alta disponibilidade de cada componente. Adequado para equipes com experiência em SRE e quando retenção de dados em território específico é requisito regulatório.
SaaS (Datadog, Grafana Cloud, Honeycomb, New Relic): zero operação, features avançadas out-of-the-box (ML anomaly detection, profiling contínuo, analysis de trace em escala). Custo: preço por volume e por host pode ser significativo em escala (Datadog é famoso por surpresas na fatura). Vendor lock-in na instrumentação se não usar OTel. Recomendado para startups e times pequenos onde o custo de operação do self-hosted é proibitivo. Use OTel para instrumentação independente do backend escolhido.
Como praticar
-
Implemente um sistema de observabilidade mínimo completo: configure structured logging com trace_id automático (usando slog + OTel bridge), exponha métricas Prometheus RED por endpoint (
/metrics), e instrumente uma chamada HTTP com OpenTelemetry. Use Jaeger local para visualizar o trace. Navegue do trace para os logs usando o trace_id.
Critério: dado um trace_id de um request com erro, você consegue encontrar o log exato do erro em menos de 10 segundos; o span de banco de dados aparece como filho do span HTTP no Jaeger. -
Demonstre o problema de cardinalidade: crie uma métrica
http_requests_totalcom labeluser_ide simule 10.000 usuários únicos fazendo requisições. Observe o crescimento de memória do Prometheus (process_resident_memory_bytes). Em seguida, movauser_idpara atributo de span OTel e compare o uso de recursos.
Critério: com label user_id: memória do Prometheus cresce linearmente com número de usuários únicos; com atributo de span: memória do Prometheus é constante; diferença documentada com números de before/after. -
Construa um cenário de "unknown unknown": configure dois serviços onde uma falha ocorre apenas quando
customer_tier=premium+item_count > 10+ uma condição de corrida. Configure alertas de P99 médio e taxa de erro total — ambos devem ficar abaixo do threshold. Usando traces com atributos de negócio, identifique o subgrupo afetado.
Critério: alertas de threshold NÃO disparam durante o incidente (falha é invisible ao monitoramento); buscando porcustomer_tier=premiumnos traces, você encontra o padrão em menos de 5 minutos; root cause documentada. -
Configure dois tipos de alerta para a mesma falha: (1) CPU > 80% por 5 minutos e (2) SLO burn rate (taxa de consumo do error budget). Provoque uma degradação gradual de latência que afeta 2% dos usuários. Meça qual alerta dispara primeiro e qual deles fornece informação diretamente acionável.
Critério: documentação comparativa: tempo até disparo de cada alerta, informação fornecida por cada um, e qual ação imediata o alerta permite; SLO burn rate deve disparar primeiro e com contexto de impacto ao usuário. -
Leia "Distributed Systems Observability" (Cindy Sridharan, O'Reilly — disponível gratuitamente online). Mapeie os gaps de observabilidade do seu projeto atual: liste 5 perguntas de debugging que você NÃO consegue responder hoje sem modificar código ou reproduzir o problema. Para cada gap, proponha a instrumentação que resolveria.
Critério: 5 gaps identificados com evidência concreta de por que não são respondíveis hoje; cada gap com proposta de instrumentação específica (qual sinal, qual atributo, qual query) e estimativa de esforço de implementação.
Perguntas de entrevista
Qual a diferença entre monitoramento e observabilidade? Por que a distinção importa na prática?
Monitoramento: você define com antecedência o que verificar — thresholds de CPU, taxa de erro, latência média. Funciona para falhas conhecidas e previsíveis. O sistema "está funcionando" ou "não está". A pergunta que o monitoramento responde é binária.
Observabilidade (definição de Cindy Sridharan): um sistema é observável se você consegue entender seu estado interno a partir das saídas externas que ele produz — sem modificar o código e sem saber de antemão qual pergunta vai fazer. É a capacidade de investigação livre. A pergunta que a observabilidade responde é aberta: "por que o sistema está se comportando assim para este subgrupo de usuários?"
Por que importa: as falhas mais custosas em sistemas distribuídos são "unknown unknowns" — combinações de condições que ninguém previu. Um cliente VIP com payload específico + memória do serviço sob pressão de outro tenant + GC pause = latência de 10s para 0.1% dos usuários, enquanto P99 geral permanece em 200ms. Nenhum threshold detecta isso. Só investigação com dados de alta cardinalidade (traces com atributos de negócio) revela o padrão. A distinção importa porque influencia a instrumentação: para monitoramento, você precisa de métricas agregadas; para observabilidade, você precisa de eventos ricos com atributos arbitrários.
O que é "cardinalidade" em métricas e por que ela é o principal limitador de performance do Prometheus?
Cardinalidade: o número de séries temporais únicas que uma métrica gera, determinado pela combinação de valores possíveis de suas labels. Uma métrica http_requests_total{method, status_code, endpoint} com 4 métodos × 10 status codes × 100 endpoints = 4.000 séries temporais. Isso é cardinalidade controlada.
O problema: adicionar uma label de alta cardinalidade explode o número de séries. http_requests_total{user_id=...} com 1 milhão de usuários = 1 milhão de séries temporais. O Prometheus armazena cada série em memória para ingestão eficiente. Alta cardinalidade = alto uso de memória, queries lentas, e eventual OOM do Prometheus.
Regra prática: labels de métricas devem ter valores de cardinalidade baixa e conhecida — status_code, method, region, service_name. Informação de alta cardinalidade (user_id, request_id, payload específico) pertence a atributos de spans em traces, não a labels de métricas. O Prometheus não é o lugar certo para dados de alta cardinalidade — Honeycomb, Grafana Tempo, e Elastic são.
Sinal de alerta: número total de séries ativas no Prometheus pode ser monitorado com prometheus_tsdb_head_series. Mais de 1-2 milhões de séries em um único Prometheus indica cardinalidade problemática ou falta de federação.
Como você estruturaria a estratégia de observabilidade de um novo microsserviço antes de ir para produção?
Instrumentação obrigatória (dia 1): (1) métricas RED por endpoint — http_requests_total{method, status, endpoint}, http_request_duration_seconds como histograma; (2) trace de toda requisição HTTP de entrada com OTel, propagando contexto para chamadas downstream; (3) logs estruturados com trace_id e span_id automáticos; (4) health check endpoint (/health + /ready) com checks de dependências críticas.
SLOs e alertas: defina os SLOs antes de ir para produção — disponibilidade (ex: 99.9%) e latência (P99 < 500ms). Configure burn rate alerts que disparam com antecedência suficiente para ação. Não crie alertas de threshold que ninguém age — cada alerta deve ter um runbook.
Atributos de negócio nos spans: adicione customer_id, customer_tier, feature_flag, e outros atributos de domínio relevantes aos spans raiz. Isso permite debugging por segmento de usuário sem precisar de queries em logs. É o que separa observabilidade de tracing técnico.
Runbook de debugging: documente como ir do alerta ao trace ao log para os 3 modos de falha mais prováveis. Um alerta sem runbook é ruído — alguém vai investigar sem saber por onde começar.
O que são os "quatro sinais dourados" do Google SRE e como eles diferem do método RED?
Quatro Sinais Dourados (Google SRE Book, capítulo 6): Latency (tempo de resposta — distinguir sucesso de falha), Traffic (demanda — req/s, transações/s), Errors (taxa de requisições que falham — explicitamente ou silenciosamente), Saturation (quão cheio está o serviço — uso de recursos que limita a capacidade).
RED Method (Tom Wilkie, Grafana): Rate (taxa de requisições/s), Errors (requisições com erro/s), Duration (distribuição de latência). É um subconjunto dos Quatro Sinais Dourados focado em serviços.
Diferença chave: os Quatro Sinais Dourados incluem Saturation — que é um sinal de infraestrutura (CPU, memória, conexões, filas). RED não inclui saturation porque é focado na perspectiva do usuário do serviço. Na prática, RED + Saturation das dependências críticas (pool de conexões do banco, tamanho da fila) é o conjunto ideal para um serviço. RED para o SLO do serviço; USE para as dependências que podem saturar.
Quando usar qual: use RED para SLOs e alertas de nível de serviço — é simples de implementar e fácil de explicar para stakeholders. Use os Quatro Sinais Dourados (+ USE) quando precisar de diagnóstico mais completo incluindo sinais de infraestrutura. O Google SRE Book recomenda instrumentar com os quatro sinais; o RED é uma simplificação prática que funciona para a maioria dos casos.
Como você explicaria a diferença entre um alerta de threshold e um alerta de SLO burn rate? Qual é mais útil e por quê?
Alerta de threshold: "P99 > 500ms por 5 minutos". Disparado quando uma métrica pontual excede um valor fixo. Problemas: (1) muitos falsos positivos — um spike de 30 segundos não significa que o SLO está em risco; (2) muitos falsos negativos — uma degradação gradual que nunca excede o threshold mas consome o error budget lentamente passa despercebida; (3) não sabe o impacto no usuário — P99 alto às 3h da manhã com tráfego mínimo tem impacto muito menor que às 14h.
Alerta de SLO burn rate: "o error budget está sendo consumido N vezes mais rápido que o esperado". Se o SLO é 99.9% de disponibilidade (0.1% de error budget mensal), e você está errando 1% das requisições agora, está consumindo o budget 10× mais rápido — em 3 dias esgota o budget do mês. O alerta dispara baseado na taxa de consumo, não em um valor absoluto.
Por que burn rate é melhor: (1) considera o volume de tráfego — 1% de erro às 2h com 100 req/min é muito menos crítico que 1% às 14h com 10k req/min; (2) correlaciona com impacto real ao usuário e SLO; (3) permite priorização — burn rate 10× em alerta crítico, burn rate 2× em warning. O Google SRE Book e o Prometheus Alerting Guideline recomendam substituir thresholds por burn rate para alertas de SLO.
Referências
- livro Distributed Systems Observability — Cindy Sridharan (O'Reilly, 2018).
- livro Observability Engineering — Charity Majors, Liz Fong-Jones & George Miranda (O'Reilly, 2022).
- artigo Twitter's Observability Engineering — Twitter Engineering Blog.
- artigo Dapper, a Large-Scale Distributed Systems Tracing Infrastructure — Google (2010).
- docs OpenTelemetry Concepts — opentelemetry.io.
- livro Site Reliability Engineering — Betsy Beyer et al. (O'Reilly / Google, 2016).
- blog Monitoring is for Knowing; Observability is for Asking — Baron Schwartz.
- artigo Observability — a 3-Year Retrospective — Charity Majors (Honeycomb, 2019).
- artigo SLO alerting: Burning Down the House — Google SRE Workbook.
- blog High Cardinality is Not a Cardinal Sin — Honeycomb Engineering.
- padrão OpenMetrics Specification — CNCF.
- livro Prometheus: Up & Running — Brian Brazil (O'Reilly, 2018).