Vai al contenuto
mcpd
nessun daemon
Codice

Campionamento

Tenere una frazione delle tracce, senza tenere una frazione di ognuna.

La decisione si prende una volta sola, alla radice

Una traccia si tiene o si scarta intera. La decisione si prende quando lo span radice parte, ed è ereditata da ogni figlio, così un’esecuzione non è mai registrata a metà.

È la regola da cui discende tutto il resto. Una traccia a cui manca un 30% a caso dei suoi span non si legge come una traccia campionata. Si legge come un difetto del sistema che stai osservando: una chiamata a uno strumento che a quanto pare non è mai avvenuta, una chiamata al modello senza un padre, un buco dove dovrebbe esserci il recupero dei documenti. Ci perderai un pomeriggio prima di capire che il buco è il tuo strumento di osservabilità e non il tuo agente.

Il comportamento predefinito tiene tutto

Senza campionamento configurato la politica è always, ed è il valore predefinito giusto finché non è il volume a dire altro. Il campionamento sacrifica proprio l’esecuzione che vorrai leggere. Pagalo solo quando ti ci costringono lo storage o la banda.

Le cinque strategie

StrategiaComportamento
always Tiene tutto. È il valore predefinito.
never Non tiene niente.
ratio Tiene una frazione fissa, estratta a caso una volta per traccia alla radice.
deterministic Tiene una frazione fissa, decisa calcolando l’hash dell’id di traccia. Ogni collector arriva alla stessa risposta senza parlare con nessun altro.
tail Rimanda la decisione a quando la traccia è finita, poi tiene ogni errore e ogni esecuzione lenta, qualunque cosa dica il tasso.

Impostare un tasso

--sample, oppure MCPD_SAMPLE, prende una frazione da 0.0 a 1.0 e configura la strategia deterministica. Un valore fuori da quell’intervallo ferma il daemon all’avvio con un messaggio che nomina quello che gli hai passato. Non lo riporta dentro l’intervallo, perché un tasso scritto male che in silenzio diventa 1.0 lo scopri un mese dopo dalla fattura dello storage.

tieni una traccia su dieci

mcpd --sample 0.1

Ogni SDK legge la stessa frazione, come sample nella configurazione del client o come MCPD_SAMPLE nell’ambiente. Campionare nel client costa di meno: uno span non campionato non finisce mai in coda e non viene mai esportato, quindi non costa niente in banda. Campionare nel daemon ti costa il viaggio di andata e ritorno, ma tiene il tasso in un posto solo, che è quello che vuoi quando una dozzina di servizi riferiscono a una sola istanza.

Delle cinque strategie, il daemon ne raggiunge due, always e deterministic: --sample e il sample di un progetto nel file di configurazione impostano entrambi la strategia deterministica. never, ratio e tail sono implementate nel tipo della politica e in questa build non si raggiungono né da un’opzione né dal file di configurazione.

Perché l’hash fa accordare una flotta

Fai girare più istanze di mcpd dietro un bilanciatore e gli span di una traccia non arrivano tutti alla stessa. Un campionatore che tira a sorte per istanza, o per span, terrebbe una parte di una traccia e scarterebbe il resto, che è proprio il guasto che la prima regola esiste per impedire.

La strategia deterministica proietta l’id di traccia su tutto l’intervallo a 64 bit e tiene la traccia quando quel valore cade sotto la soglia che il tasso implica. Ogni istanza calcola lo stesso numero dallo stesso id, quindi vanno tutte d’accordo, senza stato condiviso, senza un protocollo di coordinamento e senza niente che possa perdere il passo. Lo stesso id dà anche la stessa risposta a un nuovo tentativo, quindi un batch rigiocato non può arrivare a metà.

Due dettagli rendono onesta quella proiezione. Legge i sedici caratteri esadecimali in coda all’id: OpenTelemetry richiede che gli id di traccia siano casuali, ma qualche produttore ricava i byte di testa da un timestamp, e un campionatore che leggesse quelli si correlerebbe all’ora del giorno invece di campionare. Quando l’id non è affatto esadecimale, ripiega su un hash vero con un mescolamento finale, invece di degenerare nel tenere tutto, così il tasso ottenuto resta vicino a quello che hai chiesto.

Negli SDK, una decisione a monte vince

Quando un SDK continua una traccia a partire da un traceparent W3C in arrivo, eredita il flag di campionamento insieme agli id e non applica il proprio tasso. Vale in tutte e due le direzioni: il flag alzato tiene la traccia qualunque sia il tasso locale, e il flag abbassato la scarta qualunque sia il tasso locale.

Il daemon non lo fa in questa versione. Quando uno span arriva via OTLP, il daemon conserva il suo byte di trace-flags nell’attributo mcpd.otlp.flags, ma il campionatore del daemon non lo legge. Un daemon con un tasso applica quel tasso a ogni traccia, qualunque cosa dica il flag.

L’alternativa è peggiore di come suona. Se ogni servizio applicasse il proprio tasso, una traccia che ne attraversa quattro sarebbe tenuta da due e scartata da due, e quello che ti resterebbe alla fine non è una traccia distribuita. È un insieme di buchi a forma di traccia.

Il campionamento di coda tiene le esecuzioni che vale la pena leggere

Un campionatore di testa decide prima di sapere qualunque cosa. Costa poco, ed è cieco: a un decimo, tiene anche un decimo dei fallimenti. Le tracce che apri davvero sono quelle rare, ed è proprio la rarità che un campionatore a rapporto gestisce peggio.

A strip chart recording model latency over the last hour: a baseline near 420 milliseconds with five occasional excursions towards 800 milliseconds.

Ecco che aspetto ha una registrazione di latenza: una linea di base piatta e una manciata di escursioni. Il volume sta nella linea di base e le risposte stanno nelle escursioni. Il campionamento di coda trattiene la traccia finché non finisce, poi la tiene se qualcosa al suo interno è andato in errore, la tiene se è durata più della soglia che hai impostato, e altrimenti applica lo stesso hash deterministico di prima. Tenere gli errori è attivo per impostazione predefinita, perché le tracce di cui hai più bisogno sono quelle rare, ed è proprio quelle che un campionatore a rapporto ha più probabilità di buttare via.

Il costo è il buffering. Un campionatore di coda deve tenere in memoria ogni traccia non finita finché non può decidere, ed è per questo che è una scelta voluta e non il valore predefinito. Come sopra, la strategia di coda è implementata nel tipo della politica e in questa build non è raggiungibile né da un’opzione né dal file di configurazione; il percorso di ingest del daemon applica la decisione di testa.

Sapere che cosa è stato scartato

Ogni span che il campionatore butta via incrementa mcpd_traces_sampled_out_total, esportato su /metrics in formato testo Prometheus. Metti il tasso per iscritto in un posto che la tua squadra legge. Una traccia mancante con un tasso di campionamento documentato è una spiegazione; una traccia mancante senza spiegazione è un ticket di assistenza, e di solito a scriverlo sei tu.

Avanti