What Is Head-Based Sampling in OpenTelemetry?

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.

i
How to read this guide

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.

200,000
spans per second without sampling in our example
17.3 billion
spans per day without sampling
173 million
spans per day when keeping 1 percent
Analogy

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.

Sampling trades completeness for affordability. The art is choosing what to give up.

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.

Unit of work

Span

One timed piece of work with its own ID, such as a database call.

Whole journey

Trace

All the spans of one request, tied together by a shared trace ID.

The head

Root span

The first span of a trace, created where the request first enters your system.

The badge

Context

The information that travels with the request, including the trace ID, the current span ID and the sampling flag.

Analogy

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.

Decide at the start

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.

Decide at the end

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.

Analogy

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.

i
Whole traces, not random spans

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"]
    
Figure 1: The decision is made once, at the root, and then passed along with the request.

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.
Analogy

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.

i
The most common default

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.

DecisionIs the span recorded?Is it exported?Sampled flag in the context
DropNo, the span is a hollow placeholderNoOff
Record onlyYes, its data is collectedNoOff
Record and sampleYesYesOn

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.

Analogy

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.

Keep everything

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.

Keep nothing

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.

Keep a share

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.

Follow the parent

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.

CombinationMeaning in plain wordsTypical use
AlwaysOnKeep every traceDevelopment, tests, tiny systems
AlwaysOffKeep no traceSwitching tracing off
TraceIdRatioBased with 10 percentEvery service makes its own 10 percent choice, by trace IDSimple systems where every service uses the same ratio
ParentBased with AlwaysOn as rootFollow the parent. New traces are always keptThe usual default in SDKs
ParentBased with TraceIdRatioBased as rootFollow the parent. New traces are kept at a fixed ratioThe most common production setup
ParentBased with AlwaysOff as rootFollow the parent. New traces are never startedA service that should only join traces started by others
Analogy

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.

i
Custom samplers

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.

1

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.”

2

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.

3

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.

4

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.

Analogy

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.

!
Randomness matters

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.

SituationQuestion the sampler asksDefault behavior
No parentIs this the root of the trace?Ask the root sampler, which you configure, such as a ratio sampler
Remote parent, sampledDid a service in another process say yes?Say yes (AlwaysOn)
Remote parent, not sampledDid a service in another process say no?Say no (AlwaysOff)
Local parent, sampledDid a span in this same program say yes?Say yes (AlwaysOn)
Local parent, not sampledDid 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.

Analogy

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.

!
Where the ratio really lives

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
    
Figure 2: The root decides, the flag travels, and downstream services follow.

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.

i
Messages and queues

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 nameMeaningUses the argument?
always_onKeep every traceNo
always_offKeep no traceNo
traceidratioKeep a fixed share by trace IDYes, the ratio between 0 and 1
parentbased_always_onFollow the parent, new traces always kept. This is the usual defaultNo
parentbased_always_offFollow the parent, new traces never keptNo
parentbased_traceidratioFollow the parent, new traces kept at a fixed shareYes, 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.

i
Tip

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.

!
Check what your SDK does with the ratio

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.

10,000/s
requests, with errors in 1 of 1,000 requests
10/s
error requests happening
0.1/s
error traces kept at a 1 percent rate
Analogy

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.
i
A common starting point

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

Drop noise

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.

Keep important

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.

By customer

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.

By environment

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.

By testing

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.

By rate limit

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.

Analogy

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.
!
Be careful with attributes you trust

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"]
    
Figure 3: Head-based sampling decides at the start. Tail-based sampling decides after the trace is complete.

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.

QuestionHead-basedTail-based
When is the decision made?At the start of the traceAfter the trace is complete
Where is the decision made?Inside the SDK, in the applicationIn a collector or a separate service
What does it know?Trace ID, name, kind, starting attributesThe whole trace: errors, duration, all attributes
Can it keep all error traces?No, it cannot know about errors yetYes
Can it keep the slowest traces?NoYes
Cost of dropped tracesAlmost none, nothing is producedFull cost until the decision, because all spans are sent and held
Memory needsNoneHigh, the collector holds traces while waiting
StateStatelessStateful, needs all spans of a trace in one place
Complexity to runVery low, a settingHigher, needs routing and capacity planning
Risk of an incomplete traceLow, everyone follows the flagHigher, 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.

Analogy

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.

1

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.

2

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.

3

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.

4

Store: keep the valuable traces

The backend stores the survivors. Costs are small, and the traces you most need to see are present.

!
The hidden limit of combining

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.

Analogy

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.

Analogy

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.

i
Rule of thumb

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"]
    
Figure 4: Data can be reduced at several stages between the application and long-term storage.

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.

StageWhat it can seeWhat it savesTypical role
SDK samplerTrace ID and start details onlyProduction, network and storage costs, all at onceHead-based sampling, the first and cheapest cut
Collector probabilistic samplerThe trace ID of received spansNetwork after the collector, and storageApplying one rate to many services that are hard to change
Collector tail samplerThe whole trace, with errors and durationsStorage, while keeping valuable tracesKeeping errors and slow traces
Backend rulesStored data and its ageStorage over timeRetention 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.

Analogy

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.

!
Stage by stage, keep a map

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.

Queues

Follow the message

Write the context into the message, and let the consumer follow the flag with a ParentBased sampler.

Batch

Rare means keep

Jobs that run rarely deserve a high rate, because a low ratio would hide almost every run.

Serverless

Cheap and flushed

Use a cheap sampler and make sure the data is sent before the function stops.

Long requests

Watch memory

Long-lived traces hold spans in memory, so keep the rate low or split the work into shorter traces.

Analogy

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.

ANTI-PATTERN 01 Avoid
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.

ANTI-PATTERN 02 Avoid
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.

ANTI-PATTERN 03 Avoid
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.

ANTI-PATTERN 04 Avoid
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.

ANTI-PATTERN 05 Avoid
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.

ANTI-PATTERN 06 Avoid
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.

ANTI-PATTERN 07 Avoid
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.

Default

Use ParentBased everywhere

Wrap every sampler in ParentBased so that decisions made at the root are followed by all other services.

Rate

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.

Config

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.

Consistency

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.

Edge

Control the front door

Decide on purpose how the public edge treats incoming sampling flags, and write the policy down.

Metrics

Count with metrics, not spans

Use unsampled metric instruments for counts, rates and alerts. Use traces for explanation.

Noise

Drop the boring routes

Remove health checks and similar repetitive requests with a rule, before reducing the rate for useful traffic.

Review

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.

Rate
the quickest lever to change cost
Spans per trace
reduce by removing low-value instrumentation
Retention
shorten for older, less useful traces
i
Tip

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.

Check 1

Share matches the ratio

Over many trace IDs, the kept share is close to the configured ratio.

Check 2

Children follow parents

With a parent present, the decision always equals the parent’s decision.

Check 3

Flags survive every hop

Through proxies, queues and gateways, the sampled flag arrives unchanged.

Check 4

Edge policy works

Outside callers cannot force sampling, and trusted debug headers work only for trusted callers.

Check 5

Traces are whole

Kept traces contain spans from every service on the path, with no missing middles.

Check 6

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

1

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.

2

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.

3

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.

4

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.

5

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.

6

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.

i
Tip

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.

Q1What is head-based sampling in one sentence?
It is deciding at the very start of a trace, at the root span and before the outcome is known, whether the whole trace will be kept or dropped, and passing that decision to every service in the trace.
Q2Why is it called “head-based”?
Because the decision is made at the head, the beginning, of the trace. Tail-based sampling makes the decision at the tail, after the trace has finished.
Q3Where does the sampling decision happen?
In the sampler inside the OpenTelemetry SDK of the first service, which creates the root span. Other services normally follow that decision through the sampled flag in the propagated context.
Q4What is the default sampler?
SDKs normally default to a parent-based sampler whose root behavior is always on. That means new traces are kept and existing decisions are followed. It is usually too costly for large production systems, so a ratio is typically configured.
Q5What does a ratio of 0.1 mean?
About ten percent of new traces are kept, chosen by looking at the trace ID, and about ninety percent are dropped. Over many requests, the share approaches the ratio.
Q6Why do I need ParentBased if I already have a ratio sampler?
Without ParentBased, every service makes its own decision, and traces can end up with missing pieces. ParentBased makes services follow the decision made at the root, keeping traces whole.
Q7If I change the ratio in a backend service, why does nothing happen?
When the service is configured with ParentBased, it only uses its own ratio for traces that start in that service. For requests from other services, it follows the parent’s decision.
Q8Can head-based sampling keep all error traces?
No. At the start of a request, nobody knows yet whether it will fail. To keep all error traces you need tail-based sampling, or you must detect errors with unsampled metrics and logs.
Q9What happens to a dropped span?
The SDK creates a lightweight placeholder that records nothing and is not exported. The trace ID and span ID still travel in the context, with the sampled flag off, so other services can follow the decision and logs can still carry the IDs.
Q10Is “recorded” the same as “sampled”?
No. A recorded span collects data in memory. A sampled span is also exported. A span can be recorded but not sampled, which is sometimes useful for local processing.
Q11Do metrics get affected by trace sampling?
Metrics from separate metric instruments are not affected. But numbers computed from sampled spans, such as request counts, are reduced by the sampling rate, so they must be corrected or avoided for counting.
Q12Why do some log lines point to traces that do not exist?
Because the trace ID is still written into logs even when the trace was dropped. The trace never reached the backend, but the ID is still useful to search logs across services.
Q13How is it different from tail-based sampling?
Head-based sampling decides at the start, in the SDK, with very little cost, but cannot see outcomes. Tail-based sampling decides at the end, usually in a collector, with full knowledge of the trace, but needs memory, routing and more care.
Q14Can I use both together?
Yes. Many systems use a head-based rate to remove the bulk of traffic cheaply and a tail-based stage to keep the interesting traces among the survivors. Remember that the tail stage can only choose from what the head stage kept.
Q15Should my public gateway trust incoming sampling flags?
Usually not. Outsiders can set the flag and force you to record large volumes. Make a fresh decision at the public edge and honor flags only from trusted internal callers.
Q16Which setting do I use in production?
A common choice is the parent-based ratio sampler, set through the standard sampler and sampler argument environment variables, with a ratio chosen from your traffic and budget. Put the same setting on every service.
Q17Does a lower rate make my application faster?
Slightly. Dropped spans avoid collecting attributes, batching, serializing and sending data. For most applications, the difference is small, but for very latency-sensitive paths it can matter.
Q18How do I debug one specific request in production at a low rate?
Use a trusted debug mechanism, such as a special header honored only for authenticated callers, to force sampling of that one request. Alternatively, raise the rate briefly for the affected route.

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.