Tracing distribuído
A métrica diz que o p95 do checkout dobrou. O log do serviço de checkout diz que a chamada interna demorou. Qual das seis chamadas? O trace responde: ele reconstrói uma requisição inteira, atravessando processos, com o tempo de cada trecho e a relação de causa entre eles.
Trace, span e contexto
Seção intitulada “Trace, span e contexto”Um span é uma operação com início, fim, atributos e status. Um trace é a árvore de
spans com o mesmo trace_id; cada span aponta para o pai. O contexto é o par
trace_id + span_id que precisa viajar junto com a chamada — se ele se perde, você
ganha vários traces órfãos em vez de um.
trace 4bf92f35… ── POST /checkout 412 ms ├── validar-carrinho 18 ms ├── consultar-estoque 102 ms ├── autorizar-pagamento 271 ms ← o tempo está aqui │ └── HTTP POST gateway.cielo 268 ms └── gravar-pedido 19 msPropagação entre serviços
Seção intitulada “Propagação entre serviços”O padrão é o cabeçalho traceparent do W3C, propagado em toda saída HTTP, gRPC ou
mensagem de fila.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 │ └ trace_id └ span_id └ flags (amostrado)Os três pontos onde a propagação quebra na prática:
- Fila e job assíncrono. O contexto precisa ir dentro da mensagem; ninguém propaga cabeçalho HTTP para o SQS sozinho.
- Cliente HTTP construído à mão. Instrumentação automática cobre o cliente padrão da
linguagem; um
http.Clientcriado sem o transporte instrumentado some do trace. - Proxy que remove cabeçalho desconhecido. Ingress, WAF e CDN precisam permitir
traceparentetracestate.
Amostragem: o trade-off honesto
Seção intitulada “Amostragem: o trade-off honesto”Guardar 100% dos traces em produção custa mais que guardar as métricas — e a maioria dos traces é de requisições rápidas e bem-sucedidas, que ninguém vai olhar.
- Head sampling: decide no início, pela probabilidade. Simples e barato, mas descarta o erro raro justamente porque ele é raro.
- Tail sampling: decide depois que o trace terminou, no Collector, olhando o resultado. Guarda todo erro e toda cauda lenta, e amostra o resto.
processors: tail_sampling: decision_wait: 10s # espere o trace fechar antes de decidir policies: - name: erros type: status_code status_code: { status_codes: [ERROR] } - name: lentos type: latency latency: { threshold_ms: 1000 } - name: amostra-do-resto type: probabilistic probabilistic: { sampling_percentage: 5 }Tail sampling exige que todos os spans de um trace cheguem ao mesmo Collector — com
várias réplicas, use um balanceamento por trace_id na frente, ou a decisão sai errada.
A decisão de amostragem precisa ser consistente ao longo do trace: se o serviço da ponta
amostra e o do meio não, você recebe pedaços. O flag no traceparent existe exatamente
para propagar essa decisão.
Lendo um trace para achar latência
Seção intitulada “Lendo um trace para achar latência”Na ordem, e sem pular etapas:
- Olhe a duração total e o span de maior duração própria (excluindo os filhos).
- Veja se há espaço vazio entre spans — tempo sem span costuma ser fila, GC, espera por conexão ou pool esgotado, e não trabalho.
- Conte spans repetidos: cinquenta consultas idênticas é o clássico N+1 disfarçado.
- Compare com um trace rápido da mesma rota. A diferença estrutural aparece rápido.
- Só então abra os logs daquela requisição pelo
trace_id.
# Tempo é dado, não impressão: percentil por rota, no Tempo/Jaeger via métricas de spanhistogram_quantile(0.95, sum by (le, span_name) (rate(traces_spanmetrics_latency_bucket[5m])))Grave atributos que você vai querer filtrar (http.route, db.system, messaging.system,
identificador de tenant) e não grave corpo de requisição, token ou dado pessoal em
atributo de span — o trace vai para o mesmo lugar que o log, com os mesmos riscos.
Próximo passo: para instrumentar uma vez e trocar de backend sem reescrever, veja OpenTelemetry.