Estratégias de sampling em tracing distribuído para reduzir custo
1. Fundamentos do Tracing Distribuído e o Problema do Custo
O tracing distribuído é a espinha dorsal da observabilidade em arquiteturas de microserviços. Cada requisição de usuário gera uma árvore de spans — unidades de trabalho que representam operações individuais — que, juntas, formam um trace completo. Em sistemas com dezenas ou centenas de serviços, uma única requisição pode produzir centenas de spans.
O problema surge quando cada requisição é rastreada integralmente. Em sistemas de alto throughput (milhares de requisições por segundo), o volume de dados de tracing pode facilmente atingir terabytes por dia. Armazenar, processar e consultar esses dados em ferramentas como Jaeger, Zipkin ou Grafana Tempo gera custos significativos de infraestrutura.
A relação é direta: quanto mais traces, maior o custo. A solução não é desligar o tracing, mas sim implementar estratégias inteligentes de sampling que reduzam o volume sem sacrificar a capacidade de diagnosticar problemas.
2. Conceitos Essenciais de Sampling
Head-based sampling decide no início da requisição se o trace será amostrado. É simples, mas pode perder eventos importantes que ocorrem depois da decisão.
Tail-based sampling adia a decisão até o final da requisição, permitindo analisar o trace completo antes de decidir. É mais preciso, mas consome mais recursos de buffer.
A taxa de amostragem fixa (ex.: 10% de todos os traces) é a abordagem mais simples, mas desperdiça recursos ao capturar traces normais enquanto perde outliers importantes.
A amostragem probabilística usa um hash do trace ID para determinar consistência — o mesmo trace é sempre amostrado ou não, independentemente de qual serviço o processa.
3. Estratégias Head-Based para Redução de Custo
Sampling por probabilidade uniforme
Implementação direta: cada serviço amostra uma fração fixa dos traces.
// Configuração de sampler head-based com taxa de 10%
// Em OpenTelemetry SDK (exemplo conceitual)
Sampler sampler = new TraceIdRatioBasedSampler(0.1);
TracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(sampler)
.build();
Limitação: captura 10% de tudo, incluindo 90% de requisições normais que poderiam ser descartadas.
Sampling baseado em endpoints
Prioriza endpoints críticos (checkout, login) e reduz amostragem de endpoints de baixa relevância (health checks, consultas de catálogo).
// Lógica de sampler head-based por endpoint
function shouldSample(request):
if request.path starts with "/api/pagamento":
return true // 100% para pagamentos
if request.path starts with "/api/consulta":
return random() < 0.01 // 1% para consultas
return random() < 0.05 // 5% padrão
Sampling adaptativo por latência ou erro
Amostragem dinâmica baseada em métricas em tempo real: aumenta a taxa quando a latência ou taxa de erro sobe.
// Sampler adaptativo head-based
function shouldSample(request):
errorRate = getCurrentErrorRate()
if errorRate > 0.05: // 5% de erro
return random() < 0.5 // 50% de amostragem
latencyP99 = getCurrentLatencyP99()
if latencyP99 > 2000: // 2 segundos
return random() < 0.3 // 30% de amostragem
return random() < 0.05 // 5% normal
4. Estratégias Tail-Based para Precisão e Economia
O sampling tail-based usa um buffer para armazenar spans temporariamente e decide após o trace ser concluído quais serão persistidos.
Amostragem por erro e exceção
Garante que 100% dos traces com erro sejam armazenados, mesmo que a taxa geral de amostragem seja baixa.
// Regra tail-based: reter todos os traces com erro
if trace.hasErrors():
storeCompleteTrace()
else:
if random() < 0.05:
storeCompleteTrace()
else:
discardTrace()
Sampling por duração de requisição
Foco em outliers de performance — traces que excedem thresholds de latência.
// Regra tail-based por duração
if trace.duration > 5000ms: // 5 segundos
storeCompleteTrace() // 100% dos lentos
elif trace.duration > 1000ms:
storeWithProbability(0.5) // 50% dos moderados
else:
storeWithProbability(0.01) // 1% dos normais
5. Sampling Híbrido: Combinando Head e Tail
A abordagem mais eficiente combina os dois estágios:
- Head-based reduz volume inicial (ex.: 20% de amostragem uniforme)
- Tail-based refina a seleção, priorizando traces valiosos
// Arquitetura de dois estágios
// Estágio 1: Head-based reduz para 20%
Sampler headSampler = new TraceIdRatioBasedSampler(0.2);
// Estágio 2: Tail-based no coletor
// Regras no OpenTelemetry Collector
tail_sampling:
policies:
- name: error-policy
type: status_code
status_codes: [ERROR]
sampling_percentage: 100
- name: latency-policy
type: latency
threshold_ms: 3000
sampling_percentage: 100
- name: probabilistic-policy
type: probabilistic
sampling_percentage: 5
Exemplo prático: Em um sistema de e-commerce, traces de pagamento são amostrados a 100% no head, enquanto consultas de produtos são amostradas a 1%. No tail, traces com erro ou latência alta são promovidos para armazenamento completo.
6. Implementação Prática com OpenTelemetry
Configuração de sampler customizado no SDK OpenTelemetry:
// Sampler customizado head-based com lógica de negócio
class BusinessSampler implements Sampler {
@Override
public SamplingResult shouldSample(
Context parentContext,
TraceId traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<Link> parentLinks) {
String path = attributes.get("http.target");
// Prioridade para endpoints críticos
if (path != null && path.startsWith("/api/payment")) {
return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
}
// Amostragem reduzida para health checks
if (path != null && path.equals("/health")) {
return SamplingResult.create(SamplingDecision.DROP);
}
// Amostragem probabilística padrão
if (Math.random() < 0.05) {
return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
}
return SamplingResult.create(SamplingDecision.DROP);
}
}
Integração com coletores para sampling distribuído:
// Configuração de pipeline no OpenTelemetry Collector
receivers:
otlp:
protocols:
grpc:
processors:
tail_sampling:
decision_wait: 30s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
- name: error-policy
type: status_code
status_codes: [ERROR]
sampling_percentage: 100
- name: slow-policy
type: latency
threshold_ms: 2000
sampling_percentage: 100
- name: probabilistic-policy
type: probabilistic
sampling_percentage: 5
exporters:
jaeger:
endpoint: jaeger:14250
service:
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling]
exporters: [jaeger]
7. Monitoramento e Ajuste Contínuo da Estratégia
Para avaliar a eficácia do sampling, monitore:
- Taxa de cobertura: porcentagem de serviços cobertos por traces amostrados
- Falsos negativos: incidentes que não foram capturados pelo sampling
- Custo de armazenamento: redução real em GB/dia
Ferramentas como o Jaeger Query permitem analisar a distribuição de traces amostrados versus descartados.
Ciclo de feedback: após um incidente, verifique se o trace foi capturado. Se não, ajuste a taxa de amostragem para o endpoint ou serviço relevante.
8. Casos de Uso e Boas Práticas para Redução de Custo
Cenário 1: Sistema de alta throughput (10.000 req/s)
- Head-based: 2% de amostragem uniforme
- Tail-based: 100% para erros e latência > 1s
- Resultado: redução de 98% no volume, mantendo visibilidade de problemas
Cenário 2: Sistema crítico (pagamentos, 500 req/s)
- Head-based: 100% para endpoints de pagamento
- Tail-based: 100% para erros, 50% para latência > 500ms
- Resultado: custo alto mas justificado pela criticidade
Checklist para ajuste de taxa
| Situação | Ação |
|---|---|
| Muitos falsos negativos em incidentes | Aumentar taxa de amostragem para endpoints afetados |
| Custo de armazenamento muito alto | Reduzir taxa uniforme e aumentar dependência de tail-based |
| Serviço com baixa taxa de erro | Reduzir amostragem head-based para 1-2% |
| Lançamento de nova funcionalidade | Aumentar temporariamente para 50-100% |
Referências
- OpenTelemetry Sampling Documentation — Documentação oficial sobre estratégias de sampling head-based e tail-based no ecossistema OpenTelemetry
- Jaeger Sampling Strategies — Guia completo de configuração de sampling no Jaeger, incluindo sampling remoto e adaptativo
- Honeycomb: The Science of Sampling — Artigo técnico sobre trade-offs entre diferentes estratégias de sampling para tracing distribuído
- Grafana Tempo TraceQL Sampling — Documentação sobre como usar TraceQL para sampling seletivo baseado em consultas no Grafana Tempo
- AWS X-Ray Sampling Rules — Guia prático de configuração de regras de sampling no AWS X-Ray para redução de custos