What Is Tail-Based Sampling in OpenTelemetry?
Learn how to wait until a request has finished before deciding whether its trace is worth keeping, so that the slow, broken, and surprising requests are saved while the boring ones are thrown away, and understand what it costs to do that well.
Picture a shop with fifty security cameras recording around the clock. Storing every hour of footage forever would be absurdly expensive, so the owner has to decide what to keep. One approach is to flip a coin at the start of every hour: heads, keep the hour, tails, delete it. It is cheap and simple, but when a robbery happens, there is a good chance the coin said “delete” for exactly that hour. A smarter approach is to wait until the end of the day, watch for anything unusual, and keep only the clips where something interesting happened.
Those two approaches are the heart of this guide. Deciding at the start, without knowing how things will turn out, is called head-based sampling. Waiting until the story is finished and then deciding is called tail-based sampling. OpenTelemetry supports both, but they behave very differently, and the second one brings real engineering challenges along with its power. We will build the idea from first principles and then learn how to run it safely in production.
1The Problem: Too Much Telemetry
Why anyone needs to throw tracing data away in the first place.
Distributed tracing records the journey of each request through your system as a trace, made of many small timed steps called spans. A single checkout click in a busy online shop might create twenty or thirty spans as it passes through the gateway, the cart, the payment service, the inventory service, and the database. That is useful detail when something goes wrong, because you can see exactly where the time was spent or where the error began.
The trouble is volume. A system that handles five thousand requests per second and produces twenty spans per request creates a hundred thousand spans every second. Each span has a name, timestamps, and a handful of attributes, so storing and indexing them costs real money. Networks must carry them, backends must ingest them, and engineers must search through them. At some scale, keeping every trace becomes either unaffordable or unusable, and sometimes both.
The sensible answer is to keep only a portion of the traces. This is called sampling. The hard part is choosing which portion, because not all traces are equally valuable. Ninety-nine percent of requests are healthy and nearly identical to each other. The one percent that failed or crawled are the ones engineers actually need. Any sampling method that treats them all the same wastes budget on the boring majority and risks losing the precious few.
Think of a newspaper editor with a thousand photographs from a football match and room for ten. Picking ten at random would likely give ten photos of fans queuing for snacks. A good editor looks through them all and picks the goals, the injuries, and the celebrations. Tail-based sampling is that editor, and head-based sampling is the random draw.
2Sampling Basics You Need First
A shared vocabulary for the rest of the guide.
Trace
The complete record of one request as it travels through every service involved, made of many spans joined together by a shared trace identifier.
Span
One timed step inside a trace, such as a database query or an outgoing call. It has a start, an end, a status, and attributes.
Root Span
The very first span of a trace, created where the request enters the system. Every other span descends from it.
Sampler
A piece of logic inside the SDK that decides, when a span is created, whether it should be recorded and exported.
Sampling Decision
The yes or no answer about keeping a trace. Once made, it is usually final, because dropped data cannot be recovered.
Collector
A separate OpenTelemetry service that receives telemetry, processes it, and forwards it to a backend. It is where tail-based sampling normally lives.
The goal of sampling
Sampling is not about collecting less information for its own sake. It is about spending a limited budget of storage, network, and attention on the information most likely to be useful. A good sampling strategy keeps the interesting traces, keeps enough ordinary traces to understand normal behaviour, and keeps the total within budget.
Why whole traces matter
A trace is only helpful when it is complete. If a sampling method keeps the span from the gateway but drops the span from the database, the story has a hole in it. Both head-based and tail-based sampling try to keep or drop whole traces together rather than individual spans, which is why the decision is tied to the trace identifier and not to single spans.
Sampling decides what to keep. It does not decide how spans are created. Your application still does the work of creating spans unless the decision is made early enough to skip it, which only head-based sampling can do.
3A Quick Look at Head-Based Sampling
The simple approach that tail-based sampling improves upon.
In head-based sampling, the decision is made at the very beginning of the trace, when the root span is created. The sampler inside the SDK looks at a few facts available at that moment, such as the trace identifier or the name of the operation, and answers yes or no. If the answer is no, the SDK can avoid recording the spans at all, saving the work of building and exporting them.
OpenTelemetry SDKs ship with several built-in samplers. One keeps everything, one drops everything, and one keeps a fixed percentage of traces based on the trace identifier. A fourth kind follows the parent: if the incoming request already carries a decision from an upstream service, the sampler simply obeys it. This is how the decision, made once at the entry point, spreads to every other service. It travels inside the trace context header as a flag that says whether the trace is sampled.
Advantages
- Very cheap, because decisions need no buffering
- Dropped traces cost almost nothing to produce or send
- Simple to configure with a single percentage
- Stateless, so it scales without coordination
- The decision spreads automatically through trace context
Disadvantages / Trade-offs
- Decides before knowing how the request will end
- Cannot tell an error or slow request from a normal one
- A rare failure has the same small chance of being kept as anything else
- Raising the percentage to catch more failures raises cost for everything
Head-based sampling is like deciding at the door of a restaurant which diners will be allowed to write a review, before they have eaten anything. You cannot know who will have a terrible experience, so you choose by lottery and hope the unhappy ones are among the chosen.
Imagine one percent of requests fail, and you keep five percent of traces at random. You will keep only about five percent of the failures too. Ninety-five percent of the things you most wanted to investigate are gone before anyone looked at them.
4So, What Is Tail-Based Sampling?
Deciding at the end of the story instead of the beginning.
Tail-based sampling means the decision to keep or drop a trace is made after the trace, or most of it, has been observed. The system collects the spans, holds them for a short time, looks at the whole picture, and only then applies rules. Did any span report an error? Was the total duration unusually long? Did the request involve a particular customer or feature? If the answer to an interesting question is yes, the trace is kept. If nothing stands out, it may be dropped or sampled at a low rate.
The word tail refers to the end of the trace, in contrast to the head at its beginning. Because the decision happens at the end, it can use information that did not exist at the start: the final status, the total latency, the number of spans, and the attributes that downstream services added along the way.
| Aspect | Head-based sampling | Tail-based sampling |
|---|---|---|
| When the decision is made | When the trace begins | After the trace has been observed |
| Information available | Trace identifier, root span details | Whole trace including errors, latency, and attributes |
| Where it runs | Inside the application SDK | Usually in an OpenTelemetry Collector |
| Resource cost | Very low | Memory and compute to buffer traces |
| Can keep all errors | No | Yes |
| Saves work in the application | Yes, dropped spans are never built | No, spans are built and sent first |
| Operational complexity | Low | Higher, needs routing and sizing care |
Tail-based sampling is like a film editor who watches the whole rough cut before choosing scenes for the trailer. Head-based sampling is like choosing scenes by rolling a die before the film is shot. The editor pays with time and a large room to store the rough footage, but the trailer is far better.
The price of seeing the ending
Waiting is not free. To decide at the end, something has to hold on to all the spans of every trace until the end arrives. That something needs memory, it needs a rule for how long to wait, and it needs to receive all the spans of a single trace in one place. Each of these needs shapes the architecture that the rest of this guide describes.
5How Tail-Based Sampling Works, Step by Step
From the first span arriving to the final keep or drop.
sequenceDiagram
participant A as Applications
participant C as Collector sampling stage
participant B as Backend
A->>C: Spans of trace 42 arrive from several services
Note over C: Group spans by trace identifier and start a timer
A->>C: More spans of trace 42 arrive
Note over C: Timer ends, apply policies to the whole trace
alt A policy says keep
C->>B: Export every span of trace 42
else No policy says keep
Note over C: Drop trace 42 and remember the decision
end
A->>C: A late span of trace 42 arrives
Note over C: Look up the remembered decision and act on it
Applications send everything
The SDKs record and export all spans, or at least a high share of them, to a Collector. Nothing is dropped yet, because the interesting facts are not known yet.
The Collector groups by trace
As spans arrive from many services, the sampling stage files each one under its trace identifier, building up a picture of each request in memory.
A timer starts
When the first span of a trace arrives, a countdown begins. This waiting period gives the slower services time to deliver their spans.
Policies evaluate the whole trace
When the timer ends, the stage runs a list of rules against everything it has gathered, looking at errors, duration, attributes, and size.
Keep or drop
If the rules say keep, all spans of the trace are sent on to the backend together. If not, they are discarded and their memory is freed.
Remember the decision
The outcome is cached for a while so that any span that arrives late can be handled consistently with its trace.
Nothing about this process needs the applications to know what the sampling rules are. Teams can change the rules centrally in the Collector without redeploying a single service, which is one of the practical reasons organisations like this approach.
6Where It Runs: The Collector Tail Sampling Processor
The component that does the waiting, grouping, and deciding.
Tail-based sampling in OpenTelemetry is implemented by a component of the Collector known as the tail sampling processor. Processors are building blocks that sit in the middle of a Collector pipeline: data comes in from receivers, passes through processors that can modify, filter, or group it, and then leaves through exporters. The tail sampling processor is distributed in the contrib collection of Collector components, so you need a Collector build that includes it.
The processor is configured with a handful of settings that control how long it waits, how much it remembers, and which rules it applies. Understanding these settings in plain language is more important than memorising their exact names, because names and defaults can differ between versions.
Decision wait
How long the processor waits after seeing the first span of a trace before it evaluates the rules. A longer wait catches slow traces more completely but uses more memory and delays the result.
Number of traces
The maximum count of traces held in memory at once. When the limit is reached, the oldest traces may be evaluated early or dropped to make room.
Expected new traces per second
A hint that lets the processor allocate its internal structures sensibly for your traffic level.
Decision caches
Remembered keep and drop outcomes for recently decided traces, which allow late spans to be handled in line with their trace.
Policies
The list of named rules. Each one inspects the trace and votes to keep it or not.
How multiple policies combine
Most setups define several policies, such as one for errors, one for latency, and one that keeps a small random share of everything. In the default behaviour, a trace is kept if any one policy votes to keep it. That means each policy adds to the set of traces you keep, which makes it easy to say “keep all errors, and also all slow traces, and also five percent of the rest”. Newer versions add ways to combine rules more precisely, for example by requiring several conditions together or by explicitly dropping certain traces, so check the documentation for your version.
Policy types, setting names, and defaults have changed across releases of the processor. Always read the documentation for the exact Collector version you deploy, and test your configuration with realistic traffic before trusting it in production.
7The Policies: Deciding What Is Interesting
The vocabulary of rules you can apply to a finished trace.
A policy is a named rule that looks at a complete trace and answers a single question: should this one be kept? The processor supports a family of policy types, each aimed at a common reason for caring about a trace. Choosing the right mixture is the creative part of tail-based sampling.
Status code policy
Keeps traces that contain a span with a chosen status, most commonly an error. This is the most popular rule, because failed requests are almost always worth investigating.
Latency policy
Keeps traces whose total duration exceeds a threshold, for example anything slower than two seconds. This finds the slow requests that users complain about.
Attribute policies
Keep traces where a span attribute matches a value or falls in a range. Examples include a particular customer tier, a specific endpoint, a feature flag, or a numeric value such as an order amount above a limit.
Probabilistic policy
Keeps a fixed percentage of the remaining traces, chosen by trace identifier. This gives you a baseline sample of ordinary behaviour so that you can compare healthy requests against broken ones.
Rate limiting policy
Caps the number of spans kept per second. It protects the backend from surges, for example during an outage that makes every request an error.
Span count policy
Keeps traces with a number of spans within a range. Unusually large traces often reveal loops or runaway fan-out, and unusually tiny ones may reveal early exits.
Composite and combined policies
Combine several conditions, apply different rate budgets to different kinds of traffic, or require two conditions to hold together. Newer versions also allow expressions that look at span details in a flexible way.
| Question you want answered | Policy to reach for |
|---|---|
| Show me everything that failed | Status code policy |
| Show me everything that was slow | Latency policy |
| Show me traffic for one important customer | Attribute policy |
| Show me what normal looks like | Probabilistic policy |
| Protect my backend during a storm | Rate limiting policy |
| Find runaway or strangely small traces | Span count policy |
Do not keep only errors and slow traces. Keeping a small random share of healthy traces gives you a reference for what good looks like, which makes it far easier to spot what is different about the bad ones.
8A Worked Example With Real Numbers
The same ten thousand traces, sampled both ways.
Suppose a shop produces ten thousand traces in a short period. Two percent of them contain an error, one percent are very slow but did not fail, and the other ninety-seven percent are ordinary. The team can afford to keep roughly five hundred traces. Let us see what each approach keeps.
| Kind of trace | Total | Kept by head sampling at 5 percent | Kept by tail policies |
|---|---|---|---|
| Errors | 200 | About 10 | 200, all of them |
| Slow but successful | 100 | About 5 | 100, all of them |
| Ordinary | 9,700 | About 485 | About 485 at 5 percent |
| All traces | 10,000 | About 500 | About 785 |
What the numbers tell us
Head sampling at five percent keeps about five hundred traces, but only around ten of them are errors, and only about five are slow. An engineer investigating an outage sees a tiny, unrepresentative slice of the problem. Tail sampling keeps all two hundred errors and all one hundred slow traces, plus a five percent baseline of the rest, for a total of about 785. The storage bill is only about fifty-seven percent higher than head sampling, but the number of useful traces is more than twenty times greater.
If the team wants to stay at exactly five hundred traces, they can lower the baseline for ordinary traces to about 2.1 percent, which keeps around 200 of the ordinary ones. The total becomes roughly five hundred, yet every error and every slow trace is still present. That is the real prize of tail sampling: you move the budget from the boring majority to the valuable minority.
Imagine a teacher who can only read ten essays in depth out of a hundred. Picking ten at random might miss the brilliant one and the one that cheated. Skimming all hundred first and then reading the standouts in depth takes more effort up front but never misses what matters.
Real traffic rarely splits into such neat percentages, and errors often cluster in time. During an incident, the share of error traces can jump from two percent to ninety percent, which is why rate limiting policies exist to cap the total.
9The Routing Problem: Every Span of a Trace in One Place
The architectural catch that surprises most newcomers.
To judge a whole trace, a single sampling stage must see all of its spans. This sounds obvious, but in a real system the spans of one trace are produced by many different services, running on many different machines, each sending data to whichever Collector instance happens to be convenient. If you run several Collector instances behind an ordinary load balancer, the spans of one trace get scattered across them. Each instance sees only a fragment, and the decisions it makes are based on incomplete information.
A fragment can mislead badly. One instance may hold the slow database span but not the error span, so it sees no reason to keep the trace. Another instance may hold the error span but not the long duration. Different instances may even make different decisions about the same trace, producing a backend that holds half a trace. The fix is to make sure all spans with the same trace identifier are routed to the same sampling instance.
flowchart LR
A["Services and SDKs"] --> B["Tier one Collectors"]
B -->|"Route by trace identifier"| C["Tier two Collector 1 with tail sampling"]
B -->|"Route by trace identifier"| D["Tier two Collector 2 with tail sampling"]
B -->|"Route by trace identifier"| E["Tier two Collector 3 with tail sampling"]
C --> F["Backend"]
D --> F
E --> F
The two-tier pattern
The standard solution is two layers of Collectors. The first layer receives spans from applications and forwards them using a load balancing exporter that picks the destination by trace identifier. Because the same identifier always maps to the same destination, all spans of one trace end up on one second-layer instance. The second layer runs the tail sampling processor and has a complete view of each trace.
Applications send to tier one
Any tier-one instance can accept any span. These instances do little work and are easy to scale up and down.
Tier one picks a destination by trace identifier
The load balancing exporter uses a consistent method so the same trace identifier always goes to the same backend instance.
Tier two buffers and decides
Each tier-two instance owns a share of traces completely, so its policies see whole traces.
Tier two exports the keepers
Only the traces that pass the policies are sent on to the observability backend.
Finding the second tier
The load balancing exporter needs to know which second-tier instances exist. It supports several ways of finding them, such as a fixed list, a lookup through DNS, or discovery through an orchestration platform. When instances are added or removed, the mapping from trace identifiers to instances changes, and a few traces in flight at that moment may be split. This is a normal cost of scaling and a reason to avoid frequent resizing of the sampling tier.
Putting the tail sampling processor on several Collector instances behind a plain round-robin load balancer. It appears to work, because data still flows, but the decisions are made on fragments and the results are quietly wrong.
10Time and Memory: Decision Wait, Buffers, and Late Spans
Waiting for the ending costs memory, and the clock never stops.
Holding traces while they finish is the central resource cost of tail sampling. Every span that arrives must stay in memory until its trace is decided. The amount of memory therefore depends on how many traces arrive per second, how large each one is, and how long the processor waits before deciding.
Take a moderate example. If one trace of twenty spans takes about ten kilobytes, and five thousand new traces arrive every second, then a thirty second decision wait means about a hundred and fifty thousand traces in memory at once, which comes to around one and a half gigabytes. Real memory use is usually higher than the raw size of the data, because in-memory structures carry extra overhead, so plan with a generous margin. All of this must be spread across the tier-two instances, which is why the second tier is sized for memory rather than for processing speed.
Choosing the decision wait
The wait is a trade-off between completeness and cost. If it is too short, slow services may not have delivered their spans yet, and the processor decides on an incomplete trace. A trace whose error span arrives after the verdict may be dropped because the policy never saw the error. If the wait is too long, memory use grows and the backend sees data later. A good starting point is a little longer than the slowest typical request in your system, and teams then adjust using what they observe.
Long traces and late spans
Some traces last far longer than any reasonable wait, for example a background job that continues for minutes after the request that started it. A decision made after thirty seconds will cut such a trace short. The decision caches help here. When a span arrives after its trace has already been decided, the processor looks up the remembered outcome and either forwards or discards the span accordingly. This keeps the tail of a kept trace from being lost, though it cannot rescue a trace that was wrongly dropped before the important span arrived.
Measure your slowest normal request
Use existing tracing data to find the typical upper end of request duration, ignoring truly extreme cases.
Set the wait just above it
Give slow services enough time to deliver their spans without holding everything for longer than needed.
Size memory from the wait
Multiply traces per second by the wait and by the size of a trace, then add a safety margin for overhead.
Protect the process
Use a memory limiting stage ahead of sampling so that a surge slows intake instead of crashing the Collector.
If the buffer fills before the wait expires, the processor may be forced to decide on traces early or drop them. Monitoring for this condition tells you that either the buffer is too small or the traffic has grown beyond the design.
11Combining Head and Tail Sampling
They are not rivals. Used together, each covers the other’s weakness.
Because tail sampling needs the spans to reach a Collector, it works only on the spans that applications actually send. If an SDK uses head-based sampling to drop ninety percent of traces before export, the Collector never sees those traces, and no tail rule can rescue the errors among them. This is the most important rule for combining the two: whatever the head sampler drops is gone for good.
So there are two common patterns. In the first, applications export everything, and the tail stage does all the sampling. This gives the best results but the highest cost in network traffic and Collector resources. In the second, a gentle head-based sample is applied first, for example to keep half of all traces, and tail sampling refines the half that remains. This reduces load at the cost of losing some interesting traces before the tail stage can see them.
| Strategy | What applications do | Main strength | Main weakness |
|---|---|---|---|
| Head only | Sample at a low rate in the SDK | Cheapest to run | Misses most errors |
| Tail only | Export all traces to Collectors | Keeps every interesting trace | Highest network and memory cost |
| Gentle head plus tail | Drop a modest share in the SDK | Balances cost and quality | Some interesting traces lost early |
A subtle trap with parent-based samplers
Many SDKs default to a sampler that obeys the decision made by the parent span. If the root service has a sampler that keeps only a small share, the sampled flag in the trace context tells every other service to drop the rest. This spreads the head decision across the whole system, which is exactly what you do not want when you plan to use tail sampling. For tail sampling to see everything, the entry-point sampler must keep all traces, and the downstream services must follow that decision.
Metrics must be computed before sampling
Request counts, error rates, and latency distributions are among the most valuable things you can derive from spans. If you compute them after sampling, they will be wrong, because you have thrown away most of the normal traffic. The remedy is to compute metrics from the full stream of spans before the sampling stage, using a Collector component that turns spans into metrics, and then sample the traces for storage afterwards.
flowchart LR
A["Receive spans"] --> B["Memory limiter"]
B --> C["Span to metrics step on all spans"]
C --> D["Tail sampling"]
D --> E["Export kept traces"]
C --> F["Export metrics"]
If a dashboard counts traces that survived sampling, the number is a function of your sampling rules and not of real traffic. A change in policy would appear as a change in traffic. Always base rates and totals on metrics computed before sampling.
12Reading Sampled Data Without Fooling Yourself
Sampling changes the meaning of the numbers you see.
A sampled set of traces is not a faithful miniature of reality. Tail sampling in particular deliberately over-represents errors and slow requests. If you open your tracing tool and see that half of the traces have errors, that does not mean half of your requests fail. It means your sampling policy kept all the failures and only a trickle of the successes. Reading the data as though it were a random sample leads to badly wrong conclusions.
Imagine a museum that exhibits only the strangest objects from a huge collection. A visitor who walks through might conclude that the world is full of oddities. The exhibit is accurate about each object, but it says nothing about how common each kind of object is.
Re-weighting with recorded probability
To estimate real totals from a sample, you need to know how likely each trace was to be kept. A trace kept with a probability of one in twenty stands for about twenty traces. OpenTelemetry has been developing conventions for recording the sampling probability alongside the data, so that backends can scale counts back up correctly. With a mix of policies, some traces are kept with certainty and others with a small probability, so recording that information per trace becomes essential for any accurate arithmetic.
Use pre-sampling metrics for totals
Take counts, rates, and percentiles from metrics computed on the full stream of spans.
Use sampled traces for investigation
Treat the traces as detailed case files, not as a statistical sample of traffic.
Keep a labelled random baseline
If you must estimate proportions from traces, use only the baseline portion whose keep probability is known and uniform.
Record how traces were chosen
Where your tools support it, store the sampling probability or the name of the policy that kept each trace, so later analysis can account for it.
Let metrics answer how much and how often. Let traces answer why and where. Sampling is acceptable for traces because metrics preserve the totals.
13Advantages and Trade-offs
An honest balance sheet before you commit.
Advantages
- Keeps all errors, or any outcome you choose to describe
- Keeps slow requests that users actually notice
- Lets you target important customers, endpoints, or features
- Rules can change centrally without redeploying applications
- Moves the storage budget towards valuable traces
- Rate limits protect the backend during incidents
Disadvantages / Trade-offs
- Needs memory to buffer every trace until it is decided
- Requires a two-tier layout with trace-aware routing
- Adds a delay before traces appear in the backend
- Applications still create and send every span
- Misconfigured waits or routing silently damage results
- The sampling tier is stateful and harder to scale and restart
| Resource | Effect of tail sampling |
|---|---|
| Application CPU | Unchanged, because all spans are still created |
| Network between applications and Collectors | Higher, since nothing is dropped early |
| Collector memory | Much higher, proportional to traffic and wait |
| Backend storage and ingest | Lower, since only chosen traces arrive |
| Engineer time | Saved when finding incidents, spent when operating the tier |
When it is worth it
Tail sampling pays off when your volume is high enough that you cannot keep everything, when failures are rare but costly, and when you have the engineering capacity to run a stateful Collector tier. It pays off less when traffic is small enough to store in full, or when your team has no appetite for operating extra infrastructure.
Tail sampling is like hiring a night editor for a newsroom. The editor costs salary and needs a desk, but the newspaper stops missing its biggest stories. For a tiny leaflet with three stories a week, the editor is not worth the money.
14Security and Privacy Considerations
The sampling tier sees everything, so it deserves protection.
Before sampling, the Collector tier receives the full, unfiltered stream of spans, including attributes that may contain sensitive information such as user identifiers, internal addresses, or fragments of queries. This is a bigger concentration of data than a backend that only receives the sampled subset, so the sampling tier becomes an attractive target and should be treated as a sensitive system.
Encrypt in transit
Use encrypted connections between applications, tier one, tier two, and the backend so that spans cannot be read or altered on the network.
Authenticate senders
Accept spans only from known clients so that outsiders cannot flood the tier or inject misleading traces.
Redact before storing
Remove or mask sensitive attributes in a processing stage, ideally before the data leaves the sampling tier for the backend.
Restrict access
Limit who can reach the Collectors, read their configuration, or view their diagnostic output, since memory contents hold live data.
Sampling rules can be manipulated
Policies that depend on span attributes trust whatever the applications report. If a public-facing client can influence an attribute, such as a custom header that is copied onto a span, an attacker could make their traffic look interesting enough to be kept, or make real problems look boring. Sampling rules should rely on facts that your own services set, not on values controlled by outside callers.
Because tail sampling deliberately favours certain traces, the kept data reflects your rules, not reality. Be careful when using sampled traces to make claims about customer behaviour or system health, and document the policies so that later readers understand what the data represents.
15Real-World Uses of Tail-Based Sampling
Where organisations commonly apply it.
The following examples describe patterns that are widely used across the industry. They show how the same machinery of waiting, grouping, and judging can serve very different goals.
E-commerce checkout
An online retailer keeps every trace in which a payment failed, every trace slower than a few seconds, and a small share of the successful ones. When the finance team reports missing orders, engineers can pull the exact failed attempts without having stored a flood of healthy browsing traffic.
Protecting important customers
A software company keeps every trace for its largest enterprise accounts, identified by an opaque account label set by its own gateway. When a key customer reports a problem, there is always a trace to examine.
Rollouts and canaries
During a gradual release, a team keeps all traces that touch the new version, while sampling the old version lightly. This gives complete detail on the thing being tested at a modest cost.
Incident cost control
When an outage turns nearly every request into an error, a rate limiting policy caps the number of kept traces per second. The backend stays healthy and engineers still receive a steady stream of examples.
Hunting rare bugs
A team investigating a once-in-a-million failure keeps every trace that contains a particular error code or that shows an unusual span count, so the rare event is captured the moment it occurs.
Managed alternatives
Some observability vendors and open-source tools offer their own tail-based sampling, sometimes as a managed service, using the same idea of grouping spans and judging whole traces. They remove some of the operational work but still rely on the same principles described here.
In every example, the rule is written in terms of outcomes and business meaning, not in terms of a random number. That shift in thinking is the true change that tail sampling brings.
16Design Patterns and Anti-patterns
What works in practice, and the traps that catch experienced teams.
flowchart TD
A["Can you afford to store every trace?"] -->|Yes| B["Keep everything and skip sampling"]
A -->|No| C{"Do rare failures matter more than averages?"}
C -->|No| D["Head-based sampling is probably enough"]
C -->|Yes| E{"Can you run a stateful Collector tier?"}
E -->|No| F["Use a managed tail sampling service or gentle head sampling"]
E -->|Yes| G["Use two-tier tail sampling with pre-sampling metrics"]
Patterns that work
Two tiers, trace-aware routing
Separate the routing layer from the deciding layer, and route by trace identifier so every sampler sees whole traces.
Errors, slow, baseline
Combine a rule for failures, a rule for slowness, and a small random share of everything else as the standard starting set.
Metrics before sampling
Derive request and error metrics from all spans before any trace is dropped, so dashboards stay truthful.
Guard rails
Add a memory limiter and a rate limiting policy so that surges degrade gracefully instead of crashing the tier.
Review policies regularly
Revisit thresholds as traffic and latency change, because yesterday’s slow is today’s normal.
Anti-patterns to avoid
Problem
The tail sampling processor runs on several Collector instances behind an ordinary load balancer that spreads spans without regard to trace identifier.
Why It’s Harmful
Each instance sees only fragments of traces, so policies based on errors or total duration decide on partial information. Different instances may make different choices for the same trace, leaving incomplete traces in the backend.
Correct Approach
Add a first tier that routes spans by trace identifier, so each trace lands on exactly one deciding instance.
Problem
Applications apply an aggressive head-based sample, such as one percent, and the Collector then applies tail sampling on what is left.
Why It’s Harmful
Ninety-nine percent of errors are discarded before the tail stage can see them, so the carefully written error policy rescues almost nothing.
Correct Approach
Let applications export all traces, or use only a gentle head sample, and put the real selection in the tail stage.
Problem
Dashboards compute request rates and error percentages from the traces that survived sampling.
Why It’s Harmful
The numbers reflect the sampling rules, not real traffic, and change whenever the rules change. An outage can even look like an improvement if the rate limit starts dropping traces.
Correct Approach
Compute metrics from all spans before sampling and use sampled traces only for investigation.
Problem
The decision wait is set to a few seconds because shorter feels faster, while real requests and background work last much longer.
Why It’s Harmful
Decisions are made before the slow or failing spans arrive, so the very traces you wanted are judged as boring and dropped.
Correct Approach
Base the wait on measured request durations and use the decision caches to handle the late spans that remain.
17Monitoring and Operating the Sampling Tier
A system that decides what you can see must itself be watched closely.
The sampling tier fails in quiet ways. A misrouted trace, a buffer that is too small, or a wait that is too short will not crash anything. They simply cause the wrong traces to be kept, and nobody notices until an important investigation finds an empty search result. The Collector exposes metrics about its own behaviour, and the tail sampling processor reports on things like how many traces were kept and dropped, how many are sitting in memory, how long decisions take, and how many traces were evicted too early or arrived late. Make these part of your standard dashboards.
| What to watch | Why it matters | Warning sign |
|---|---|---|
| Traces held in memory | Shows buffer pressure | Steady climb toward the configured limit |
| Traces dropped too early | Shows buffer overflow forcing early decisions | Any sustained non-zero rate |
| Decision latency | Shows how long traces wait before judgment | Rising delay beyond the configured wait |
| Late span age | Shows whether the wait is long enough | Many spans arriving long after decisions |
| Kept versus dropped counts per policy | Shows which rules do the work | One policy keeping nearly everything |
| Collector memory and restarts | Shows stability of a stateful tier | Frequent restarts that lose buffered traces |
Test policies on recorded traffic
Before changing a rule in production, replay realistic data through a test instance and compare what it keeps with what you expected.
Roll out changes gradually
Apply new policies to a single second-tier instance or a small share of traffic first, and watch the keep rate and memory.
Alert on silent damage
Alert on early drops, falling keep rates for errors, and a sudden change in total traces exported, not only on process health.
Plan restarts carefully
Restarting a second-tier instance loses the traces it holds. Drain and restart one at a time, during quiet periods, and accept that a few traces may be split.
Keep a known good test trace
Send a synthetic trace that should always be kept, such as one with a deliberate error, and alert when it fails to appear in the backend.
Write down what each policy keeps and why, who owns it, and when it was last reviewed. Months later, someone will ask why a trace is missing, and the answer should be easy to find.
18Frequently Asked Questions
Short answers to the questions people ask most often.
It normally happens in an OpenTelemetry Collector, using the tail sampling processor from the contrib components. Applications export their spans, and the Collector decides which complete traces to keep.
Not in the usual sense. An SDK only sees the spans of its own service and makes decisions as spans start, so it cannot judge a whole multi-service trace. Whole-trace decisions need a place where all spans meet, which is why Collectors are used.
No. Applications still create and export every span so that the Collector can judge complete traces. Tail sampling reduces what reaches the backend, not what the applications produce.
Because the sampler must see all spans of a trace. The first layer routes spans by trace identifier, and the second layer holds and judges them. Without the routing layer, spans of one trace would scatter across instances.
The processor can remember recent decisions in caches. A late span is checked against the remembered outcome and is kept or dropped to match its trace. A trace dropped before an important span arrived cannot be recovered.
Slightly longer than the slowest normal request in your system, based on measurement rather than guesswork. A longer wait improves completeness but increases memory use and delays traces from reaching the backend.
Yes, but anything the head sampler drops never reaches the tail stage. Use a gentle head sample or none at all if catching rare failures matters, and make sure parent-based samplers do not spread a low keep rate to every service.
They will be wrong if you compute them from sampled traces. Compute metrics from all spans before the sampling stage, and use sampled traces only for investigation.
In the default behaviour, a trace is kept when any policy votes to keep it. Newer versions add ways to require several conditions together or to drop explicitly, so confirm the behaviour in the documentation for your version.
No. It keeps far better traces, but it costs more memory, more network traffic, and more operational effort. For small systems, or where simplicity matters most, head-based sampling or keeping everything can be the wiser choice.
19Summary and Key Takeaways
Tail-based sampling answers a simple but powerful question: why decide what to keep before you know what happened? By collecting the spans of a trace, waiting for the story to finish, and then applying rules to the complete picture, it lets you keep every failure, every slow request, and every trace that matters to your business, while discarding most of the uneventful majority. In the worked example, the same budget that head sampling spent mostly on healthy traffic kept every error instead.
That power has a price. The sampling tier must hold traces in memory, receive every span of a trace in one place, and be watched carefully, because its failures are silent. Applications still create and send every span, and metrics must be computed before sampling or they will lie. Used with a two-tier layout, sensible waits, guard rails, and good monitoring, tail sampling turns an overwhelming flood of telemetry into a focused collection of the traces you actually need. Like the editor choosing photographs after the match, it works because it waits for the ending.
Key Takeaways
- The decision comes at the end — tail-based sampling judges a trace after it has been observed, using errors, latency, attributes, and size that did not exist at the start.
- It runs in the Collector — the tail sampling processor groups spans by trace, waits, applies policies, and keeps or drops whole traces together.
- Policies describe what is interesting — combine rules for errors, slowness, important attributes, and a small random baseline so you keep both the exceptions and the norm.
- Routing is mandatory — every span of a trace must reach the same deciding instance, which usually means a first tier that routes by trace identifier and a second tier that samples.
- Memory and time are the cost — the buffer grows with traffic, trace size, and the decision wait, so size the tier carefully and protect it with a memory limiter and rate limits.
- Do not let head sampling undo it — whatever an SDK drops early never reaches the tail stage, so keep the SDK sample gentle or off and avoid spreading a low keep rate through parent-based samplers.
- Compute metrics before sampling — use full-stream metrics for rates and totals, use sampled traces for investigation, and watch the sampling tier closely because its failures are silent.