Sampling Strategies: Ridurre Volume e Costi della Telemetria
In un sistema distribuito con centinaia di microservizi e migliaia di richieste al secondo, raccogliere ogni traccia e proibitivamente costoso. Il sampling (campionamento) e la tecnica che permette di selezionare un sottoinsieme rappresentativo delle tracce, riducendo il volume di dati e i costi di storage senza perdere la capacità di diagnosticare i problemi.
La sfida del sampling e trovare il giusto equilibrio: campionare troppo poco riduce la visibilità; campionare troppo aumenta i costi. Le strategie di sampling avanzate permettono di mantenere tutte le tracce con errori e le richieste lente, scartando solo il traffico "normale" e ripetitivo.
Cosa Imparerai in Questo Articolo
- Head-based sampling: decisione all'inizio della traccia
- Tail-based sampling: decisione alla fine della traccia
- Probabilistic sampling e rate limiting
- Configurazione del sampling nel Collector
- Strategie di ottimizzazione dei costi
- Trade-off tra accuratezza e costi
Head-Based Sampling
Il head-based sampling prende la decisione di campionare una traccia
all'inizio, nel primo span (root span). La decisione viene poi propagata
a tutti i servizi downstream attraverso il flag sampled nel traceparent
header. Se il root span decide di non campionare, nessun servizio nella catena registrera
span per quella traccia.
Il vantaggio principale e la semplicità e l'efficienza: la decisione viene presa immediatamente, senza dover attendere il completamento della traccia. Lo svantaggio e che non conosce il risultato della richiesta al momento della decisione: una traccia con errore potrebbe essere scartata.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.sampling import (
TraceIdRatioBased,
ParentBasedTraceIdRatio,
ALWAYS_ON,
ALWAYS_OFF,
)
# --- 1. Probability Sampler (25% delle tracce) ---
# Ogni traccia ha il 25% di probabilità di essere campionata
sampler_25 = TraceIdRatioBased(0.25)
# --- 2. ParentBased Sampler (rispetta la decisione del parent) ---
# Se il parent e campionato, il figlio e campionato
# Se non c'è parent, usa il ratio specificato
sampler_parent = ParentBasedTraceIdRatio(0.25)
# --- 3. Configurazione nel TracerProvider ---
provider = TracerProvider(
sampler=ParentBasedTraceIdRatio(0.25)
)
trace.set_tracer_provider(provider)
# --- 4. Configurazione via variabili d'ambiente ---
# OTEL_TRACES_SAMPLER=parentbased_traceidratio
# OTEL_TRACES_SAMPLER_ARG=0.25
Tipi di Head-Based Sampler
| Sampler | Comportamento | Uso Tipico |
|---|---|---|
| AlwaysOn | Campiona tutte le tracce (100%) | Sviluppo, testing, servizi a basso traffico |
| AlwaysOff | Non campiona mai (0%) | Disabilitare il tracing temporaneamente |
| TraceIdRatioBased | Campiona una percentuale fissa | Approccio semplice per ridurre il volume |
| ParentBased | Rispetta la decisione del parent | Garantire consistenza across servizi |
Tail-Based Sampling
Il tail-based sampling prende la decisione di campionare una traccia dopo che tutti gli span sono stati raccolti, analizzando la traccia completa. Questo permette decisioni intelligenti basate sul risultato della richiesta: mantenere tutte le tracce con errori, le richieste lente e le tracce anomale, scartando solo il traffico ripetitivo e di successo.
Il tail-based sampling richiede un componente centralizzato (tipicamente il Collector in modalità Gateway) che raccoglie tutti gli span di una traccia, li assembla, valuta le policy di campionamento e decide se conservare o scartare l'intera traccia.
# Configurazione tail_sampling nel Collector Gateway
processors:
tail_sampling:
# Tempo massimo per attendere tutti gli span di una traccia
decision_wait: 30s
# Numero massimo di tracce in attesa di decisione
num_traces: 100000
# Numero atteso di nuove tracce al secondo
expected_new_traces_per_sec: 1000
policies:
# Policy 1: mantenere TUTTE le tracce con errori
- name: errors-policy
type: status_code
status_code:
status_codes:
- ERROR
# Policy 2: mantenere le tracce lente (latenza > 2s)
- name: latency-policy
type: latency
latency:
threshold_ms: 2000
# Policy 3: mantenere tracce con attributi specifici
- name: vip-customers
type: string_attribute
string_attribute:
key: customer.tier
values:
- premium
- enterprise
# Policy 4: campionamento probabilistico per il resto
- name: probabilistic-policy
type: probabilistic
probabilistic:
sampling_percentage: 10
# Policy 5: rate limiting globale
- name: rate-limiting
type: rate_limiting
rate_limiting:
spans_per_second: 500
# Policy 6: policy composita (AND/OR)
- name: composite-policy
type: composite
composite:
max_total_spans_per_second: 1000
policy_order: [errors-policy, latency-policy, probabilistic-policy]
rate_allocation:
- policy: errors-policy
percent: 50
- policy: latency-policy
percent: 30
- policy: probabilistic-policy
percent: 20
Confronto Head vs Tail Sampling
Head-Based vs Tail-Based Sampling
| Aspetto | Head-Based | Tail-Based |
|---|---|---|
| Momento della decisione | Inizio della traccia | Fine della traccia |
| Conoscenza del risultato | No | Si (errori, latenza, attributi) |
| Risorse richieste | Minime (distribuite) | Significative (centralizzate) |
| Consistenza | Sempre consistente | Rischio di span orfani |
| Complessità | Bassa | Alta (infrastruttura dedicata) |
| Errori catturati | Solo se campionati casualmente | Tutti (policy dedicata) |
Strategie di Ottimizzazione dei Costi
I costi di observability crescono con il volume di telemetria. Le strategie di ottimizzazione si concentrano su tre leve: ridurre il volume di tracce, ridurre il numero di attributi per span e ottimizzare la retention nel backend.
# Strategia di ottimizzazione multi-livello
# 1. Filtrare il rumore nel Collector
processors:
filter:
traces:
span:
# Eliminare health check e readiness probe
- 'attributes["http.route"] == "/health"'
- 'attributes["http.route"] == "/ready"'
- 'attributes["http.route"] == "/metrics"'
# Eliminare richieste di asset statici
- 'attributes["http.route"] =~ "/static/.*"'
# 2. Rimuovere attributi ad alta cardinalita
attributes:
actions:
# Rimuovere header sensibili
- key: http.request.header.authorization
action: delete
- key: http.request.header.cookie
action: delete
# Troncare query SQL lunghe
- key: db.query.text
action: hash
# 3. Tail sampling intelligente
tail_sampling:
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: keep-slow
type: latency
latency:
threshold_ms: 1000
- name: sample-rest
type: probabilistic
probabilistic:
sampling_percentage: 5
# 4. Batch per efficienza
batch:
send_batch_size: 2048
timeout: 10s
Checklist per l'Ottimizzazione dei Costi
- Filtrare il rumore: eliminare health check, readiness probe e asset statici prima del backend
- Controllare la cardinalita: limitare le label delle metriche a valori bounded (max 5-7 label per metrica)
- Tail sampling sugli errori: mantenere il 100% delle tracce con errori, campionare il resto
- Retention differenziata: retention lunga per metriche aggregate (90 giorni), breve per tracce raw (7-14 giorni)
- Compressione: abilitare gzip/zstd nel Collector per ridurre il traffico di rete
- Downsampling metriche: ridurre la risoluzione delle metriche storiche (da 15s a 5m dopo 7 giorni)
Adaptive Sampling
L'adaptive sampling adatta automaticamente il tasso di campionamento in base al volume di traffico corrente. Quando il traffico e basso, campiona una percentuale maggiore; quando il traffico e alto, riduce il tasso per mantenere il volume entro i limiti di budget.
Il rate limiting nel tail sampling del Collector e una forma semplice di adaptive sampling:
spans_per_second: 500 garantisce un volume massimo costante indipendentemente
dal traffico. Per strategie più sofisticate, servono soluzioni custom o servizi SaaS che
implementano l'adaptive sampling a livello di piattaforma.
Conclusioni e Prossimi Passi
Il sampling e una competenza fondamentale per gestire l'observability a scala. La scelta della strategia dipende dal volume di traffico, dal budget e dal livello di visibilità richiesto. L'approccio consigliato e combinare head-based sampling per la riduzione base del volume con tail-based sampling per garantire che errori e anomalie vengano sempre catturati.
La regola d'oro e: campionare le tracce, mai le metriche. Le metriche aggregate hanno un costo fisso indipendente dal traffico e forniscono la visibilità di alto livello necessaria per gli alert. Le tracce sono costose ma necessarie solo per il debugging dettagliato.
Nel prossimo articolo esploreremo l'AI observability, analizzando come tracciare le chiamate ai Large Language Model, monitorare l'utilizzo dei token, le latenze delle inferenze e il comportamento degli agenti AI.







