Distributed tracing: entendendo o caminho de uma requisição em microsserviços

1. O Desafio da Rastreabilidade em Arquiteturas Distribuídas

1.1. Por que logs e métricas isolados são insuficientes em microsserviços

Em sistemas monolíticos, rastrear uma requisição é trivial: um único processo, um único log, uma única métrica. Em arquiteturas de microsserviços, uma única requisição pode atravessar dezenas de serviços independentes, cada um com seus próprios logs e métricas. Logs isolados mostram o que aconteceu em cada serviço, mas não revelam a ordem exata dos eventos, o tempo gasto em cada etapa ou onde exatamente uma falha ocorreu. Métricas agregadas, como latência média ou taxa de erro, escondem os outliers — justamente os problemas mais críticos.

1.2. O problema da latência oculta e falhas em cascata

Imagine um serviço de catálogo que chama um serviço de estoque, que por sua vez chama um serviço de preços. Se a requisição total leva 5 segundos, onde está o gargalo? Sem tracing, você precisa adivinhar ou inserir logs manuais em cada ponto. Pior ainda: uma falha no serviço de preços pode causar timeouts em cascata, derrubando todo o sistema. Logs e métricas isolados mostram os sintomas, mas não a causa raiz.

1.3. Conceito fundamental: contexto de propagação entre serviços

A solução é propagar um contexto único (trace) através de todas as chamadas entre serviços. Esse contexto carrega identificadores que permitem reconstruir o caminho completo da requisição, como um fio de Ariadne digital.

2. Fundamentos do Distributed Tracing

2.1. Estrutura de um trace: spans, trace ID e span ID

Um trace representa o percurso completo de uma requisição. Dentro dele, cada operação unitária é um span. Cada span possui:
- trace ID: identifica o trace global
- span ID: identifica o span individual
- parent span ID: identifica o span que o chamou

2.2. Hierarquia de spans: pai-filho e árvore de chamadas

Os spans formam uma árvore hierárquica. O span raiz representa a requisição inicial. Cada chamada subsequente cria um span filho. Essa hierarquia permite visualizar exatamente a sequência e o aninhamento das operações.

Exemplo de hierarquia de spans:
Trace ID: abc123
├── Span A (raiz): "GET /api/pedidos" (duração: 500ms)
│   ├── Span B: "consultar_estoque" (duração: 200ms)
│   │   └── Span C: "consultar_banco_estoque" (duração: 150ms)
│   └── Span D: "calcular_frete" (duração: 250ms)
│       └── Span E: "chamar_api_frete" (duração: 200ms)

2.3. Metadados essenciais: timestamps, duração, tags e baggage

Cada span carrega metadados cruciais:
- timestamp: momento de início
- duração: tempo total da operação
- tags: pares chave-valor para contexto (ex: http.method, error.code)
- baggage: dados propagados entre spans para contexto de negócio (ex: user.id)

3. Propagação de Contexto: O Coração do Tracing

3.1. Propagação via headers HTTP (W3C Trace Context e Zipkin B3)

A propagação mais comum ocorre via headers HTTP. Dois padrões se destacam:

W3C Trace Context (recomendado):

traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: vendor=eyJzZWNyZXQiOiJ0cnVlIn0=

Zipkin B3 (legado):

x-b3-traceid: 0af7651916cd43dd8448eb211c80319c
x-b3-spanid: b7ad6b7169203331
x-b3-parentspanid: 0020000000000001
x-b3-sampled: 1

3.2. Propagação em filas, mensageria e eventos assíncronos

Em sistemas assíncronos, o contexto precisa ser propagado nas mensagens. Exemplo com RabbitMQ:

Headers da mensagem:
traceparent: 00-abc123...-def456...-01
tracestate: ...

3.3. Desafios de propagação em chamadas gRPC e GraphQL

gRPC usa metadados de contexto nativos. GraphQL requer propagação nos resolvers e data loaders. Ambos exigem instrumentação específica para manter o trace completo.

4. Instrumentação: Como Coletar os Dados

4.1. Instrumentação manual vs. automática (bibliotecas e agentes)

  • Automática: agentes como OpenTelemetry auto-instrumentation interceptam chamadas HTTP, banco de dados e mensageria sem alterar código
  • Manual: criação explícita de spans para lógica de negócio personalizada

4.2. OpenTelemetry como padrão unificado de coleta

OpenTelemetry é o padrão da CNCF para coleta de traces, métricas e logs. Oferece SDKs para todas as linguagens principais.

4.3. Exemplo de código: criação manual de spans com OpenTelemetry

# Python com OpenTelemetry
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor

# Configuração
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

# Criação manual de spans
def processar_pedido(pedido_id):
    with tracer.start_as_current_span("processar_pedido") as span:
        span.set_attribute("pedido.id", pedido_id)
        span.set_attribute("pedido.valor", 150.00)

        # Lógica do serviço
        resultado = realizar_pagamento(pedido_id)

        if resultado["status"] == "erro":
            span.set_status(trace.Status(trace.StatusCode.ERROR))
            span.record_exception(resultado["erro"])

        span.add_event("pedido.processado", {"duracao_ms": 45})
        return resultado

5. Armazenamento e Consulta de Traces

5.1. Backends populares: Jaeger, Zipkin, Grafana Tempo, Datadog

Ferramenta Tipo Diferencial
Jaeger Open Source (CNCF) Interface rica, suporte a sampling
Zipkin Open Source Simplicidade, legado
Grafana Tempo Open Source Integração com Grafana, baixo custo
Datadog APM SaaS Correlação nativa com logs e métricas

5.2. Amostragem: head-based vs. tail-based sampling

  • Head-based: decide amostrar no início do trace (ex: 1 em cada 100 requisições)
  • Tail-based: amostra baseada em características do trace completo (ex: traces com erro ou alta latência)

5.3. Consultas por trace ID, tags e filtros temporais

# Exemplo de consulta no Jaeger
find traces with:
  service = "servico-pedidos"
  tags = {"error": "true"}
  duration > 1000ms
  time range = "última 1 hora"

6. Análise Prática: Diagnosticando Problemas com Traces

6.1. Identificando gargalos de latência em cadeias de chamadas

Um trace revela exatamente qual span consumiu mais tempo. Se o span "consultar_banco" leva 800ms de um total de 1s, o gargalo está no banco de dados.

6.2. Detectando erros e exceções distribuídas

Traces com status ERROR e tags como http.status_code=500 indicam falhas. A árvore de spans mostra exatamente onde o erro ocorreu e qual serviço foi impactado.

6.3. Correlação com logs e métricas (observabilidade integrada)

Com OpenTelemetry, logs podem carregar trace_id e span_id, permitindo consultar logs específicos de um trace problemático. Métricas de latência podem ser segmentadas por serviço e operação.

7. Boas Práticas e Armadilhas Comuns

7.1. Cuidados com overhead de instrumentação e volume de dados

Cada span adiciona latência (geralmente < 1ms) e gera dados. Em sistemas com milhões de requisições por segundo, o volume de traces pode ser inviável. Use sampling agressivo (ex: 1%) e evite spans desnecessários.

7.2. Políticas de retenção e custos de armazenamento

Traces ocupam espaço. Defina:
- Retenção de 7 dias para traces completos
- Retenção de 30 dias para traces com erro
- Agregação de métricas para dados históricos

7.3. Integração com service mesh (Istio, Linkerd) para tracing transparente

Service meshes como Istio podem gerar spans automaticamente para todo o tráfego entre serviços, sem instrumentação de código. Isso fornece tracing básico imediato, embora sem contexto de negócio.

8. Do Tracing à Resiliência: Próximos Passos

8.1. Uso de traces para testes de caos e engenharia de resiliência

Traces permitem simular falhas controladas e observar o impacto real no sistema. Ferramentas como Chaos Mesh usam traces para validar que circuit breakers e retries funcionam corretamente.

8.2. SLIs e SLOs baseados em dados de tracing

Com traces, você pode calcular SLIs precisos como:
- Latência p95 por serviço e operação
- Taxa de erro por endpoint
- Disponibilidade baseada em traces completos bem-sucedidos

8.3. Evolução para rastreamento em tempo real e alertas proativos

Ferramentas modernas permitem alertas em tempo real baseados em traces: "Se latência p99 do serviço X ultrapassar 2s por mais de 5 minutos, notificar equipe". Isso transforma tracing de ferramenta de diagnóstico em sistema de prevenção.

Referências