In the ever-evolving landscape of modern observability, distributed tracing stands out as a powerful signal, capable of encapsulating the rich execution context that traditional logs often lack. This enhanced visibility is largely facilitated by OpenTelemetry, a crucial project that enables the collection of trace data across a vast array of frameworks, libraries, and technologies. However, the very power of distributed tracing brings with it a significant challenge: cost. As systems become increasingly distributed, even moderately sized environments can generate an overwhelming volume of trace spans. While the cost of data storage has generally decreased, the ability to efficiently query this massive dataset has not kept pace with its production.
Sampling: A Historical Imperative for Trace Management
To address the challenge of querying vast amounts of trace data, a logical approach is to reduce the size of the dataset itself. Sampling, the practice of selectively retaining only a portion of the generated tracing data, is a concept as old as distributed tracing itself. Its prominence was highlighted in the seminal 2010 Dapper paper, widely recognized as the origin of the modern industry-standard approach that ultimately led to OpenTelemetry. Earlier research, such as the X-Trace paper, also acknowledged the importance of sampling.
The fundamental principle behind sampling is to manage the sheer volume of data. In contexts where systems are "sometimes gratuitously" distributed, the amount of trace data can quickly become unmanageable. While the storage costs have become more palatable, the ability to effectively query and analyze these large datasets remains a bottleneck. Sampling, therefore, becomes an essential tool for making distributed tracing practical.
It is worth noting a common point of confusion outside of specialized observability circles: the term "sampled" in this context means "this span we are keeping," rather than a synonym for filtering in the sense of discarding data based on specific criteria. This distinction is crucial for understanding the mechanics of trace data management.
Head Sampling: Making Decisions at the Trace’s Genesis
Head sampling, a foundational technique in trace management, involves making a sampling decision at the very beginning of a trace’s lifecycle. This approach is conceptually straightforward: when a new trace is initiated, an immediate decision is made about whether to collect it.
The Theory Behind Head Sampling
The decision-making process in head sampling can be informed by various request attributes. However, in practice, it most commonly relies on random selection, often derived from the trace identifier using a deterministic method such as a modulo operation. This technique is frequently referred to as consistent probability sampling or deterministic sampling. The underlying assumption is that, statistically, all traces hold equal potential value. Alternatively, it’s assumed that with a sufficiently high sampling rate and a large enough pool of traces, critical signals like errors and latency spikes will still be adequately represented and visible.
However, this assumption can falter in real-world scenarios, particularly at single-digit sampling rates. Consistent probability sampling can inadvertently miss or underestimate localized issues where a small subset of requests exhibits behavior significantly different from the rest.
Head Sampling in OpenTelemetry: Practical Implementations
OpenTelemetry offers two primary methods for implementing head sampling:
-
Propagating Sampling Decisions via Trace Context: This more flexible approach involves embedding the sampling decision within the trace context. This context, the same mechanism used to link spans together in a trace, is propagated downstream.
In OpenTelemetry, the sampling decision is typically made when a root span is created. The SDK consults a configured sampler, with
TraceIdRatioBasedbeing a common choice for consistent probability sampling. This sampler examines the trace ID and deterministically decides whether the trace should be sampled. Crucially, the same trace ID will consistently yield the same decision, regardless of which service processes it.This decision is encoded as a single bit, the "sampled" flag, within the trace flags, and is transmitted downstream as part of the trace context. A prime example of this is the
traceparentheader defined by the W3C Trace Context specification, which standardizes trace propagation over HTTP. A typicaltraceparentheader might look like:traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01. The final byte (01in this example) indicates the sampling decision:01signifies "sampled," while00means "not sampled."When a downstream service receives a
traceparentheader with the "sampled" flag set, its SDK honors this decision and proceeds to generate spans for the trace. If the flag is not set, no spans are exported. AWS X-Ray employs a similar model, encoding the sampling decision within theX-Amzn-Trace-Idheader. The benefit of this method is that a single decision made at the beginning of the trace is applied consistently across all services without requiring centralized coordination.While more refined approaches exist to enhance the randomness of sampling at the cost of implementation complexity, they are often considered unnecessary for practical deployment.
-
Constant Probabilistic Sampling in the Observability Pipeline: An alternative strategy involves always generating spans via the
AlwaysOnsampler within the SDKs, and then discarding unsampled traces later in a cost-effective and distributed manner.In this model, application SDKs generate all spans. OpenTelemetry Collectors, situated closer to the applications, then filter out spans belonging to traces whose trace identifiers do not meet a predefined deterministic criterion, such as falling outside a configured hash range. The
probabilistic_samplerprocessor within the OpenTelemetry Collector simplifies this process.A typical configuration might specify a
sampling_percentageof 10. Each Collector instance independently hashes the trace ID and retains only the spans associated with traces that fall within the configured percentage. Because the decision is deterministic based on the trace identifier, all Collector instances within the fleet will agree on which traces to keep, eliminating the need for inter-instance communication.This approach offers simplicity and is appealing due to its ease of deployment across large fleets of Collectors, which are often managed centrally by platform teams. It leverages the fact that
AlwaysOnis the default sampler in OpenTelemetry SDKs. While it incurs some resource expenditure by generating spans that may later be discarded, its operational simplicity often outweighs this drawback.
Tail Sampling: Prioritizing Value and Insight
Tail sampling operates on a fundamental truth acknowledged by most observability practitioners: not all traces are created equal in terms of their value. The theoretical underpinning is straightforward: collect all spans belonging to a trace, and then decide whether the trace warrants retention. When executed effectively, tail sampling can yield significant benefits. However, its implementation is notoriously challenging.
Defining Tail Sampling Criteria
Common tail sampling strategies often revolve around prioritizing traces that exhibit errors, with a baseline sample of other traces retained for context. The baseline is critical for understanding normal system behavior under everyday conditions.
Focusing solely on traces with errors is an oversimplification. A more insightful approach considers how "interesting" a trace is. Not all errors are equally significant; recurrent, benign, or recoverable errors can accumulate without providing actionable insights. Conversely, many interesting traces may not contain explicit errors. Operations with high business impact or significant user visibility are valuable to observe, even when they execute successfully.
Perhaps most importantly, and often overlooked, is the inherent interest in the unusual. Rarely executed code paths or operations yielding unexpected outcomes are prime candidates for detailed tracing. For instance, discovering an API that returns an HTTP status code 418 (I’m a teapot) is an unusual event that warrants further investigation.

Tail Sampling in OpenTelemetry: The Implementation Hurdles
Regardless of the specific criteria, achieving effective tail sampling at scale presents several significant challenges. At its core, tail sampling necessitates a time-deferred, centralized decision-making process. This means all spans contributing to a trace must be considered collectively, which runs counter to the inherent design strengths of the OpenTelemetry Collector, optimized for live, stateless, streaming data processing.
Bridging this gap requires more complex architectures within the observability pipeline. A common architecture involves a two-tier system of OpenTelemetry Collectors.
The first tier comprises an agent layer, with a Collector instance deployed per node or as a sidecar, situated close to the applications. By default, OpenTelemetry SDKs use the AlwaysOn sampler, ensuring all spans are created and sent to the nearby agent. Logs and metrics are typically forwarded directly to the backend, as they are not generally subject to tail sampling. Traces, however, are handled differently. Agents utilize a loadbalancingexporter to deterministically hash the trace identifier, routing all spans of a particular trace to the same Collector in the second tier.
The second tier is the sampling layer, a pool of Collector instances running the tailsamplingprocessor. Because the loadbalancingexporter guarantees that all spans for a given trace arrive at the same instance, this Collector can buffer them, evaluate the configured sampling policies (such as error status, latency thresholds, or rate limits), and then either forward the trace to the backend or discard it.
While this architecture functions, it introduces considerable operational complexity. Both tiers must be scaled and monitored independently. Consistent hashing mechanisms must remain stable during scaling events. The use of DNS for managing the list of collectors in the second layer, and the resulting eventual consistency, can make troubleshooting exceedingly difficult.
Deeper challenges also emerge. From the perspective of individual spans, there’s no inherent indication that a trace is complete. Distributed tracing lacks a direct equivalent to a file system’s End-Of-File marker. Once a sampling decision is made, it must be consistently remembered to handle late-arriving spans. Statements like "our traces are fast, they finish in under a minute" often overlook the significance of slow traces, which can be exceptionally interesting. Many environments feature long-running batch jobs performing critical business functions like reconciliation or billing.
The sampling layer is inherently stateful, buffering spans while awaiting sufficient data to render a decision. Ideally, tail sampling would permit decisions to be deferred for a significant duration. This requires durable storage for spans to survive this delay, coupled with efficient deletion mechanisms if the trace is ultimately discarded. Currently, the OpenTelemetry Collector stores pending spans in memory, leading to complex sizing challenges. While community proposals aim to offload this buffering to disk, production-ready solutions remain elusive.
Furthermore, tail sampling, as implemented, can conflict with the design principles of resilient distributed systems. Services are often distributed across availability zones or regions to mitigate correlated failures. Spans, consequently, are scattered across these zones. Tail sampling mandates the convergence of all trace spans in a single location, necessitating cross-zone routing to a specific Collector instance. This undermines architectural principles designed to avoid central choke points, and the observability pipeline can end up concentrating traffic in ways the application architecture was specifically built to prevent.
Finally, networking costs associated with routing spans to the correct Collector can be substantial, particularly due to cross-availability-zone traffic. In many instances, the revealed networking costs of observability pipelines are eye-watering.
The Fundamental Limitation: Sampling and Metrics Accuracy
A crucial, yet often overlooked, limitation of sampling is its inherent incompatibility with computing accurate metrics. Foundational observability metrics, such as RED (Request rate, Error rate, and Duration distributions), power dashboards, Service Level Objectives (SLOs), and alerts. The precision of these metrics is paramount, and this precision is directly at odds with sampling.
Consider calculating RED metrics using consistent probability sampling at 10 percent and then extrapolating the results by multiplying by ten. Request and error counts can be off by as much as 90 percent. Duration histograms are likely to be significantly underestimated, as slower requests have a comparatively higher probability of not being recorded.
Conversely, tail sampling strategies that exclusively retain errors, slow requests, and a small fraction of normal traces introduce bias in the opposite direction. Errors become overrepresented, and duration histograms are heavily skewed towards the "unhappy path." While some observability vendors compensate by annotating "multiplicity" on spans during sampling, this article focuses on the approaches available within OpenTelemetry.
In either scenario, accurate RED metrics cannot be reconstructed by querying only the spans that survive the sampling process. Consequently, any architecture that samples traces must materialize metrics before sampling discards data.
This principle explains why, in the previously described two-tier architecture, the sampling layer incorporates connectors like spanmetricsconnector or the newer signaltometricsconnector before the tailsamplingprocessor. These connectors process every span, generating accurate counts and histograms. Sampling then occurs only after this accurate metric generation.
The metric generation process itself is not trivial. OpenTelemetry metrics incorporate the concept of temporality, which can be cumulative (representing totals since process start) or delta (representing changes since the last reporting interval). These are not interchangeable, and different backends have distinct preferences. Emitting metrics with the incorrect temporality necessitates stateful processors like deltatocumulative or cumulativetodelta, introducing overhead, routing complexity, and statefulness.
The question naturally arises: if generating RED metrics in the pipeline is so complex, why not generate them within the traced applications themselves? Could SDKs emit accurate RED metrics directly? In principle, they can. The OpenTelemetry specification defines semantic conventions for HTTP metrics, gRPC metrics, and others, carefully designed to avoid high-cardinality problems (where metrics with attributes like full URLs or user identifiers lead to an explosion of metric series).
While these semantic conventions are valuable, they do not cover all scenarios. For example, there are no widely adopted conventions for "headless" operations, such as scheduled jobs. In practice, SDK support for these metrics is uneven, especially across various auto-instrumentation libraries. As a result, in many real-world deployments, the observability pipeline, rather than the SDK, becomes the primary source of RED metrics.
Even when SDKs do emit metrics, the problem of cardinality aggregation further down the pipeline persists. With each collector potentially generating metric data points for each service, aggregation is required to manage cardinality. This can lead to a third layer of OpenTelemetry Collectors, increasing cross-availability-zone network traffic and overall complexity.
Conclusion: The Ongoing Evolution of Observability
Observability is an inherently complex domain. The sheer volume of data generated necessitates careful engineering to transform it into actionable and cost-effective insights. Sampling, while a fundamental necessity for observing large distributed systems, introduces a layer of complexity that permeates the entire observability pipeline, particularly concerning the generation and preservation of RED metrics.
Encouragingly, progress continues. Proposals for disk-based buffering for the tailsamplingprocessor aim to alleviate the operational burden of tail sampling. Newer connectors like signaltometricsconnector enhance the practicality of generating accurate metrics even within heavily sampled pipelines.
Ultimately, there is no single panacea. The path forward involves a combination of improved tooling, more intelligent default configurations, and a clear understanding that sampling represents a series of trade-offs, not a problem to be definitively solved and forgotten. Innovative ideas, such as "exemplar-based tail sampling on storage," offer promising avenues. This approach, grounded in the intuition of keeping data "hot" for a short period before sampling, can be implemented relatively easily with OpenTelemetry Collectors by duplicating incoming data to both a short-lived, full-precision stream and a sampled stream.
The quest for the perfect solution continues, with the alchemy of observability continuously refining its processes. This ongoing evolution is particularly relevant as the industry prepares for events like KubeCon + CloudNativeCon Europe, a key gathering for adopters and technologists in the cloud-native ecosystem, where discussions on such critical topics are paramount.
