03 - Tracing Distribuído (Jaeger, Zipkin, X-Ray)
video: https://youtu.be/... no frontmatter da nota.Tracing Distribuído (Jaeger, Zipkin, X-Ray)
Tracing distribuído é a técnica de observabilidade que reconstrói o caminho completo de uma requisição através de todos os serviços, filas e bancos que ela atravessa, representando esse caminho como um grafo de operações temporizadas. Em arquiteturas distribuídas, um único pedido do usuário pode percorrer dezenas de componentes, e métricas agregadas e logs isolados não conseguem mostrar onde exatamente a latência foi adicionada ou em qual salto o erro nasceu. O tracing resolve isso atribuindo a cada requisição um identificador único, o trace id, e registrando cada trecho de trabalho como um span com início, fim, status e metadados. Ferramentas como Jaeger, Zipkin e AWS X-Ray armazenam, correlacionam e visualizam esses dados, enquanto o OpenTelemetry se consolidou como a camada de instrumentação neutra que alimenta todas elas. Em produção, tracing é a principal ferramenta de diagnóstico de latência em cascata e de mapeamento de dependências reais entre serviços.
O problema que o tracing resolve
Sem tracing, a pergunta "onde está a lentidão" em um fluxo com dez serviços exige abrir dez painéis de métricas e cruzar timestamps manualmente, com precisão de segundos e sob pressão de incidente. Métricas dizem que o p99 subiu, mas não dizem qual chamada dentro do fluxo puxou a curva; logs dizem o que cada serviço viu, mas não revelam a ordem e o aninhamento das operações. O tracing fecha essa lacuna ao preservar a relação causal entre operações, incluindo o tempo gasto na rede e na fila, invisível para qualquer outra ferramenta. Ele também resolve o problema de conhecimento institucional: em vez de depender de alguém que sabe como os serviços se conectam, o service map registra a verdade observada em produção.
Conceito fundamental: trace, span e contexto
Um trace é a árvore completa da execução de uma requisição, e um span é um nó dessa árvore, representando uma operação com nome, duração, status e atributos. Spans carregam referência aos pais, formando a hierarquia que revela sequência e paralelismo, e podem ter eventos internos, como a gravação de um erro, e links para outros traces, como no consumo de uma fila. O contexto é a informação que viaja pela requisição mantendo a ligação entre os spans: trace id, span id e flags de amostragem. Sem propagação de contexto, cada serviço geraria traces soltos e a reconstrução seria impossível. A qualidade do tracing depende menos da ferramenta de visualização e mais da disciplina de instrumentação: toda chamada entre serviços deve criar spans no cliente e no servidor, e todo acesso a banco e a fila deve aparecer como span filho.
Um ponto de disciplina importante é a granularidade dos nomes de span. O OpenTelemetry publica convenções semânticas com nomes padronizados, como GET /pedidos/{id} para spans HTTP e SELECT pagamentos para spans de banco, o que permite que agregações e service maps agrupem spans equivalentes de instâncias diferentes. Nomes com valores dinâmicos embutidos, como GET /pedidos/88213, fragmentam a análise e violam o mesmo princípio de cardinalidade que rege as métricas.
Propagação de contexto com W3C TraceContext
A propagação é feita por headers HTTP ou propriedades de mensagem que viajam junto com a requisição. O padrão W3C TraceContext se consolidou como formato universal, e em 2025 o OpenTelemetry adotou o W3C TraceContext Level 2 como base de amostragem consistente entre SDKs e Collectors, o que garante que a decisão de amostrar ou não um trace seja respeitada de ponta a ponta. O formato usa dois headers principais, como mostra o exemplo.
# Headers W3C TraceContext enviados pelo serviço A ao serviço B
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
# versão-trace id-span id-flags, onde 01 indica trace amostrado
tracestate: vendor=acme-observability
# Estado adicional de fornecedores, preservado ao longo do caminho
O SDK recebe o header, cria um span filho apontando para o span id recebido e repassa um novo header com o span id atualizado. O mesmo contexto precisa ser propagado em mensagens assíncronas, injetando os atributos nos metadados de eventos Kafka, mensagens SQS e payloads de jobs, caso contrário o trace se quebra exatamente no limite que mais interessa investigar. Bibliotecas de instrumentação automática do OpenTelemetry fazem essa injeção para os protocolos mais comuns, mas integrações próprias precisam propagar o contexto manualmente.
OpenTelemetry como instrumentação neutra
O OpenTelemetry unificou métricas, logs e traces em um único conjunto de APIs, SDKs e protocolos, encerrando a era de instrumentação acoplada a um vendor específico. A arquitetura de aplicação usa a API para criar spans, o SDK configura processamento, amostragem e exportação, e o Collector é o processo independente que recebe telemetria de todos os serviços, aplica filtragem, processamento em lote e amostragem tail, e encaminha para o backend escolhido. O acoplamento do código é apenas com a API, então trocar de Jaeger para Datadog ou X-Ray muda configuração, não código. Essa neutralidade é o principal motivo para que novos projetos instrumentem com OpenTelemetry mesmo quando o backend atual é único.
# Configuração resumida do OpenTelemetry Collector
receivers:
otlp:
protocols:
grpc: # Serviços enviam spans via OTLP/gRPC
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 5s # Agrupa spans em lotes para eficiência de rede
tail_sampling:
decision_wait: 30s
policies:
- name: erros-completos # Mantém todos os traces com erro
type: status_code
status_code: {status_codes: [ERROR]}
- name: latencia-alta # Mantém traces acima de 2 segundos
type: latency
latency: {threshold_ms: 2000}
- name: base-probabilistica # E amostra uma fração do restante
type: probabilistic
probabilistic: {sampling_percentage: 10}
exporters:
otlp/jaeger:
endpoint: jaeger-collector.observability:4317
Sampling: head versus tail
Amostragem é o mecanismo que controla quanto dos traces é de fato retido, e existe porque instrumentar tudo com 100 por cento de retenção é proibitivo em custo para sistemas de alto tráfego. As duas estratégias fundamentais funcionam de forma oposta.
| Estratégia | Quando decide | Vantagem | Limitação |
|---|---|---|---|
| Head sampling | No início, antes do trace existir | Previsível, sem custo de buffer | Pode descartar exatamente os traces problemáticos |
| Tail sampling | No fim, com o trace completo | Retém 100 por cento de erros e outliers | Exige Collector com memória e decisão atrasada |
A prática recomendada combina as duas: head sampling probabilístico baixo para tráfego normal, complementado por regras de tail que garantem retenção integral de traces com erro, latência acima de um limiar e traces de rotas críticas. Outra técnica valiosa é a amostragem por prioridade de usuário, mantendo taxa integral para operações de pagamento, por exemplo, e reduzindo para navegação de catálogo.
O item que conecta as duas estratégias é a flag de decisão carregada no traceparent, que indica desde a origem se o trace será amostrado. Com o W3C TraceContext Level 2 adotado pelo OpenTelemetry, essa decisão fica consistente entre SDKs e Collectors, eliminando o comportamento antigo em que o cliente amostarava e o servidor decidia outra coisa, gerando traces parciais sem a parte mais importante.
Jaeger, Zipkin e X-Ray
Os três backends resolvem o mesmo problema com perfis de adoção distintos, e a comparação abaixo resume os pontos de decisão.
| Aspecto | Jaeger | Zipkin | AWS X-Ray |
|---|---|---|---|
| Origem | Uber, CNCF graduado | Twitter, pioneiro do modelo | Nativo AWS |
| Protocolo | OTLP nativo | Zipkin v2 e compatibilidade OTLP | Protocolo próprio via daemon |
| Armazenamento | Cassandra, Elasticsearch, ClickHouse | Memória, Elasticsearch, MySQL | Gerenciado pela AWS |
| Service map | Sim, nativo | Parcial, por extensão | Sim, console integrado |
| Operação | Auto-hospedado ou via produto gerenciado | Auto-hospedado, leve | Zero operação, custo por trace |
| Cenário ideal | Kubernetes com stack Grafana open source | Sistemas legados compatíveis com Zipkin | Arquiteturas fortemente AWS, com Lambda |
Jaeger é o padrão de facto em ambientes cloud native e recebe spans diretamente via OTLP, sem conversores. Zipkin mantém relevância em bases legadas e em bibliotecas que já falam seu formato. X-Ray se destaca pela integração com Lambda, API Gateway e ELB, onde a instrumentação automática dos serviços AWS aparece no mesmo trace da aplicação.
Aplicação prática: investigação de latência
O fluxo típico de investigação começa em um alerta de p99 de latência na métrica de um endpoint. A partir daí, o analista abre o tracing, filtra traces da mesma operação no período de degradação e ordena por duração, revelando rapidamente o formato do problema. O waterfall mostra o span culpado e sua hierarquia, e três padrões aparecem com frequência.
- Latência somada em cascata, quando várias chamadas síncronas sequenciais a serviços externos acumulam dezenas de milissegundos cada, e o caminho de correção é paralelizar chamadas independentes ou introduzir cache.
- Fila de consumidor saturada, visível quando o span de processamento é curto, mas o intervalo entre a publicação e o início do consumo é longo, indicando backlog ou prefetch mal dimensionado.
- Timeout oculto, quando o span do cliente termina por timeout, mas o serviço chamado continuou processando, sinal de que o trabalho deve ser tornado idempotente e o deadline propagado.
Cada um desses padrões leva a uma decisão arquitetural, não apenas a um ajuste de configuração, e é por isso que tracing costuma ser a ferramenta que mais acelera a evolução da arquitetura: ele torna visível o custo real de cada decisão de acoplamento tomada meses antes.
Service map e análise de dependências
Além do waterfal por trace, os backends constroem um service map, um grafo dos serviços com arestas pesadas por volume, taxa de erro e latência. Esse mapa é a melhor representação da arquitetura real, muitas vezes diferente da arquitetura documentada, e é capaz de revelar dependências esquecidas, chamadas circulares e serviços órfãos. Em um incidente, o mapa indica imediatamente qual aresta degradau, reduzindo o tempo de localização de causa. A leitura disciplinada do service map também alimenta decisões de arquitetura: arestas com latência dominante candidatam-se a cache ou troca para comunicação assíncrona, e nós com alta fan-out de chamadas síncronas indicam acoplamento excessivo.
Para que o mapa seja confiável, o nome do serviço precisa vir de um único ponto de configuração por processo, normalmente a variável OTEL_SERVICE_NAME ou o resource do SDK, e não de valores espalhados pelo código. Nomes inconsistentes do mesmo serviço dividem o nó em dois e distorcem o grafo, o mesmo fenômeno de duplicidade que afeta qualquer modelagem de dependências.
Erros comuns
Os problemas abaixo aparecem com frequência em implantações iniciais de tracing.
- Não propagar contexto em chamadas assíncronas, quebrando o trace justamente no trecho entre produtor e consumidor.
- Criar um span por operação trivial, como cada leitura de cache local, inflando custo e ruído sem valor de diagnóstico.
- Amostragem head de 100 por cento sem política de custo, seguida de descarte silencioso no Collector quando o volume explode.
- Atributos sensíveis em spans, como tokens e payloads completos, transformando a ferramenta de diagnóstico em risco de segurança.
- Traces órfãos gerados por jobs agendados que iniciam contexto novo sem ligação com o gatilho original, dificultando a análise.
- Ignorar a amostragem no planejamento de capacidade do Collector, que exige memória proporcional ao número de traces simultâneos em decisão de tail.
Boas práticas
O conjunto abaixo consolida a adoção madura de tracing distribuído.
- Instrumente com OpenTelemetry desde o primeiro dia, usando instrumentação automática para frameworks e spans manuais apenas nos pontos de negócio relevantes.
- Padronize atributos de span com as convenções semânticas do OpenTelemetry, incluindo nome do serviço, versão, ambiente e destinatário da chamada.
- Combine head sampling probabilístico com tail sampling de erros e outliers, com metas de custo revisadas junto ao volume de tráfego.
- Garanta que o trace id apareça em todos os logs do fluxo, criando a ponte de investigação entre logs e traces.
- Revise o service map mensalmente como exercício de arquitetura, tratando arestas inesperadas como dívida técnica documentada.
- Valide a propagação de contexto em testes de integração entre serviços, cobrindo também o caminho assíncrono via filas e tópicos.
- Defina um orçamento de telemetria por serviço, com limite de spans por trace revisado junto à complexidade do fluxo de negócio.