Kosten mit Sampling kontrollieren: SDK- und Collector-Konfiguration
Entscheide, wann du im SDK sampelst und wann Tail Sampling in einem zweistufigen Collector nutzt.
Jeden Trace zu behalten wird schnell teuer, und die meisten sind Routineanfragen, die nie jemand öffnen wird. Sampling behält eine repräsentative oder interessante Teilmenge. OpenTelemetry bietet zwei Orte für die Entscheidung: im SDK, wenn ein Trace beginnt (Head Sampling), und im Collector, nachdem der Trace abgeschlossen ist (Tail Sampling). Beides ist reine Konfiguration. Diese Anleitung zeigt jedes und wie du wählst.
Head Sampling im SDK
Die Entscheidung fällt, wenn der Root-Span erstellt wird, sie ist also billig und braucht keine zusätzliche Infrastruktur. Sie kann nicht wissen, ob die Anfrage fehlschlagen oder langsam sein wird, weil das noch nicht passiert ist.
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
Die von der Spezifikation definierten Sampler-Namen sind unter anderem always_on, always_off, traceidratio, parentbased_always_on (der Standard), parentbased_always_off und parentbased_traceidratio. Nutze die parentbased_-Varianten: Sie folgen der Sampling-Entscheidung des Parents der eingehenden Anfrage, sodass ein Trace über Services hinweg entweder ganz behalten oder ganz verworfen wird. Ein einfaches traceidratio in jedem Service würde unabhängige Entscheidungen treffen und kaputte Traces erzeugen.
In der deklarativen Konfigurationsdatei sieht dieselbe Idee so aus:
tracer_provider:
sampler:
parent_based:
root:
trace_id_ratio_based:
ratio: 0.1
Prüfe die Dokumentation deines SDKs auf die Sampler-Namen, die es im Dateiformat unterstützt.
Tail Sampling im Collector
Tail Sampling wartet, bis ein Trace vollständig ist, und wendet dann Policies an. Es lässt dich jeden Fehler und jeden langsamen Trace plus einen kleinen Anteil des Rests behalten, was Head Sampling nicht kann. Der Preis ist mehr Infrastruktur und Speicher.
processors:
tail_sampling:
decision_wait: 10s
num_traces: 50000
expected_new_traces_per_sec: 100
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: keep-slow
type: latency
latency:
threshold_ms: 2000
- name: sample-the-rest
type: probabilistic
probabilistic:
sampling_percentage: 5
So funktioniert es:
decision_waitgibt an, wie lange der Prozessor nach dem ersten Span eines Traces wartet, bevor er entscheidet. Es muss länger sein als die Dauer deines langsamsten Traces, sonst entscheidest du anhand unvollständiger Traces.num_tracesgibt an, wie viele Traces beim Warten im Speicher gehalten werden. Dimensioniere es auf etwa deine Trace-Ankunftsrate maldecision_wait, mit Reserve.policieswerden für jeden Trace ausgewertet. Ein Trace wird behalten, wenn irgendeine Policy sagt, ihn zu behalten, die drei oben bedeuten also „behalte Fehler, behalte langsame und behalte zusätzlich 5 Prozent von allem“.- Weitere Policy-Typen sind
string_attribute(einen Wert wie eine Kundenstufe matchen),numeric_attribute,always_sample,and(Policies kombinieren) undcomposite(Rate über Policies zuteilen).
tail_sampling ist in der Contrib-Distribution, und seine Optionsnamen entwickeln sich weiter, prüfe also seine README für deine Version.
Der Haken: ein Trace muss einen Collector erreichen
Tail Sampling funktioniert nur, wenn alle Spans eines Traces bei derselben Collector-Instanz ankommen. Bei mehreren Replikas hinter einem einfachen Load Balancer verstreuen sich die Spans eines Traces, und Entscheidungen werden auf Fragmenten getroffen. Die Standardlösung sind zwei Stufen:
- Stufe 1 (Load-Balancing-Schicht) nutzt den Exporter
loadbalancing, um Spans nach Trace-ID konsistent an eine Instanz der Stufe 2 zu routen. - Stufe 2 (Sampling-Schicht) betreibt
tail_samplingund exportiert die behaltenen Traces.
Stufe 1:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
exporters:
loadbalancing:
routing_key: traceID
protocol:
otlp:
tls:
insecure: true
resolver:
dns:
hostname: otel-sampler-headless.observability.svc.cluster.local
port: 4317
service:
pipelines:
traces:
receivers: [otlp]
exporters: [loadbalancing]
Stufe 2:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
tail_sampling:
decision_wait: 10s
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: sample-the-rest
type: probabilistic
probabilistic:
sampling_percentage: 10
batch:
exporters:
otlp/backend:
endpoint: tempo:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlp/backend]
Nutze einen Headless Service, damit DNS die Pods der Stufe 2 auflistet, und beachte: Wenn Pods hoch- oder herunterskalieren, verschiebt sich das Routing, und einige Traces im Flug können anhand unvollständiger Daten entschieden werden. Die Schichten getrennt zu halten isoliert außerdem Fehler: Ein Problem beim Sampling blockiert die Aufnahme nicht.
Head und Tail kombinieren
Ein gängiges und sinnvolles Setup:
- Ein moderater Head-Sampler im SDK (zum Beispiel 50 Prozent), um das Volumen an der Quelle für sehr verkehrsreiche Services zu senken.
- Tail Sampling im Collector, um zu garantieren, dass Fehler und langsame Anfragen überleben.
Sei vorsichtig: Was am Head verworfen wird, kann der Tail nicht wiederherstellen. Musst du jeden Fehler behalten, sample diese Services am Head nicht aggressiv.
Metriken überleben das Sampling, wenn du sie zuerst ableitest
Traces zu verwerfen verwirft auch die Zahlen, die du daraus berechnen könntest, etwa Anfrageraten und Latenz-Perzentile. Erzeuge Metriken vor dem Sampling-Schritt mit dem Connector spanmetrics, damit Dashboards genau bleiben:
connectors:
spanmetrics:
service:
pipelines:
traces/all:
receivers: [otlp]
exporters: [spanmetrics, loadbalancing]
metrics/derived:
receivers: [spanmetrics]
processors: [batch]
exporters: [otlphttp/prometheus]
Hier speist jeder Span den Connector, während nur die gesampelte Menge zum Speicher weitergeht.
Wählen
| Ziel | Ansatz |
|---|---|
| Kosten einfach senken, keine neue Infrastruktur | Head Sampling mit parentbased_traceidratio |
| Alle Fehler und langsamen Anfragen behalten | Tail Sampling in einem zweistufigen Collector |
| Genaue Raten und Perzentile behalten | spanmetrics vor dem Sampling |
| Wenige Services, geringes Volumen | Kein Sampling; überprüfe es, wenn Kosten zählen |
Prüfen
- Vergleiche in Stufe 2 die Span-Zahlen ein und aus mit den eigenen Metriken des Collectors (Port 8888), um zu bestätigen, dass das Verhältnis zu deinen Policies passt.
- Suche nach einer bekannten fehlgeschlagenen Anfrage und stelle sicher, dass ihr gesamter Trace vorhanden ist.
- Beobachte den Speicher in Stufe 2, weil sie Traces für
decision_waithält. Wächst er, senkenum_tracesoder füge Replikas hinzu und haltememory_limiterdavor.
Beliebte Anleitungen
-
OpenTelemetry erklärt: SDKs, OTLP und der Collector Verstehe die beweglichen Teile und die drei Orte, an denen Konfiguration lebt, bevor du irgendein YAML schreibst. -
Das Konfigurationsmodell des Collectors: Komponenten, Pipelines und Provider Lerne die Regeln des Collector-YAML: Benennung, Pipelines, Connectors, Zusammenführen von Dateien und Umgebungsvariablen. -
SDKs mit OTEL_*-Umgebungsvariablen konfigurieren Setze Service-Namen, Exporter, Sampling und Limits für jede Sprache mit Standardvariablen. -
Apps ohne Code-Änderungen instrumentieren: Java, Python und Node.js Hänge OpenTelemetry-Agents allein mit Umgebungsvariablen und Kommandozeilen-Flags an. -
Den Collector produktionsreif machen: Speicherlimits, Batching und Retries Konfiguriere Prozessoren, Queues und persistenten Speicher, damit Telemetrie Überlast und Ausfälle übersteht. -
Den Collector auf Kubernetes mit Helm deployen Installiere das offizielle Chart, nutze Presets für Logs und Metriken und überschreibe die Konfiguration über Values-Dateien. -
Fehler bei Collector und SDKs beheben Eine systematische Checkliste für den Fall, dass Telemetrie nicht ankommt: Debug-Exporter, Health Checks, interne Metriken und häufige Fehler.