Practical OpenTelemetry — Part 10: Sampling and Deployment Models
Chapter 10 of Practical OpenTelemetry answers the question every adoption eventually hits once instrumentation is actually flowing: how do we afford this at scale? Two threads run through it — trace sampling (three strategies, each with a real, distinct tradeoff) and the four places a Collector can physically live in your infrastructure.
Trace Sampling: Not All Traces Are Equal
Sampling exists because storing every trace is both unaffordable and pointless — Part 1’s “more than 95% of full-volume telemetry would be of no debugging interest” argument, applied concretely. But how you sample determines whether you keep the 5% that matters.
The book distinguishes two families:
- Probability sampling — every element has a pre-established chance of selection. Simple, predictable, statistically honest.
- Non-probability sampling (purposive) — biased toward elements with interesting characteristics: errors, slow requests. Better debugging value, but you can’t know the true probability beforehand, which breaks any downstream statistical math built on top of it.
Head-Based Sampling: Decide at Creation
Head-based sampling decides in-process, at span creation — cheap, and it reduces the tracer’s own overhead. Two API concepts interact here:
- IsRecording — the span is passed to processors, but may still not be exported (useful for span-to-metrics pipelines that need the span object without paying export cost).
- Sampled — the propagated flag. A span can be recording but not sampled.
Built-in samplers: AlwaysOn, AlwaysOff, TraceIdRatioBased (deterministic — the same trace ID always lands on the same side of the ratio), and ParentBased — the default and the recommendation. ParentBased replicates the parent’s decision, falling back to a root sampler only for genuinely new traces; this prevents orphaned spans — child spans sampled while their parent wasn’t, which break the causal tree a reader is trying to follow.
Consistent Probability Sampling: The p and r Values
Plain head-based sampling has a hidden flaw: a parent’s decision distorts a child service’s effective sampling probability, which breaks adjusted counts — the inverse-probability math used to estimate a trace’s true population size from a sample. The fix, in the experimental ConsistentProbabilityBased sampler plus W3C tracestate:
- p-value (0–63) — the negative log2 of the sampling probability (1 = 50%, 2 = 25%, 3 = 12.5%…); powers of two only.
- r-value (0–63) — generated once at the trace root (commonly, leading zeros of the trace ID), propagated unmodified through the whole trace.
- Decision: sample if p ≤ r.
The same r-value travels with the entire trace, so services configured with different p-values still produce consistent, possibly partial traces — service A sampling at 12.5% and service B at 50% means every span B keeps is a strict superset of what A keeps. On the wire: tracestate: ot=r:3;p:2.
Collector-Side Probability Sampling
The probabilistic_sampler processor hashes the trace ID with a configurable hash_seed and sampling_percentage. Two caveats: the hash_seed must be identical across every collector that could see spans from the same trace (the constraint that matters most in gateway deployments), and it honors the legacy OpenTracing sampling.priority attribute for migrations. It’s the simplest scheme — no state held, no routing required — which is exactly when to reach for it.
Tail-Based Sampling: Decide at the End
Here’s the counterintuitive one: probability sampling is random, but errors and slow requests are precisely what you want to keep — and “good” traces are what you want to drop. Tail-based sampling decides after the whole trace completes, based on its actual characteristics.
The costs:
- The
tail_samplingprocessor must hold all spans of in-flight traces in memory for adecision_waitwindow. - All spans of a trace must reach the same Collector instance — solved either by a single non-scaling replica, or two-tier routing by trace ID via the
loadbalancingexporter. - Memory must be bounded: how many traces in flight, max trace duration.
The payoff: telemetry that concentrates on the transactions that actually matter for debugging. The book’s summary line — “sometimes, less is more” — is the whole thesis in four words.
Figure: the three sampling strategies form an adoption ladder — head-based first, consistent when you need math, tail-based when you need discrimination.
Four Deployment Models
Chapter 10 also covers where the Collector — and your SDKs — physically live:
| Model | Shape | Pros | Cons |
|---|---|---|---|
| Collector-less | SDK exports straight to backend | Simplest; nothing extra to run | No central config; app bears aggregation cost; exporter saturation hurts the app |
| Node agent | Collector daemonset per node | Replaces node-exporter/kube-state-metrics-style tooling (hostmetrics, kubeletstats, k8scluster receivers); smaller blast radius | Apps still manage their own export config |
| Sidecar agent | Collector injected per pod | Pipelines decoupled from apps early; enrichment config independent of app lifecycle | Pod recreation to change pipelines; real resource cost at scale (the book’s own figure: ~100m CPU / 128MB per pod); no global view |
| Gateway | Central funnel cluster | Global sampling and policies; central “last hop” shields backend migrations from every team | Operational complexity; needs a TargetAllocator for pull-based scraping at scale |
The gateway is where tail-based sampling actually lives in production — it’s the only topology where “all spans of a trace on one instance” is tractable to guarantee. The book’s tip is worth repeating verbatim: provide a common organizational telemetry endpoint resolving to the best gateway, so teams never configure backend URLs directly and a future migration touches one DNS record instead of every service’s config.
Where This Leaves the Book
Sampling and deployment topology are the last technical decisions in the book. Everything past this point — Part 11 and Part 12 — is about people: how you actually get an organization to adopt any of the last nine posts’ worth of tooling without a mutiny, and how you keep the resulting telemetry worth trusting once it’s live. The book’s own transition is a useful one to sit with: the hardest part of an observability rollout was never the sampler math.
Next: Practical OpenTelemetry — Part 11: Minimizing Adoption Friction — the enablement framework, migration shims, and why automatic instrumentation should always win by default.
References
Have thoughts on this?
I read every email. If something resonated, felt wrong, or made you think — I'd love to hear from you.
Comments