Tracing Distribuído
Spans, traces, OpenTelemetry, Jaeger e como acompanhar requests através de múltiplos serviços.
O que é o tracing distribuído?
Quando um request do usuário atravessa 5, 10 ou 20 serviços antes de devolver uma resposta, como saber onde o tempo foi gasto? Qual serviço falhou? Qual chamada foi a mais lenta?
O tracing distribuído resolve exatamente esse problema: permite acompanhar o percurso completo de um request através de todos os serviços que participam dele, medindo tempos e capturando contexto em cada etapa.
Conceitos fundamentais
Trace
Um trace representa o percurso completo de uma operação end-to-end. Por exemplo, um trace de “criar pedido” inclui tudo, desde quando o frontend envia o request até quando o usuário recebe a confirmação.
Cada trace tem um identificador único (traceId) que se propaga entre todos os serviços envolvidos.
Span
Um span representa uma unidade de trabalho dentro de um trace. Cada serviço ou componente que participa da operação cria um ou mais spans.
Um span contém:
- Nome: Descrição da operação (ex: “POST /api/orders”).
- Timestamps: Início e fim da operação.
- SpanId: Identificador único do span.
- ParentSpanId: Referência ao span pai, formando uma árvore.
- Tags/Attributes: Metadados adicionais (método HTTP, status code, etc.).
- Events/Logs: Eventos pontuais dentro do span.
- Status: OK, ERROR ou UNSET.
Contexto de propagação
Para que o tracing funcione entre serviços, o contexto (traceId, spanId) precisa se propagar em cada chamada. Os mecanismos mais comuns são:
- Headers HTTP:
traceparent,tracestate(padrão W3C Trace Context). - Headers de mensageria: Atributos em mensagens do Kafka, RabbitMQ, SQS.
- gRPC metadata: Propagação automática em chamadas gRPC.
OpenTelemetry
OpenTelemetry (OTel) é o padrão aberto para instrumentação de observabilidade. Unifica a coleta de traces, métricas e logs sob uma única API.
Por que OpenTelemetry?
Antes do OTel, cada vendor tinha sua própria API de instrumentação. Se você usava Jaeger, instrumentava com a API do Jaeger. Se mudava para Zipkin, tinha que reinstrumentar tudo. O OpenTelemetry resolve isso com uma API vendor-neutral.
Componentes do OTel
- API: Interface para instrumentar código. É estável e não muda.
- SDK: Implementação da API com configuração de exportadores, samplers, etc.
- Exporters: Enviam dados para backends específicos (Jaeger, Zipkin, OTLP).
- Collector: Componente intermediário que recebe, processa e exporta telemetria.
- Auto-instrumentação: Bibliotecas que instrumentam frameworks populares automaticamente.
O Collector
O OTel Collector é um componente chave em arquiteturas de produção:
Serviços → OTel Collector → Backend (Jaeger, Tempo, etc.)
Vantagens de usar um Collector:
- Desacoplamento: Os serviços não precisam saber para qual backend enviar dados.
- Processamento: Pode filtrar, samplear e enriquecer traces antes de exportá-los.
- Buffering: Absorve picos de tráfego sem perder dados.
- Multi-backend: Pode enviar dados para múltiplos destinos simultaneamente.
Jaeger
Jaeger é uma plataforma de tracing distribuído open-source, originalmente desenvolvida pela Uber.
Funcionalidades principais
- Visualização de traces: Interface web para explorar traces como diagramas de cascata (waterfall).
- Busca: Encontrar traces por serviço, operação, tags, duração ou traceId.
- Comparação: Comparar dois traces para identificar diferenças de desempenho.
- Grafos de dependência: Visualizar as relações entre serviços com base nos traces coletados.
Arquitetura do Jaeger
Jaeger pode ser implantado de várias formas:
- All-in-one: Um único binário para desenvolvimento e testing.
- Produção: Collector, armazenamento (Elasticsearch, Cassandra) e serviço de Query separados.
- Com OTel Collector: Usando o Collector como receptor no lugar do agente do Jaeger.
Alternativas ao Jaeger
- Zipkin: Outra plataforma open-source de tracing, mais simples.
- Grafana Tempo: Backend de traces otimizado para custo, integrado ao Grafana.
- AWS X-Ray: Serviço gerenciado de tracing na AWS.
- Datadog APM: Plataforma SaaS com tracing integrado.
Correlação entre sinais
O verdadeiro poder do tracing aparece quando ele é correlacionado com logs e métricas:
Trace → Logs
Inclua o traceId em cada linha de log. Quando você encontrar um trace lento ou com erros, pode buscar todos os logs associados filtrando por esse traceId.
Trace → Métricas
Os traces podem gerar métricas automaticamente:
- Latência por serviço e operação.
- Taxa de erros por endpoint.
- Distribuição de duração dos spans.
Isso é conhecido como “métricas derivadas de traces” e ferramentas como o Grafana Tempo suportam isso nativamente.
Exemplars
Os exemplars são links de uma métrica para um trace específico. Quando você vê um pico de latência em um dashboard, pode clicar e ir diretamente ao trace que o causou.
Sampling
Em sistemas de alto tráfego, capturar 100% dos traces pode ser proibitivamente custoso. O sampling permite capturar apenas um subconjunto representativo.
Tipos de sampling
- Head-based sampling: Decide-se no início do trace se ele será capturado ou não. Simples, mas pode perder traces interessantes.
- Tail-based sampling: Decide-se no final do trace, quando o resultado já é conhecido. Permite capturar 100% dos erros e dos traces lentos, mas requer buffering.
- Rate limiting: Capturar um máximo de N traces por segundo.
Estratégia recomendada
- Capture 100% dos erros — sempre.
- Capture 100% dos traces lentos (acima de um limiar).
- Aplique sampling probabilístico ao restante (ex: 10% dos traces bem-sucedidos).
Boas práticas
Instrumentação significativa
Não instrumente cada função — instrumente as operações que cruzam limites:
- Chamadas HTTP/gRPC entre serviços.
- Queries a bancos de dados.
- Publicação/consumo de mensagens.
- Chamadas a serviços externos.
Nomes de spans descritivos
Use nomes que identifiquem a operação, não a implementação:
- Bom:
POST /api/orders,OrderRepository.save - Ruim:
handler,function1,doStuff
Atributos úteis
Adicione atributos que ajudem a diagnosticar problemas:
http.method,http.status_code,http.urldb.system,db.statement(sem dados sensíveis)user.id,order.id(identificadores de negócio)
Propagação consistente
Garanta que o contexto se propague em todos os canais de comunicação — HTTP, mensageria, cron jobs, workers assíncronos. Um trace incompleto é pior do que não ter trace nenhum.
Resumo
O tracing distribuído é o terceiro pilar da observabilidade e possivelmente o mais valioso em arquiteturas de microsserviços. Permite entender o fluxo completo de uma operação, identificar gargalos e diagnosticar erros que envolvem múltiplos serviços. Com o OpenTelemetry como padrão e ferramentas como o Jaeger, implementar tracing é mais acessível do que nunca.