What Is Head-Based Sampling in OpenTelemetry?
A plain-English guide to deciding, at the very start of a request, whether its trace will be kept or thrown away, how OpenTelemetry samplers make that choice, how the decision travels between services, and the mistakes that quietly leave you with broken or blind traces.
Imagine you run a very popular museum. Ten thousand visitors walk through the front door every minute. You would love to know what each of them thinks of the exhibits, but you cannot possibly interview all ten thousand. You do not have enough staff, and the visitors would never get to see any art. So you make a plan: at the entrance, a staff member hands a feedback card to every tenth visitor, and only those visitors are asked for opinions. The decision is made at the door, before the visitor has seen a single painting.
That is the heart of head-based sampling. In a large software system, recording a detailed trace of every single request would be slow, expensive and mostly wasteful. So the system makes a quick decision at the very start of each request, “keep this one” or “skip this one,” and then everyone involved in that request follows the decision. In this guide you will learn what head-based sampling is, how OpenTelemetry does it, how the choice travels from service to service, how to pick a sensible rate, and which beginner mistakes to avoid. No prior knowledge is assumed. Every new term is explained the first time it appears.
Read the first five chapters in order, because they build the vocabulary. After that, you can jump to the chapter on choosing a rate, the mistakes, or the FAQ when you need a specific answer.
1The Big Picture: Why We Cannot Keep Everything
Before we learn how to throw data away, we need to understand why anyone would want to. It all starts with the sheer size of modern traffic.
When a program runs on your laptop, you can look at every detail of what it does. When a program runs in production, serving real customers on many machines, every detail it produces has to be collected, sent over a network, stored on disks and searched later. That costs money, time and computing power.
The most detailed kind of information a system can produce is a trace, which is a record of the full journey of one request through all the services it touched. Traces are wonderful for finding out why something was slow or broken. But they are also big. One request can produce dozens of small records, called spans, and a busy system handles thousands of requests every second.
A quick calculation
Let us do the arithmetic for a modest shopping website. Suppose it handles 10,000 requests per second, and each request produces about 20 spans as it travels through the services. That is 200,000 spans every second. Multiply by the 86,400 seconds in a day and you get about 17.3 billion spans per day. If each span takes even one kilobyte to send and store, that is more than 17 terabytes of data every day, just for traces.
Now imagine that you keep only one request out of every hundred. The numbers fall to about 2,000 spans per second and about 173 million spans per day, which is a much more comfortable amount. You still see a statistically fair picture of what your system is doing, but the cost has dropped by a factor of one hundred.
Think of an opinion poll before an election. Pollsters do not ask every voter. They ask a carefully chosen small group, and the results are close enough to the truth to be very useful. Sampling traces is the same idea: you look at a small, fair share of the traffic and learn about all of it.
The catch
Sampling saves a great deal of money, but it comes with a price: some requests will not be recorded at all. The big question of sampling is therefore not only “how much should we keep?” but also “when do we decide which ones to keep?” That second question is exactly what separates head-based sampling from its cousin, tail-based sampling, and it is the subject of this guide.
2Traces, Spans and Context: The Basic Vocabulary
A few small words appear everywhere in tracing. Learn them once and the rest of this guide becomes easy.
Span
A span is a record of one piece of work, such as “the payment service charged the card” or “the database answered a query.” A span has a name, a start time, an end time and some extra details called attributes. Think of a span as one line in a diary that says what happened and how long it took.
Trace
A trace is a collection of spans that all belong to the same request. If a customer’s checkout touched six services, the trace holds the spans from all six, arranged like a family tree. The first span is the parent, the work it triggered creates child spans, and those can have children of their own.
Root span
The very first span of a trace, the one that has no parent, is called the root span. It is usually created by the first service that receives the request from the outside world, such as a gateway or a web server. The root span is the “head” of the trace. That is where the name “head-based” comes from.
Trace ID and span ID
Every trace has a unique trace ID, a long random number that is shared by all its spans. Every span has its own span ID. When one service calls another, it passes along the trace ID and the ID of the calling span, so that the second service can create a child span in the same trace.
Context and propagation
Context is a small bag of information that travels with a piece of work. In OpenTelemetry, it holds the identity of the current span. When the work crosses the network from one service to another, the context is written into the message headers by the sender and read out again by the receiver. This is called context propagation. It is the reason the second service can continue the same trace instead of starting a new one.
Span
One timed piece of work with its own ID, such as a database call.
Trace
All the spans of one request, tied together by a shared trace ID.
Root span
The first span of a trace, created where the request first enters your system.
Context
The information that travels with the request, including the trace ID, the current span ID and the sampling flag.
Imagine a relay race. The trace is the whole race, each runner’s lap is a span, and the first runner’s lap is the root span. The baton is the context. It is passed from runner to runner, and written on the baton is a small note that every runner can read. As you will soon see, that note will carry the sampling decision.
3What Is Sampling?
Now we can define sampling clearly and see the two big families of it.
Sampling is the practice of choosing which traces to keep and which to throw away, so that the cost of telemetry stays under control while the picture of the system stays useful. Sampling is not a failure or a shortcut. It is a standard, planned part of running tracing at scale.
Every sampling approach has to answer two questions. The first is who decides: which piece of software makes the call. The second is when the call is made: before the request starts, or after it is finished. The answer to the second question divides sampling into two families.
Head-based sampling
The decision is made when the trace begins, at the root span, before anything is known about how the request will turn out. This guide is about this family.
Tail-based sampling
The decision is made after the trace is complete, when it is known whether the request was slow, failed or normal. It needs more machinery and memory.
Back to the museum. Head-based sampling is handing a feedback card to every tenth visitor at the entrance. You do not know who will love the museum and who will hate it. Tail-based sampling is waiting at the exit and interviewing only the visitors who looked upset or who stayed unusually long. The second approach finds more interesting stories, but it needs a staff member standing at the exit, and it needs to remember who came in.
Where else the word “sampling” appears
The word is used for other things in monitoring too. Metrics can be sampled by collecting a reading every few seconds instead of continuously. Logs can be sampled by keeping only some of the repeated messages. In this guide, sampling always means trace sampling, the decision to keep or drop whole traces.
Good trace sampling keeps or drops a trace as a whole. If you keep random individual spans, you end up with trees that have missing branches and missing roots, which are very hard to read. Head-based sampling gets this right in a clever way: because the decision is made once at the root and then passed along, every service in the same trace follows the same decision.
4What Is Head-Based Sampling?
Here is the clear definition, followed by a walk through the idea piece by piece.
Head-based sampling is a sampling strategy in which the decision to keep or drop a trace is made at the very beginning of the trace, when the root span is created, using only the information that is available at that moment. Once the decision is made, it is recorded in the context and passed to every downstream service, which simply follows it.
In OpenTelemetry, the decision is made by a small component called a sampler. When your code or your framework starts a new span, the SDK asks the sampler: “Should this span be recorded and exported?” The sampler looks at what it knows and answers right away. The answer takes microseconds, and it costs almost nothing.
graph LR
A["Request arrives at the first service"] --> B["SDK creates the root span"]
B --> C{"Sampler: keep this trace?"}
C -->|"Yes"| D["Span is recorded and exported, sampled flag set to on"]
C -->|"No"| E["Span is not exported, sampled flag set to off"]
D --> F["Context with the flag travels to the next service"]
E --> F
F --> G["Next service follows the same decision"]
Follow the figure from left to right. A request arrives. The SDK creates the root span. The sampler is asked for a decision. If the answer is yes, the span is recorded and later exported, and a small flag in the context is turned on. If the answer is no, the span is not exported, and the flag is turned off. Either way, the context travels along with the request, so the next service can read the flag and make the same choice.
The two key properties
- The decision is early. It happens before the request has been processed, so the sampler cannot know whether the request will be slow, will fail or will be boring. It must decide using only what is visible at the start, such as the trace ID, the name of the operation, and some attributes.
- The decision is shared. Because the flag travels with the context, all services in the same trace follow the root’s decision. A trace is therefore either kept whole or dropped whole, not kept in pieces.
Think of a coin tossed at the entrance of a theme park. The ticket-taker flips a weighted coin for every family. If it lands on heads, the family is given a bright wristband. Every ride operator inside the park only needs to look at the wristband: if it is bright, they record the family’s visit, and if not, they do not. No ride operator flips their own coin. They all trust the decision made at the gate.
Why it is called “head”
A trace has a head, which is its beginning, and a tail, which is its end. A head-based sampler decides at the head. A tail-based sampler waits for the tail. The names are simple pictures of when the decision is made.
If you do nothing special, OpenTelemetry SDKs usually apply a head-based approach that follows the parent’s decision and, for brand new traces, keeps everything. This is fine for development and small systems, but it is usually too expensive for large production systems, which is why teams configure a rate.
5Anatomy of a Sampling Decision
Inside the SDK, a sampler is a tiny function with clear inputs and a clear output. Understanding them makes every later chapter easier.
What the sampler is told
When a new span is about to start, the SDK gives the sampler a small set of facts. These are the only things a head-based sampler can use, because nothing else exists yet.
- The parent context. Is there a parent span? If so, was the parent sampled? Was the parent in the same program or in a remote service?
- The trace ID. The long random number that identifies the trace. Many samplers use it to decide fairly and consistently.
- The span name. For example, “GET /checkout.”
- The span kind. Is the span a server handling a request, a client making a call, a producer sending a message, or an internal step?
- The starting attributes. Details known at the start, such as the HTTP method or the target address.
- Links. References to other spans that are related but are not the parent.
What the sampler answers
The sampler returns one of three decisions. The names come from the OpenTelemetry specification.
| Decision | Is the span recorded? | Is it exported? | Sampled flag in the context |
|---|---|---|---|
| Drop | No, the span is a hollow placeholder | No | Off |
| Record only | Yes, its data is collected | No | Off |
| Record and sample | Yes | Yes | On |
The sampler can also add extra attributes to the span and can change a note called tracestate, though most samplers do not need to.
“Recorded” and “sampled” are not the same
Beginners often mix up these two words. A span is recording when the SDK collects its data, such as attributes and events, in memory. A span is sampled when it will actually be sent to the backend. A “record only” span is useful for internal processing, such as a span processor that wants to see every span’s data even though it will not be exported. In most simple setups, you will only meet the other two decisions: drop, where nothing is collected, and record and sample, where everything is collected and exported.
Think of a school photographer. “Recording” means the photographer’s camera is switched on and the photographs are taken. “Sampled” means the photo is printed and put into the yearbook. A drop decision is like leaving the camera off. A record-only decision is like taking the photo but keeping it in the camera’s memory card. Record and sample means the photo goes into the book.
A dropped span still travels
One important detail: even when a span is dropped, the context still travels to the next service, with the sampled flag turned off. A dropped span is a lightweight placeholder that costs almost nothing, but it keeps the trace ID and span ID flowing. This is what allows downstream services to see the “off” flag and also skip the trace, and it also allows logs from different services to share the same trace ID even when the trace itself is not stored.
6The Built-In Samplers
OpenTelemetry ships with a small set of ready-made samplers. Most real systems use just a combination of these.
AlwaysOn
Every span is recorded and sampled. It is perfect for development, testing and very small systems. It is usually too costly for large production traffic.
AlwaysOff
Every span is dropped. It is useful to switch tracing off without removing the code, or in places where you want no traces at all.
TraceIdRatioBased
Keeps a fixed fraction of traces, such as one in a hundred, deciding by looking at the trace ID. The same trace ID always gets the same answer.
ParentBased
A wrapper sampler. If the request already has a parent, it follows the parent’s decision. If there is no parent, it asks another sampler, called the root sampler.
How they are used together
The first three are simple. The fourth, ParentBased, is the glue that makes head-based sampling work across many services. A typical production setup is “ParentBased wrapped around TraceIdRatioBased.” In words: when a request arrives with a decision already attached, obey it. When a request arrives with no decision, which means this service is the root, make a fresh random decision with a fixed ratio.
| Combination | Meaning in plain words | Typical use |
|---|---|---|
| AlwaysOn | Keep every trace | Development, tests, tiny systems |
| AlwaysOff | Keep no trace | Switching tracing off |
| TraceIdRatioBased with 10 percent | Every service makes its own 10 percent choice, by trace ID | Simple systems where every service uses the same ratio |
| ParentBased with AlwaysOn as root | Follow the parent. New traces are always kept | The usual default in SDKs |
| ParentBased with TraceIdRatioBased as root | Follow the parent. New traces are kept at a fixed ratio | The most common production setup |
| ParentBased with AlwaysOff as root | Follow the parent. New traces are never started | A service that should only join traces started by others |
Think of a building with a front desk and many inner offices. The front desk has a rule for new visitors: “give a badge to one visitor in ten.” The inner offices have a different rule: “if the visitor has a badge, record the meeting. If the visitor has no badge, do not record.” The front desk is the root sampler. The inner offices are ParentBased samplers following the parent’s badge.
The specification also lets you write your own sampler, for example one that samples by route or by customer tier. That is covered in Chapter 12.
7How TraceIdRatioBased Works Inside
The ratio sampler is the workhorse of head-based sampling. Its secret is that it uses the trace ID itself as a source of fairness.
A trace ID is a long random number. If the IDs are truly random, then the digits of the number are spread evenly. That means you can look at a piece of the number and use it like a lottery ticket. The ratio sampler takes a portion of the trace ID, treats it as a number between zero and one, and compares it with the configured ratio. If the number is smaller than the ratio, the trace is kept. If not, it is dropped.
Choose the ratio
You configure a ratio between zero and one. A ratio of 0.01 means “keep one trace in a hundred.” A ratio of 1 means “keep everything.” A ratio of 0 means “keep nothing.”
Look at the trace ID
When a span starts, the sampler reads the trace ID and converts a portion of it into a fraction between zero and one. Because trace IDs are random, these fractions are spread evenly across the range.
Compare
If the fraction is below the ratio, the answer is “record and sample.” Otherwise, the answer is “drop.” In the long run, about the configured share of traces will be kept.
Same input, same answer
The comparison depends only on the trace ID and the ratio. So if two different services use the same ratio and look at the same trace ID, they reach the same answer without talking to each other. This property is called being deterministic.
Why determinism is helpful
Because the answer depends only on the trace ID, the ratio sampler does not need to remember anything. It keeps no counters and no memory of earlier traces. It does not need to contact any other component. It is a stateless, instant calculation, which is why it is so cheap to run on every request.
Determinism also helps in situations where the sampled flag is lost. Imagine a message that passes through a system that drops headers. The next service starts a brand new context for the same trace ID. If every service uses the same ratio, they still make consistent decisions for the same ID. That is a safety net, although you should not rely on it. The proper way to keep decisions consistent is to follow the parent, as described in the next chapter.
Imagine that every visitor receives a ticket with a long random number, and the rule is “keep the visitor if the last three digits are between 000 and 099.” Any guard who reads the ticket reaches the same conclusion, without a phone call to anyone. Different guards can never disagree because they use the same ticket and the same rule.
The ratio sampler assumes trace IDs are random. If a system generates IDs in a pattern, for example by counting upward, the portion of the ID used for sampling will not be evenly spread, and the kept share can be far from the ratio you chose. Use the default ID generators that come with the SDK unless you have a special reason to change them.
A note on newer approaches
The OpenTelemetry community has been refining how probability sampling works so that services using different ratios can still make consistent choices, and so that the actual sampling rate can be recorded in the trace and used later to correct counts. This work is often called consistent probability sampling. It builds on the same idea of using the trace ID, adds a small note in the trace state, and is rolling out gradually. Check the documentation for your SDK version to see what is currently supported.
8ParentBased in Detail
ParentBased is the single most important piece of head-based sampling across services. It turns a local decision into a system-wide decision.
A request can enter a service in several different situations. The service might be the very first one, with no parent at all. Or it might receive the request from a remote service whose decision was “sampled.” Or from a remote service whose decision was “not sampled.” The same three situations can occur for a parent that lives inside the same program. ParentBased lets you set a separate behavior for each situation.
| Situation | Question the sampler asks | Default behavior |
|---|---|---|
| No parent | Is this the root of the trace? | Ask the root sampler, which you configure, such as a ratio sampler |
| Remote parent, sampled | Did a service in another process say yes? | Say yes (AlwaysOn) |
| Remote parent, not sampled | Did a service in another process say no? | Say no (AlwaysOff) |
| Local parent, sampled | Did a span in this same program say yes? | Say yes (AlwaysOn) |
| Local parent, not sampled | Did a span in this same program say no? | Say no (AlwaysOff) |
Almost nobody changes the four “follow the parent” defaults. The only slot you normally set is the root sampler. That is the one that decides what happens when a new trace begins in this service.
Why following the parent is so valuable
Imagine that three services each used a plain 10 percent ratio sampler with no ParentBased wrapper, and imagine that their trace ID checks happened to differ, for example because they used slightly different ratios. Service A might keep a trace, while service B drops it, and service C keeps it again. The result is a trace with a hole in the middle. The spans of A and C exist, but they cannot be connected, because B’s span is missing.
With ParentBased, the problem disappears. Service A, the root, decides. B and C simply obey. The trace is either whole or entirely absent.
Think of a group of friends deciding whether to take photos on a trip. If every friend decides alone, you end up with photos of some places and none of the places before and after, so the story cannot be followed. If the trip leader decides “today we take photos” and everyone follows, the album is complete for that day. ParentBased makes everyone follow the trip leader.
When every service uses ParentBased, the ratio configured in non-root services is never used for requests that come from other services. It matters only for requests that start in that service. This surprises many beginners who change a ratio in a deep backend service and see no effect on their traces.
The remote and the local parent
Why does the specification distinguish between “remote” and “local” parents? Because sometimes you might want different behavior at the boundary of your system. For example, you might want to ignore the sampling decision of an untrusted outside caller (a remote parent) but obey the decisions made by spans inside your own program (local parents). Having separate slots allows exactly this kind of rule.
9Data Flow and Lifecycle Across Services
Let us follow one sampled request and one unsampled request through a small system, and watch how the decision travels.
sequenceDiagram
participant U as User
participant A as Gateway root, ratio 10 percent
participant B as Orders Service, ParentBased
participant C as Payments Service, ParentBased
U->>A: Request one
Note over A: Trace ID random check says keep, flag on
A->>B: Call with context, sampled flag on
B->>C: Call with context, sampled flag on
Note over A,C: All three spans recorded and exported
U->>A: Request two
Note over A: Trace ID random check says drop, flag off
A->>B: Call with context, sampled flag off
B->>C: Call with context, sampled flag off
Note over A,C: No spans exported, IDs still travel
In the first request, the gateway is the root. Its sampler looks at the new trace ID and decides to keep the trace, so the flag is on. The gateway calls the orders service and passes the context. The orders service has a ParentBased sampler. It sees a remote parent with the sampled flag on, so it also samples. It calls the payments service, which does the same. The result is a complete trace of three spans.
In the second request, the gateway’s random check says drop, so the flag is off. Both downstream services see the “off” flag and also drop. Nothing is exported, but the trace ID and span IDs still travel along, which is helpful for logs.
The lifecycle of one span
- Start. The SDK asks the sampler for a decision. If the decision is drop, it creates a lightweight non-recording span that does almost nothing.
- During the work. Recording spans collect attributes and events in memory. Non-recording spans ignore all such calls, which is why dropping saves processing time.
- End. A recording and sampled span is handed to the span processor, which batches it and gives it to the exporter. A dropped span simply disappears.
- Export. The exporter sends the batch to a collector or backend, which stores it and makes it searchable.
Notice where the savings come from. A dropped span avoids the work of storing attributes, building batches, serializing data, sending it across the network and storing it in the backend. That is a major reason head-based sampling is cheap: the savings begin immediately, at the source.
The same flow applies when work passes through message queues. The producer writes the context, including the sampled flag, into the message. When a consumer later reads the message, a ParentBased sampler follows the flag, even if the consumer runs hours later on a different machine.
10Configuring Head-Based Sampling
The good news is that you rarely need to write code. OpenTelemetry defines two standard settings that control sampling in almost every SDK and agent.
The first setting is OTEL_TRACES_SAMPLER. It chooses the sampler by name. The second is OTEL_TRACES_SAMPLER_ARG. It carries the argument that some samplers need, such as the ratio.
| Sampler name | Meaning | Uses the argument? |
|---|---|---|
| always_on | Keep every trace | No |
| always_off | Keep no trace | No |
| traceidratio | Keep a fixed share by trace ID | Yes, the ratio between 0 and 1 |
| parentbased_always_on | Follow the parent, new traces always kept. This is the usual default | No |
| parentbased_always_off | Follow the parent, new traces never kept | No |
| parentbased_traceidratio | Follow the parent, new traces kept at a fixed share | Yes, the ratio between 0 and 1 |
For most production systems, the best choice is parentbased_traceidratio with an argument such as 0.05, meaning five percent. Put this same setting on every service. The services that are roots will apply the ratio, and the others will follow their parents.
Where to set it
- In deployment files. Put the two settings in your container or orchestration configuration, next to your other environment variables. That way the rate is visible, reviewable and easy to change per environment.
- In an agent configuration. Auto-instrumentation agents read the same variables, so you can control sampling without changing any code.
- In code. If you need a custom sampler, you configure it when you build the SDK’s tracer provider. The environment variable cannot describe custom logic.
Different rates in different environments
It is common to use “keep everything” in development and testing, a small ratio in production and a larger ratio in a staging environment where you want enough traces to validate changes. Because the setting is just text in a configuration file, switching between these is simple.
Keep sampling settings in version control, and write the reason for the rate in a comment next to it. A year from now, nobody will remember why the ratio was 0.07, and they will be afraid to touch it.
Details such as how values outside the range zero to one are handled, or the exact default when no setting is given, can vary slightly between SDK versions. Test your configuration in a staging environment and look at the real share of traces that arrive in your backend.
11Choosing a Sampling Rate
The question every team asks is “what percentage should we keep?” There is no magic number, but there is a sensible way to think about it.
Choosing a rate is a balance between three forces: cost, detail and the chance of seeing rare events. Let us look at each force with numbers.
Force one: cost
Cost grows in direct proportion to the rate. Keeping 10 percent costs ten times as much as keeping 1 percent. If your tracing budget is fixed, the budget divided by the per-trace cost tells you the maximum affordable rate. Remember that the cost includes not only storage but also network use, processing in the collector and the time engineers spend searching through more data.
Force two: detail
For common, frequent operations, a small rate still gives plenty of traces. Suppose the checkout endpoint handles 100 requests per second. At 1 percent, you keep one trace per second, which is 3,600 traces per hour. That is more than enough to understand typical behavior.
Force three: rare events
Rare events are the hard part. Suppose errors happen in one request out of a thousand, and your system handles 10,000 requests per second. There are 10 errors every second. With a 1 percent head-based rate, you keep about 0.1 error traces per second, which is about 6 per minute. That is probably fine. But if the system handles only 10 requests per second, there is one error every 100 seconds, and a 1 percent rate keeps one error trace every 10,000 seconds, which is about once in nearly three hours. For low-traffic or very rare events, head-based sampling at a small rate may effectively never catch what you are looking for.
Think of fishing with a net that catches one fish in a hundred. If the lake is full of common fish, you will catch plenty and learn what lives there. If you are looking for one rare golden fish, the same net might catch nothing for weeks. A small head-based rate is good for learning about the common fish and weak for hunting the rare one.
A practical way to decide
- Start from the traffic of your busiest important endpoint. Decide how many traces per second you want to see from it, for example one to five.
- Compute the ratio. Divide the number of traces you want per second by the number of requests per second.
- Raise the rate for low-traffic services. A rate that works for a busy gateway is too low for a service that handles ten requests a minute.
- Plan for rare events separately. Use rule-based sampling or tail-based sampling for errors and slow requests rather than raising the global rate to unaffordable levels.
- Review after launch. Check real costs and real usefulness after a few weeks, and adjust.
Many teams start with one to ten percent for busy services in production and keep everything in development and testing. Treat this as a first guess to improve with real data, not as a rule.
12Rule-Based and Custom Head Samplers
A flat percentage treats every request the same. Real systems often want different rules for different kinds of traffic. Custom samplers make that possible, within the limits of what is known at the start.
Because a sampler receives the span name, the span kind and the starting attributes, you can write a sampler that makes different choices for different requests. This is still head-based sampling, because the decision is made at the start, but it is smarter than a single percentage.
Examples of useful rules
Skip health checks
Load balancers call health check endpoints every few seconds. These produce huge numbers of identical, boring traces. A rule that drops spans for the health check route saves a large share of volume at no loss of insight.
Always keep checkout
A rule that keeps every trace for the checkout or payment route at 100 percent, while using a small rate for everything else, protects the most valuable business flows.
Higher rate for key customers
If the customer tier is known at the start, for example from a header, a rule can sample premium customers at a higher rate so that their problems are easy to investigate.
Different rates per route
A rarely used admin tool might be sampled at 100 percent, while a high-traffic public product page is sampled at 0.5 percent.
Keep requests marked as debug
An engineer can add a special header to a request in order to force that single request to be sampled. This is very useful for investigating a specific problem, as long as the header is only honored for trusted callers.
Cap traces per second
A rate-limiting sampler keeps at most a fixed number of traces per second, so bursts of traffic cannot cause a burst of cost.
The big limit: only the start is visible
A head sampler can only use facts that exist at the moment the span starts. It does not know:
- Whether the request will end in an error.
- How long the request will take.
- What the response code will be.
- What happens in downstream services.
So a rule such as “keep every trace that has an error” cannot be done at the head, because the error has not happened yet. That kind of rule belongs to tail-based sampling, which is discussed in the next chapters. A head sampler can only guess in advance, using the information it has: which route, which customer, which method.
A nightclub bouncer decides at the door using only what is visible: age on the ID, the dress code, the guest list. The bouncer cannot know whether a visitor will cause trouble later. A manager reviewing the security cameras at the end of the night, who sees what really happened, is the tail-based sampler.
Writing a custom sampler safely
- Keep it fast. The sampler runs on every new span, so it must finish in microseconds. Never call the network or read from a database.
- Keep it safe. Never throw an error. If the rules cannot decide, fall back to a default decision.
- Always wrap it in ParentBased. Your custom logic should be the root sampler. Let ParentBased make sure that the rest of the system follows the decision.
- Keep it deterministic when possible. Where it makes sense, base the decision on the trace ID so that results are fair and repeatable.
- Document the rules. A custom sampler is hidden logic. Write down what it does so that others can understand why traces are missing.
If a rule depends on a value from an incoming request, such as a customer tier header, remember that outside callers can set any header they like. Do not let untrusted input push your sampling rate to 100 percent.
13Head-Based Versus Tail-Based Sampling
To fully understand head-based sampling, it helps to compare it with its sibling. Each has strengths that the other lacks.
graph TD
A["Request starts"] --> B["Head-based: decide now using the trace ID and start details"]
B --> C["Keep or drop immediately at the source"]
A --> D["Tail-based: record spans and send them to a collector"]
D --> E["Collector holds all spans of the trace in memory"]
E --> F["Trace finishes, collector looks at errors, duration and attributes"]
F --> G["Keep or drop after seeing the whole story"]
Look at the two paths in the figure. On the first path, the decision is quick and happens at the source, so almost no data is produced for dropped traces. On the second path, every span is produced and sent to a collector, which holds the spans of each trace in memory until the trace seems complete, and only then decides. The second path is more powerful, and also more demanding.
| Question | Head-based | Tail-based |
|---|---|---|
| When is the decision made? | At the start of the trace | After the trace is complete |
| Where is the decision made? | Inside the SDK, in the application | In a collector or a separate service |
| What does it know? | Trace ID, name, kind, starting attributes | The whole trace: errors, duration, all attributes |
| Can it keep all error traces? | No, it cannot know about errors yet | Yes |
| Can it keep the slowest traces? | No | Yes |
| Cost of dropped traces | Almost none, nothing is produced | Full cost until the decision, because all spans are sent and held |
| Memory needs | None | High, the collector holds traces while waiting |
| State | Stateless | Stateful, needs all spans of a trace in one place |
| Complexity to run | Very low, a setting | Higher, needs routing and capacity planning |
| Risk of an incomplete trace | Low, everyone follows the flag | Higher, late spans may arrive after the decision |
The honest summary
Head-based sampling is simple, cheap and robust, but it is blind to outcomes. Tail-based sampling is smart and can keep the interesting traces, but it needs infrastructure, memory and care. Neither is better in every case. They answer different needs.
Head-based sampling is like a security guard checking every tenth bag at the airport entrance. It is fast, cheap and fair, but it can miss a suspicious bag. Tail-based sampling is like reviewing the camera footage at the end of the day and saving only the clips where something odd happened. It catches more, but needs cameras, storage and someone to review.
Why tail-based sampling needs all spans in one place
To judge a whole trace, the tail sampler must see all of its spans. But the spans come from many services, and they are sent to the collector at different times, possibly through different collector instances. If the spans of one trace are scattered across several collector machines, none of them has the full picture. So tail-based setups usually add a routing layer that sends all spans with the same trace ID to the same collector instance. They also wait for a fixed period for late spans before deciding. These extras are the price of the extra power.
14Combining Head and Tail Sampling
The two approaches are not rivals. Many large systems use both, so that each covers the other’s weakness.
A common design has two stages. First, a head-based sampler in the SDKs removes the bulk of ordinary traffic cheaply. Second, a tail-based sampler in the collector looks at the remaining traces and keeps the interesting ones. The head stage reduces the load on the tail stage, and the tail stage finds the stories that the head stage would have missed.
Head stage: cut the bulk
The SDK keeps, for example, 20 percent of traces using ParentBased with a ratio root. Four out of five traces never produce spans.
Collector stage: gather whole traces
The surviving traces are sent to collectors. A routing layer makes sure all spans of a trace reach the same collector.
Tail stage: apply smart policies
The tail sampler keeps all traces with errors, all traces slower than a limit, and a small share of the rest. Everything else is discarded.
Store: keep the valuable traces
The backend stores the survivors. Costs are small, and the traces you most need to see are present.
The tail stage can only choose among traces that the head stage kept. If the head stage drops 80 percent of traces, then 80 percent of all errors are also gone before the tail stage ever sees them. A two-stage design therefore does not give you “all errors.” It gives you “all errors among the head-sampled traces.” If you truly need every error, you must send all traces to the tail stage and accept the cost, or use other signals such as error metrics and logs to catch every failure.
Collector-side probabilistic sampling
The OpenTelemetry Collector also offers a probabilistic sampler that makes a decision using the trace ID, much like the ratio sampler in an SDK. It is applied after spans have already been produced and sent, so it does not save the cost of producing and transmitting them. But it can reduce the cost of storing them, and it is a convenient way to apply one rate to traces coming from many services that are hard to change. Because it uses the same trace ID idea, it keeps whole traces together as long as the ratios are consistent.
Think of a hiring process. The first round is a quick, cheap screening of every application, which removes most of them. The second round is a careful interview of those who remain. The first round is the head stage, the second is the tail stage. The company never interviews the people removed in the first round, even if some of them would have been wonderful.
15Real-World Use Cases
Here are the situations where head-based sampling is the natural choice. The scenarios are illustrative examples meant to build intuition, not descriptions of any particular company’s internal systems.
1. A high-traffic public website
A news site gets hundreds of thousands of page views per minute. Most of them are alike. A small percentage of traces is more than enough to understand typical performance, find slow dependencies and compare releases. Head-based sampling keeps the cost low without any complex infrastructure.
2. A microservice platform with many teams
Dozens of teams run hundreds of services. Asking each team to run complex tail-sampling infrastructure is unrealistic. A single company-wide setting, parent-based with a ratio, gives every team consistent behavior and consistent traces with almost no effort.
3. Cost control for startups
A young company with a small observability budget needs predictable bills. Because head-based cost scales directly with the rate, the team can pick a rate that matches the budget and adjust it as traffic grows.
4. Noise removal for health checks and monitoring probes
A platform team notices that half of all spans come from load balancer health checks. A simple custom sampler drops those routes entirely and the tracing bill falls by a large fraction overnight.
5. Protecting a slow or fragile backend
Tracing adds a little work to every request. For a very latency-sensitive service, such as a pricing engine that must answer in a few milliseconds, the team reduces the rate so that the tracing overhead stays tiny, and keeps a higher rate in other services.
6. Targeted debugging in production
An engineer investigating a customer’s problem sends a request with a special trusted header that forces sampling for that single request. The trace appears in the backend even though the global rate is very low. This gives high-detail debugging without raising the overall cost.
7. Gradual roll-out checks
When a new version is deployed to a small share of servers, a team temporarily raises the rate for those servers, so that they can compare traces from the new and old versions without paying for full tracing everywhere.
16The Effect on Metrics, Logs and Alerts
Sampling does not only change your trace storage. It changes how you should read everything that is built from traces, and how traces connect to logs.
Metrics built from traces can lie
Some tools compute numbers from spans, such as the number of requests per endpoint, the error rate or the average duration. If only one in a hundred traces is kept, then the number of spans is also one hundredth of the true number. A chart that says “12 requests per second” may really mean “1,200 requests per second, seen through a 1 percent window.” If you forget this, dashboards will appear to show a collapse in traffic every time somebody lowers the sampling rate.
There are two main remedies. The first is to compute such metrics from separate metric instruments that count every request, not from sampled spans. A counter that increments for every request is unaffected by trace sampling. The second is to record the sampling rate along with each kept trace, and to multiply counts by the inverse of the rate when estimating totals. The second approach is what the newer consistent probability sampling work aims to make easy.
If a pollster interviews one voter in a hundred and finds that 12 of them like a candidate, the correct statement is that about 1,200 voters out of the whole crowd like that candidate, not that 12 do. Using sampled data without scaling is like announcing the poll count as the whole election result.
Alerts should not depend on sampled traces
An alert that says “page me if there are more than five error traces per minute” will behave differently at different sampling rates. Worse, at a low rate, a real incident might produce zero sampled error traces for a few minutes. Alerts should be based on metrics that count every event, such as error counters and latency histograms, which are not sampled. Traces are for investigating, not for detecting.
Logs and traces
Many systems add the trace ID and span ID to every log line, so that an engineer can jump from a log message to the trace. With sampling, many of those trace IDs point to traces that were dropped. The link leads nowhere. This is normal, but it surprises people. Remember that the trace ID is still present even for dropped traces, because the context travels even when the span is not recorded. You can still use the trace ID to search logs across services, which keeps the logs useful on their own.
Some log tools can also be told to keep only the logs that belong to sampled traces. That reduces log volume, but it also means that logs for dropped traces are lost, so choose carefully.
Use unsampled metrics to know that something is wrong, and sampled traces to find out why. Do not use sampled traces to count things.
17Advantages, Disadvantages and Trade-offs
Head-based sampling is simple and powerful, but it is not magic. An honest look at both sides helps you use it wisely.
Advantages
- Very simple to set up, often with just two settings.
- Almost no overhead: dropped traces cost nearly nothing to produce.
- Stateless, so it needs no memory, no routing and no coordination.
- Predictable cost that scales directly with the rate.
- Whole traces are kept or dropped together, because everyone follows the parent.
- Works the same way in every language and with every backend.
- Reduces network traffic and load on collectors from the very start.
Disadvantages
- Blind to outcomes: it cannot choose traces because they have errors or are slow.
- Rare events can be missed entirely, especially in low-traffic services.
- A fixed rate does not adapt to traffic spikes or quiet periods.
- Counts and rates computed from sampled spans are wrong unless corrected.
- Logs may point to traces that were never kept.
- Mistakes in configuration can silently break traces across services.
- Outsiders can try to influence sampling if the edge trusts incoming flags.
The central trade-off: cost and simplicity versus insight
Every choice in sampling moves a slider between two ends. On one end, you pay little, run little infrastructure and see a fair but blind sample. On the other end, you pay more, run more machinery and see the stories that matter most. Head-based sampling sits at the cheap and simple end. For many systems, that is exactly the right place to be. For systems where every failure matters, it is a good first layer but not the whole answer.
When head-based sampling is a good fit
- You have high traffic, and the typical behavior of common requests matters most.
- You need a low-cost, low-effort setup that works across many teams.
- You can detect failures by other means, such as unsampled metrics and alerts.
- Your budget is fixed and must be predictable.
When it is not enough on its own
- You must be able to inspect every failed or slow request.
- Traffic is low and failures are rare, so a small rate would hide them.
- Compliance or audit rules require a record of every transaction, which should be handled by dedicated audit logs and not by traces.
18Where Sampling Can Happen: SDK, Collector and Backend
Sampling is not a single place in your system. Data can be thinned out at several points along its journey, and each point has different powers and different costs.
graph LR
A["Application with SDK sampler"] -->|"spans that were kept"| B["Agent or Collector close to the application"]
B -->|"optional probabilistic or tail sampler"| C["Central Collector"]
C -->|"optional tail sampler"| D["Tracing Backend"]
D --> E["Retention rules and storage limits"]
Read the figure from left to right. The first and earliest place is the sampler inside the SDK. This is where head-based sampling lives. Everything dropped here costs almost nothing, because it never becomes a real span. The next places are collectors, which receive spans and can apply their own rules, such as a probabilistic sampler or a tail sampler. Finally, the backend itself may apply retention rules, discarding older data or limiting how much it will accept.
| Stage | What it can see | What it saves | Typical role |
|---|---|---|---|
| SDK sampler | Trace ID and start details only | Production, network and storage costs, all at once | Head-based sampling, the first and cheapest cut |
| Collector probabilistic sampler | The trace ID of received spans | Network after the collector, and storage | Applying one rate to many services that are hard to change |
| Collector tail sampler | The whole trace, with errors and durations | Storage, while keeping valuable traces | Keeping errors and slow traces |
| Backend rules | Stored data and its age | Storage over time | Retention and rate limits |
Why the earliest cut saves the most
Imagine a factory that makes bread. If you decide not to bake a loaf at all, you save the flour, the oven time, the packaging and the delivery. If you decide to throw the loaf away at the shop, you have already paid for everything before that. A head-based sampler decides before baking. Later stages decide after much of the cost has been paid, although they may still save storage and some processing.
Think of water filters in a house. The first filter at the main pipe cleans the water for the whole house cheaply. Finer filters near the kitchen tap give you even better drinking water, but they only work on water that has already passed the first one. Sampling stages work the same way: coarse and cheap at the start, fine and costly later.
The danger of stacking samplers carelessly
When several stages sample independently, their rates multiply. If the SDK keeps 10 percent and a collector then keeps 10 percent of those, you end up with only 1 percent. This is not always intended. Teams sometimes find that their effective rate is far below what they configured, because somebody added a second sampler in a collector without telling the application teams. Keep a written map of every place where sampling happens, and calculate the combined rate.
Write down each stage, its rate and who owns it. The effective rate is the product of all the rates. If the product is not what you want, change the stage that is cheapest to change.
Consistency between stages
Stages that make decisions by trace ID can stay consistent with each other if they use the same method and compatible rates. Whole traces are then kept or dropped together. If a collector sampler uses a different method, for example random choice per span instead of trace ID, it will cut holes into traces that the head stage kept whole. Always choose collector samplers that decide per trace, not per span, unless you have a very specific reason.
19Head-Based Sampling in Queues, Batch Jobs and Serverless Code
Web calls are the easy case. Many real systems also pass work through queues, scheduled jobs and short-lived functions. Sampling works there too, but a few details need care.
Message queues
When a producer puts a message on a queue, it writes the trace context, including the sampled flag, into the message’s metadata. A consumer that reads the message later makes its decision using a ParentBased sampler, which follows the flag. This keeps the whole chain, from the producer through the queue to the consumer, either fully recorded or fully dropped.
There are two details worth knowing. First, the consumer may run long after the producer, so the trace can be spread over a long time. Second, a consumer that processes a batch of messages from many different traces handles several decisions at once. A good consumer reads the context of each message separately, and links its own work to each producer rather than pretending that all messages belong to one trace.
Scheduled and batch jobs
A job that starts on a timer has no incoming request, so it has no parent. It is a root. The sampler for its root span decides whether to keep this run. For jobs that run once an hour or once a day, a low ratio such as 1 percent would almost never keep a single trace. For such rare, important runs, it is usually better to sample at 100 percent, because the volume is tiny and each run matters.
Illustration: a nightly report job
A nightly job builds reports for customers. It runs once, takes two hours and calls many services. If the team used the same 1 percent ratio as the website, they would see a trace on average once every hundred nights. By using a rule that always samples this job, they keep every run, and the cost is negligible because there is only one trace per night. This is an invented example to show the thinking.
Serverless functions
Functions that run on demand in a serverless platform start quickly and stop quickly. Two points matter here. The sampler must be cheap, because it runs on every start, and a ratio sampler fits well. And the exporter must send its data before the function is frozen or stopped, otherwise sampled spans can be lost. Many serverless tools provide a way to flush data at the end of each call. Because a function is often called by another service, a ParentBased sampler lets it follow the caller’s decision.
Long-running requests and streaming
Some requests last for minutes or hours, such as file uploads, video streams or long polling connections. Their decision is made at the start, like any other, but the spans can stay in memory a long time. Consider sampling them at a lower rate or breaking the work into several shorter traces, so that memory use stays under control.
Follow the message
Write the context into the message, and let the consumer follow the flag with a ParentBased sampler.
Rare means keep
Jobs that run rarely deserve a high rate, because a low ratio would hide almost every run.
Cheap and flushed
Use a cheap sampler and make sure the data is sent before the function stops.
Watch memory
Long-lived traces hold spans in memory, so keep the rate low or split the work into shorter traces.
A queue is like a letter in the post. The sender writes the sampling note on the envelope. The reader, who opens the letter hours later, reads the note and follows it. A nightly job is like a once-a-year festival. If you only film one day in a hundred, you will probably never film the festival, so you decide to film it every time it happens.
20Common Mistakes and Anti-patterns
These mistakes appear again and again in real systems. Each one looks harmless, and each one can silently break traces or inflate costs.
The mistake
Using a plain ratio sampler in every service without ParentBased, or using different ratios in different services.
Why it hurts
Each service makes its own independent choice. One service keeps a trace, the next drops it, and the third keeps it again. The result is traces with missing middles and orphan spans that cannot be connected.
Better approach
Wrap the ratio sampler in ParentBased everywhere. Let only the root service use the ratio, and let all others follow the parent.
The mistake
Leaving the default “keep everything for new traces” in a high-traffic production system.
Why it hurts
The bill grows with traffic, and sometimes quietly. Teams are often surprised by a monitoring invoice after a successful marketing campaign multiplied their traffic.
Better approach
Choose a rate on purpose before launch. Revisit it when traffic grows.
The mistake
Expecting head-based sampling to keep all errors or all slow requests.
Why it hurts
The sampler decides before the outcome is known. At a rate of 1 percent, 99 percent of error traces are discarded along with the rest. Engineers then search for the trace of a known failure and find nothing.
Better approach
Detect errors with unsampled metrics and logs. Use tail-based sampling, or a very high rate on critical routes, when you must keep error traces.
The mistake
Building dashboards and alerts from sampled spans, for example “requests per second” or “error rate” computed from the spans that reach the backend.
Why it hurts
The numbers shift whenever the sampling rate changes, and they are wrong in absolute terms. Alerts may fire falsely or fail to fire at all.
Better approach
Use dedicated metric instruments that count every event, or correct sampled counts by the inverse of the sampling rate.
The mistake
Trusting the sampled flag from the open internet at the public edge.
Why it hurts
Anyone can send a request with the sampled flag turned on. If your edge follows it, outsiders can force you to record large volumes of traces, raising cost and filling storage.
Better approach
At the public edge, ignore incoming sampling flags from untrusted sources and make a fresh decision. Honor flags only from trusted internal callers.
The mistake
Breaking context propagation and then blaming sampling, or the other way around.
Why it hurts
If the context is not passed between two services, the second one starts a brand new trace and makes its own random decision. The result looks like a sampling problem, but the true cause is propagation.
Better approach
Check propagation first. Confirm that the trace headers arrive at every hop before changing any sampling settings.
The mistake
Setting one low global rate for everything, including very low-traffic services.
Why it hurts
A service that handles ten requests per minute, sampled at 1 percent, produces about one trace every ten minutes. It becomes practically invisible.
Better approach
Use higher rates, or full sampling, for low-traffic entry points, and low rates for busy ones. Use a custom or rate-limited sampler where one number does not fit all.
21Best Practices
These habits keep head-based sampling predictable, cheap and trustworthy. Think of them as a checklist to run whenever you touch sampling settings.
Use ParentBased everywhere
Wrap every sampler in ParentBased so that decisions made at the root are followed by all other services.
Choose the rate with arithmetic
Estimate traffic, spans per trace, and cost per span. Then compute the rate that fits your budget, and write the reasoning down.
Use the standard settings
Prefer the standard environment variables over code, so the rate is visible in deployment files and easy to change and roll back.
One policy across the company
Define a company default and share it through templates or a base image, so teams do not invent their own rules.
Control the front door
Decide on purpose how the public edge treats incoming sampling flags, and write the policy down.
Count with metrics, not spans
Use unsampled metric instruments for counts, rates and alerts. Use traces for explanation.
Drop the boring routes
Remove health checks and similar repetitive requests with a rule, before reducing the rate for useful traffic.
Revisit regularly
Traffic, costs and needs change. Review the rate every few months and after big launches.
A short pre-launch checklist
- Is every service configured with ParentBased, and do all root services share the same intended rate?
- Do all services propagate context correctly, including through queues and proxies?
- Do dashboards and alerts rely on unsampled metrics rather than on sampled spans?
- Is the public edge configured to handle untrusted sampling flags?
- Have noisy routes such as health checks been handled?
- Do low-traffic services have a rate that produces a useful number of traces?
- Is there a plan for catching rare failures, for example error metrics, logs or a tail-sampling stage?
22Security, Privacy and Cost
Sampling looks like a purely technical dial, but it affects what data you store, who can influence your system, and how much you pay.
Security
- Forced sampling. The sampled flag travels in a header that the caller controls. If your public edge trusts it blindly, an attacker can set it on every request and force your system to record and export everything. This is a form of resource exhaustion that hits your bill and your storage. Make a fresh decision at the public edge.
- Trusted debug headers. A special header that forces sampling is a very handy debugging tool, but it must only be honored for authenticated, trusted callers. Otherwise it becomes the same attack, with an invitation.
- Sampler logic and untrusted input. A custom sampler that uses request attributes must not let outsiders raise their own sampling rate or crash the sampler with strange input.
- Telemetry transport. The spans that you keep must travel and be stored securely, with encryption in transit and access control in the backend.
Privacy
Sampling reduces the amount of data stored, but it does not remove sensitive data from the traces that are kept. A kept trace still carries every attribute that your instrumentation added. If a span contains an email address, a card number or a patient name, that data is stored whenever the trace is sampled. Sampling is a cost tool, not a privacy tool. Protect sensitive data by not putting it in attributes in the first place, and by using processors in the collector to remove or mask it.
One subtle privacy point is that a lower sampling rate can make a rarely used feature appear almost nowhere in the data. That is fine for privacy but might hide security-relevant activity. Security monitoring should not rely on sampled traces.
Cost
Cost is where sampling shines. A rough way to think about total tracing cost is:
- Requests per second, multiplied by
- spans per request, multiplied by
- the sampling rate, multiplied by
- the average size of a span, multiplied by
- the retention period in the backend.
Of these five factors, the sampling rate is the easiest one to change quickly. Halving the rate halves the number of stored spans. A change in a configuration file takes effect after the next restart or deployment, which is much easier than shortening retention or removing instrumentation.
Besides the sampling rate, you can lower cost by limiting the attributes you attach to spans and by removing spans from tiny internal functions that add little insight. Smaller spans and fewer spans per trace multiply with the sampling savings.
23Testing and Validating Your Sampling Setup
A wrong sampling setup rarely causes an error message. It causes missing or lopsided data, which you notice only when you need it. A few deliberate checks prevent that.
Check the ratio statistically
Generate a large number of random trace IDs, for example one hundred thousand, and run them through your configured sampler. Count how many are kept. The share should be very close to the configured ratio. With a ratio of 10 percent and one hundred thousand trials, you should see about ten thousand kept, with a small natural variation. If you see a very different share, the trace IDs are not random enough, or the configuration is not what you think.
Check that decisions are consistent across services
Create two samplers configured like your two services and feed them the same trace IDs. Confirm that, with ParentBased, the second always follows the first’s decision when it is given the first’s context. Also test the case with no parent, to confirm that the root sampler is used.
Check that flags survive the journey
In a staging environment, send requests through every kind of path in your system: web to web, web to queue to worker, and through each proxy or gateway. Confirm that the sampled flag arrives unchanged at the last hop. If it is lost or reset at some hop, the downstream services will start making their own decisions and traces will break.
Check the edge policy
Send a request from outside with the sampled flag set on, and another with it set off, and confirm that your edge handles both as your policy says. If the policy is “ignore incoming flags,” neither should change what the edge decides.
Check the effect on the backend
After deployment, compare the real share of traces that arrives in the backend with the intended rate. Measure the number of root spans per minute against the known request rate from an unsampled metric. The ratio of the two should match your configured rate.
Share matches the ratio
Over many trace IDs, the kept share is close to the configured ratio.
Children follow parents
With a parent present, the decision always equals the parent’s decision.
Flags survive every hop
Through proxies, queues and gateways, the sampled flag arrives unchanged.
Edge policy works
Outside callers cannot force sampling, and trusted debug headers work only for trusted callers.
Traces are whole
Kept traces contain spans from every service on the path, with no missing middles.
Backend volume matches
The number of traces in the backend matches the expected rate against the unsampled request count.
24Monitoring and Troubleshooting Sampling
When traces look strange, sampling is often the first suspect. Here is a calm way to find out whether it is really the cause.
Signs of a sampling problem
- Traces that start in the middle of a request, with no root span.
- Traces that stop halfway, with some services missing from the middle.
- The number of traces suddenly changes after a deployment, without a change in traffic.
- Engineers cannot find the trace for a known failing request.
- Costs rise or fall in a way that does not match traffic.
A troubleshooting walk-through
Is context reaching each service?
First rule out propagation problems. Look at the incoming headers at the service where the trace breaks. If the trace headers are absent, the cause is propagation, not sampling.
What sampler is each service really using?
Check the actual environment settings and any code configuration. Look for services that use a plain ratio sampler, always-on, or always-off instead of a ParentBased variant.
Is the sampled flag preserved?
Compare the flag at the sender with the flag at the receiver. A proxy, a message system or a custom library might reset or drop it.
Are rates and rules as intended?
Check the root services’ ratios and any custom rules. A rule written for one route may unintentionally catch another route.
Are spans being dropped after sampling?
Sampled spans can still be lost later, for example when an exporter queue is full or a collector rejects data. Check the SDK and collector logs and internal metrics for dropped spans or failed exports.
Is a second sampler downstream?
A collector with its own probabilistic or tail sampler may drop more traces after the SDK has already kept them. Compare the counts before and after each stage to see where the losses happen.
Measuring sampling health over time
Treat sampling as something to observe, not just configure. Track the number of root spans created, the number sampled, and the number exported, and compare them with the true request rate from an unsampled counter. The ratio of sampled to created should match the configured rate. A sudden change signals a configuration problem. A growing gap between sampled and exported signals trouble in the export path.
When you investigate a single failing request, temporarily add the trusted debug header, or temporarily raise the rate on the affected route. Do it for a short, planned time, then put the settings back.
25Frequently Asked Questions
Short, direct answers to the questions beginners ask most often.
26Summary and Key Takeaways
You started with a museum handing feedback cards at the entrance and ended with a full picture of how head-based sampling works in OpenTelemetry. Here is everything that matters in one place.
Head-based sampling makes one quick decision at the start of every trace, using only what is known at that moment, and then passes the decision to every other service through the context. It is cheap, simple and predictable, and it keeps traces whole. Its weakness is that it is blind to what will happen later, so it cannot promise to keep errors or slow requests. Used with ParentBased everywhere, a sensible rate, a careful public edge, and unsampled metrics for counting and alerting, it is the foundation of tracing at scale. When you need more insight, add a tail-based stage on top.
Key Takeaways
- What it is: the keep-or-drop decision for a trace is made at the root span, at the start of the trace, and followed by all other services.
- How it works: a sampler looks at the trace ID and start details, returns drop, record only, or record and sample, and the choice travels in the sampled flag of the context.
- The key combination: ParentBased wrapped around a ratio sampler, set through the standard sampler environment variables, on every service.
- Choose the rate with arithmetic: use traffic, spans per trace, cost per span and the need to see rare events, and review it regularly.
- Know its blind spot: it cannot choose traces because of errors or slowness. Use unsampled metrics, logs and, when needed, tail-based sampling.
- Do not count with sampled spans: build dashboards and alerts on unsampled metrics, or correct counts by the sampling rate.
- Protect the edge and test the setup: do not trust incoming sampling flags from the internet, and verify the ratio, the consistency and the flag’s journey in staging.