What Is a Sampler in OpenTelemetry?
A friendly, ground-up guide to the small decision-maker inside your application that chooses, for every new span, whether it should be kept or thrown away: what it sees, what it answers, which ones are built in, and how to choose and trust them.
Picture the doorman at a busy concert hall. Thousands of people arrive every evening, and the doorman has only a few seconds to decide about each one. Does this person have a ticket? Are they on the guest list? Is this a door for staff only? The doorman cannot read every person’s life story. He looks at a few quick facts, such as the ticket and the wristband, and says “come in” or “not this way.” The decision must be fast, fair and consistent, because a slow doorman creates a queue, and an unfair one creates arguments.
In OpenTelemetry, a sampler is that doorman for your traces. Every time your application starts a new piece of work that could be recorded, the sampler looks at a few quick facts and decides whether this work should be kept or dropped. In this guide, we will learn what a sampler is, what it can see, what answers it can give, which ones come built in, and how to choose and configure them with confidence, using simple words and everyday examples.
1The Problem: Deciding Before You Know
Before learning the tool, let us feel the problem it solves.
A quick reminder about traces
A trace is the complete story of one request as it travels through your system. A span is one timed step inside that story, such as “check stock” or “charge the card”. Spans are joined together by a shared trace ID, and together they make a tree that shows where the time went.
Why not record every span?
A busy system can create billions of spans every day. Each one must be created, filled with details, stored in memory, packed into a batch, sent over a network, stored on disk and later searched. Every step costs CPU, memory, network and money. Most spans come from boring, healthy, nearly identical requests. Keeping all of them is like filming every second of a quiet street for a year, just to catch the one moment when something happens.
So we need a way to keep only a share of traces. This is called sampling. The big question is: who decides, and when?
The earliest possible moment
The cheapest time to decide is at the very start. If we decide at the start that a trace will not be kept, we can skip almost all the work for it: no details to collect, no memory to hold, no bytes to send. The component that makes this early decision is the sampler. It is cheap and fast, but it has one big limit: at the start, it does not yet know how the request will end. It cannot know if the request will fail, be slow or be special. It must decide using only what is known at that moment.
Think of a school photographer arriving at a sports day. She cannot take a picture of every second. At each moment, she must quickly decide whether to press the button. She uses simple rules: “photograph every race start,” or “photograph one out of every ten moments.” She cannot know in advance who will win, so she decides with the facts she has right now. A sampler is that photographer’s quick rule.
2So, What Is a Sampler?
The short answer first, and then the pieces.
A sampler is a small component inside the OpenTelemetry SDK, the engine that sits behind the API in your application. Its single job is to answer one question each time a new span is about to be created: “Should this span be recorded and kept?” It is attached to the tracer provider, which is the object that hands out tracers, and every tracer under that provider uses it.
What a sampler is not
- It is not in the API. The API only describes how code asks for spans. The sampler is part of the SDK.
- It is not a processor. A span processor works on spans after they exist and end. A sampler works before the span is even fully created. This is one of the most common confusions, and we will return to it.
- It is not tail sampling. Tail sampling happens later, usually in the Collector, after the whole trace is known. The sampler is the early, head-style decision inside the application.
- It does not affect metrics. Metrics recorded through the metrics API do not pass through the trace sampler.
- It is not a security filter. It decides which traces to keep, not what sensitive data they contain.
Inside the SDK
Part of the tracer provider, configured when the SDK is built or through standard settings.
At Span Creation
Consulted exactly once for each new span, at the moment it is created and before any real work is recorded.
One Question
Returns a decision about whether to record the span and whether to mark the trace as sampled.
Control Cost Early
Dropping early avoids work, memory, network and storage for traces nobody needs.
3Where the Sampler Sits in the Span Lifecycle
Let us follow what happens when your code starts a span.
sequenceDiagram
participant Code as Your Code
participant API as OpenTelemetry API
participant Tr as Tracer in SDK
participant Sm as Sampler
participant Sp as Span
participant Pr as Span Processor
Code->>API: Start a span called charge card
API->>Tr: Create span
Tr->>Sm: Should this span be sampled? Here are the facts
Sm-->>Tr: Decision plus optional extra attributes
alt Decision is drop
Tr-->>Code: Return a non-recording span with the same identity
else Decision is record and sample
Tr->>Sp: Create real recording span
Tr-->>Code: Return recording span
Code->>Sp: Add attributes and end the span
Sp->>Pr: Hand over finished span for export
end
What happens to a dropped span
When the answer is “drop,” the SDK does not throw an error and does not return nothing. It returns a lightweight non-recording span. Your code can still call it, add attributes and end it, and the calls simply do nothing. This is crucial for two reasons. First, your code never needs to check whether sampling is on. Second, the span still carries the trace identity, so the “not sampled” decision can be passed on to the next service.
Think of a theatre that gives every visitor a wristband, but only some wristbands are linked to the camera system. Visitors with an unlinked wristband can still walk anywhere, scan the wristband and show it to staff. Nothing is filmed, but they are still part of the crowd. A non-recording span is the unlinked wristband.
The sampler runs before the span processor
A span processor, such as the batch processor, only ever sees spans that were created as recording spans and then ended. A span that the sampler dropped never reaches it. This is why a sampler can save so much more work than any later step: the dropped span never gets details, never joins a queue, and never reaches an exporter.
4Core Concepts: What a Sampler Sees and What It Says
A sampler is like a function: facts go in, a decision comes out.
The inputs: the facts a sampler can use
When the SDK asks the sampler for a decision, it hands over a small bundle of facts about the span that is about to start.
| Input | What It Is | Example Use |
|---|---|---|
| Parent context | The identity and flags of the parent span, if there is one | Follow the parent’s decision |
| Trace ID | The random identity shared by the whole trace | Use it as a fair lottery number |
| Span name | The label of the span, such as the operation name | Drop spans with a certain name |
| Span kind | The role: server, client, internal, producer or consumer | Sample server spans differently from internal ones |
| Attributes at start | Details given when the span was created | Drop requests to a health-check address |
| Links | Connections to other spans given at creation | Treat batch jobs that link to many messages differently |
The outputs: what a sampler returns
The sampler returns a small answer with up to three parts.
The Decision
One of three choices: drop, record only, or record and sample. We explain these in the next chapter.
Extra Attributes
Optional details the sampler wants to add to the span, such as the rule that matched. These appear on the span if it is recorded.
Trace State
An optional updated list of vendor notes that travels with the trace identity, which advanced samplers can use to record how the decision was made.
A sampler can only see facts that exist at the moment the span starts. If a detail is added to the span later, such as an error status or a response code, the sampler never sees it. This is the single most important limit of any sampler, and the reason errors cannot be reliably kept by a sampler alone.
Samplers have a name
Every sampler also provides a short description string, such as “always on” or “ratio based with 0.1”. This is not used in decisions. It helps people see in logs and debug output which sampler is active, which is very handy when you are puzzled about why traces are missing.
5The Three Decisions and the Sampled Flag
Three answers, and a small flag that carries the answer across the network.
| Decision | Is the Span Recording? | Is the Sampled Flag On? | What Happens |
|---|---|---|---|
| Drop | No | No | The span is non-recording. No details are collected. Nothing is exported. |
| Record only | Yes | No | Details are collected inside the program, but the trace is not marked as sampled for others. Normally not exported as a kept trace. |
| Record and sample | Yes | Yes | Details are collected, the trace is marked as sampled, and the finished span is exported. |
The sampled flag
The sampled flag is a single on or off switch inside the trace flags, which are part of the span’s identity, called the span context. It travels from service to service in the standard trace header. When it is on, downstream services learn “this trace is being kept.” When it is off, they learn “this trace is not being kept.”
Recording versus sampled: two ideas, not one
These two words sound alike but mean different things.
- Recording is a local fact. It means this span, inside this program, is collecting details such as attributes and events.
- Sampled is a shared fact. It is a message to the whole trace that says the trace will be kept and exported.
Most of the time the two go together. The special case “record only” separates them. It lets a program collect details for its own use, such as turning spans into metrics inside a span processor, without declaring that the trace should be exported or promising anything to other services.
Think of a student taking notes during a lecture. Taking notes for yourself is “recording.” Handing the notes in to be graded is “sampled.” You can take notes without handing them in, but you cannot hand in notes you never took. The “record only” decision is a student who takes notes for private use only.
For most beginners, only two outcomes matter: keep (record and sample) and drop. “Record only” is a specialist tool, and you will rarely set it yourself unless you are building a custom sampler or a special span processor.
6The Simplest Samplers: Always On and Always Off
Two samplers that never think. They always give the same answer.
Keep Everything
Every span is recorded and sampled. Perfect for learning, testing and low-traffic services, because you lose nothing.
Keep Nothing
Every span is dropped. Useful to switch tracing off quickly without removing instrumentation, or to protect a very sensitive or very heavy code path.
When “always on” is the right answer
- Development and testing: you want to see everything while you learn.
- Low-traffic services: if a service handles a few requests a minute, keeping all traces costs almost nothing and gives complete visibility.
- Short-lived experiments: for a short debugging period, you may want full detail.
When “always off” is the right answer
- Emergency brake: if tracing is causing trouble, switching to always off stops it at once.
- Excluded areas: a custom setup may use “always off” for certain operations, such as health checks.
- Cost freeze: temporarily pausing trace collection while you fix a budget problem.
In most SDKs, the default sampler keeps every new trace, while following the parent’s decision when there is one. That is the same as “always on” for new traces. On a very busy production system, this default can lead to large bills and slow tools. Choose your sampler on purpose, not by default.
7The Ratio Sampler: A Fair Lottery Using the Trace ID
The workhorse for cutting volume in a simple and fair way.
The trace ID ratio sampler keeps a fixed share of traces, such as one in ten. It does so without needing a counter or any memory of earlier decisions. It uses the trace ID itself as a lottery number.
How the lottery works
A trace ID is a very large random number. Think of it as a ticket number in a lottery with an enormous range of possible tickets. The sampler picks a cut-off point. If you want to keep ten percent, the cut-off is set so that ten percent of all possible ticket numbers fall below it. For each new trace, the sampler checks whether the trace ID falls below the cut-off. If yes, keep. If no, drop.
flowchart LR
A["New trace with random trace ID"] --> B["Turn part of the ID into a number"]
B --> C{"Is the number below the cut-off for the chosen ratio?"}
C -->|Yes| D["Record and sample"]
C -->|No| E["Drop"]
Why this design is clever
- No shared memory needed. Every service can compute the same answer from the same trace ID, without talking to any other service.
- Even and unbiased. Because trace IDs are random, the kept traces are a fair mix, not favouring any route or customer.
- Very fast. A simple comparison of numbers, with no waiting and no network.
- Predictable. Over a large number of traces, the share kept is very close to the ratio you chose.
Imagine a school where every student has a roll number from 1 to 100, and the rule for a health check is “all students with roll numbers 1 to 10 come today.” Any teacher who sees a student’s roll number can tell, without asking anyone, whether this student is chosen. That is how every service reaches the same decision from the same trace ID.
Important cautions about the ratio sampler
It Ignores the Parent by Itself
On its own, the ratio sampler does not look at the parent’s decision. It just uses the trace ID. To respect the parent, wrap it inside the parent-based sampler, which we cover next.
Different Ratios Disagree
If service A keeps ten percent and service B keeps five percent, they may not agree on the same traces, which produces partial traces unless parent-based sampling is used.
Algorithms May Differ
The exact way the ID is turned into a number is not guaranteed to be identical in every language. Mixing languages with independent ratio samplers can lead to small disagreements.
It Cannot Prefer Errors
The lottery does not know which traces will fail or be slow. Random choice means you also randomly drop most of your errors.
Newer approaches in the specification, called consistent probability sampling, aim to fix the mixed-ratio and mixed-language issues by recording extra information that makes decisions line up. We look at this idea in a later chapter. Support is still maturing in many SDKs.
8The Parent Based Sampler: Following the Leader
The most important sampler for any system with more than one service.
Imagine three services in a chain: a gateway, an order service and a payment service. If each service made its own independent decision, you would get traces with holes in them. The parent-based sampler solves this with a simple rule: if the span has a parent, follow the parent’s decision. If it does not have a parent, ask another sampler.
The five slots
The parent-based sampler is really a small switchboard with five slots. Each slot holds another sampler that is used in a specific situation. This is where its power, and sometimes its confusion, comes from.
| Situation | Slot Name | Typical Default | Meaning |
|---|---|---|---|
| No parent at all, a brand new trace | Root | The sampler you choose, such as a ratio sampler | The first and only fresh decision |
| Parent came from another service and was sampled | Remote parent sampled | Always on | Follow the caller’s “keep” |
| Parent came from another service and was not sampled | Remote parent not sampled | Always off | Follow the caller’s “drop” |
| Parent is inside the same program and was sampled | Local parent sampled | Always on | Keep the child because its parent is kept |
| Parent is inside the same program and was not sampled | Local parent not sampled | Always off | Drop the child because its parent is dropped |
flowchart TD
A["New span is created"] --> B{"Is there a parent span context?"}
B -->|No| R["Use the root sampler such as the ratio sampler"]
B -->|Yes| C{"Is the parent remote?"}
C -->|Yes| D{"Is the parent sampled?"}
C -->|No| E{"Is the parent sampled?"}
D -->|Yes| RS["Use the remote parent sampled slot"]
D -->|No| RN["Use the remote parent not sampled slot"]
E -->|Yes| LS["Use the local parent sampled slot"]
E -->|No| LN["Use the local parent not sampled slot"]
Why “remote” and “local” are separated
A remote parent is a span that lives in another program and reached us through a request header. A local parent is a span inside the same program. The difference matters for trust. A local parent was created by your own code, so you trust its decision. A remote parent was created by someone else, possibly outside your company, so you may want to treat its “keep” with caution. This leads to a very useful trick, which we describe in the chapter about public edges.
sequenceDiagram
participant G as API Gateway with ratio 10 percent
participant O as Order Service with parent based
participant P as Payment Service with parent based
G->>G: No parent so root sampler rolls the lottery
G->>O: Request with trace header and sampled flag on
O->>O: Remote parent sampled slot says keep
O->>P: Request with trace header and sampled flag on
P->>P: Remote parent sampled slot says keep
P-->>O: Response
O-->>G: Response
Think of a school trip. The head teacher decides at the school gate whether this class is going on the trip. Every other teacher on the trip, the bus driver and the guide simply follow that decision. They do not hold their own votes. The five slots are like five small rules for special cases, such as “what if the instruction came from the school office versus from a teacher standing right here?”
Production Example: Holes in the Trace
A team set a ten percent ratio sampler in each of five services, without wrapping it in a parent-based sampler. Each service rolled its own lottery, using the same ratio, but different languages used slightly different algorithms. Many traces came out with missing middle steps. After wrapping the sampler in a parent-based one in every service, a trace was either complete or absent, and the confusion stopped.
9Common Sampler Setups and When to Use Them
Real systems use a few well-known combinations.
| Setup | How It Behaves | Good For |
|---|---|---|
| Parent based with always on as root | Keeps every new trace, and follows the parent for calls it receives | Development, small systems and the safe default |
| Parent based with ratio as root | Keeps a fixed share of new traces, and everyone else follows | The classic production setup for busy systems |
| Parent based with always off as root | Starts no new traces on its own, but joins traces started elsewhere that were sampled | Internal services where a front door service owns the decision |
| Plain ratio, no parent based | Each service decides from the trace ID alone | Rare. Only when every service uses an identical ratio algorithm and you accept the risks |
| Always off | No traces at all | Switching tracing off completely |
| Custom rule-based root | Different rates for different routes or customers, followed by everyone else | When some traffic is far more valuable than others |
flowchart TD
Q["Choose your sampler"] --> A{"Is traffic small enough to keep everything?"}
A -->|Yes| B["Parent based with always on"]
A -->|No| C{"Is this a front door service that starts traces?"}
C -->|Yes| D{"Do all routes deserve the same rate?"}
D -->|Yes| E["Parent based with a ratio root"]
D -->|No| F["Parent based with a custom rule-based root"]
C -->|No| G["Parent based, following the decision of callers"]
The “who owns the decision” idea
A clean design gives one place the job of starting traces and making the fresh decision, usually the first service that receives a user request. Every other service uses parent-based sampling and simply follows. This keeps traces whole, and it makes the system easy to reason about: to change the keep rate for the whole company, you change one setting at the front door.
10Configuring a Sampler Without Writing Code
Most of the time, you choose a sampler with standard settings.
OpenTelemetry defines standard environment variables, which are named settings handed to a program when it starts. Two of them control the sampler, and they work in nearly all languages and in auto-instrumentation agents.
| Setting | What It Controls | Allowed Values |
|---|---|---|
OTEL_TRACES_SAMPLER | Which sampler to use | always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio |
OTEL_TRACES_SAMPLER_ARG | An argument for the sampler, such as the ratio | A number between 0 and 1 for ratio samplers, such as 0.1 for ten percent |
How to read these names
- always_on and always_off are the plain samplers, with no parent logic.
- traceidratio is the ratio sampler on its own, with no parent logic.
- parentbased_ followed by a name means: use a parent-based sampler, with that named sampler as the root. So parentbased_traceidratio means “follow the parent, and use a ratio for new traces.”
For a typical production service, the setting parentbased_traceidratio with an argument of your chosen share is the most common choice. It keeps traces whole and gives you a single dial for volume.
Code-based and agent-based setups
- Manual SDK setup: when you build the SDK in code, you pass a sampler object to the tracer provider. This is also how you plug in a custom sampler or fine-tune the five parent-based slots.
- Auto-instrumentation agents: agents read the same environment variables and usually offer extra, language-specific settings for more advanced samplers.
- Operator-based setups: in Kubernetes, the OpenTelemetry Operator lets you set a sampler as part of the instrumentation definition, so that a team can control sampling for many services in one place.
Changing these settings normally needs a restart of the application, since the sampler is built when the SDK starts. If you need to change rates without restarting, look at remote sampling, covered later in this guide.
11Rule-Based and Custom Samplers
When the built-in samplers are not enough, you can add your own rules.
The sampler is a small, well-defined piece, with a clear contract: facts in, decision out. This makes it easy to write your own, or to use ready-made ones from the OpenTelemetry contributions. A rule-based sampler looks at the facts and applies a list of rules, such as: “if the span is a request to the health-check address, drop it. If it is a checkout request, keep all of them. For everything else, keep ten percent.”
What rules usually look at
Route or Path
Drop noisy paths such as health checks and metrics pages, and keep valuable ones such as checkout.
Span Name or Kind
Sample server spans differently from internal ones, or drop spans with a known noisy name.
Customer or Tier
Keep all traces for premium customers or for a testing group, if that detail is known at the start.
Debug Markers
Keep every trace where a debug flag was set, so engineers can force a trace on when investigating.
The golden rule: only start-time facts
A rule can only use facts that are known when the span starts. This leads to a common surprise. Web instrumentation libraries usually give the sampler the HTTP method and the request path when a server span starts. But some details, such as the final route template, the response status code and any error, are only known later, after the request has been handled. A rule that depends on them cannot work in a sampler.
| Fact | Known at Span Start? | Usable by a Sampler? |
|---|---|---|
| HTTP method and request path | Usually yes | Yes |
| Span name and kind | Yes | Yes |
| Parent identity and sampled flag | Yes | Yes |
| Route template chosen after routing | Often no | Usually no |
| Response status code | No | No |
| Error or exception | No | No |
| Total duration | No | No |
Writing your own sampler: the rules of the road
- Be fast. The sampler runs on the path of every span, thousands of times a second. It must do only quick work, with no network calls, no file reads and no waiting.
- Be thread-safe. Many threads may ask for decisions at once. The sampler must work correctly without them stepping on each other.
- Be deterministic where possible. The same trace ID and facts should give the same answer, so that services can agree.
- Delegate to existing samplers. Build rules on top of the parent-based and ratio samplers rather than rewriting their logic.
- Be careful with state. A sampler that counts or limits rates needs shared state, which makes decisions less consistent across services and copies.
- Describe yourself clearly. Provide a helpful description string, so that debug output tells you which rule set is active.
Production Example: Dropping the Noise at the Door
A team found that nearly half of their spans came from a load balancer calling a health address every second. Their front door service used a rule-based root sampler: drop requests to the health address, keep all requests to checkout and payment, and keep ten percent of everything else. Every service behind the front door used parent-based sampling. Cost dropped sharply, and the traces that mattered most were always there.
12Remote Samplers: Changing the Dial Without Restarting
What if the sampling rate could be controlled from a central place?
Restarting hundreds of services just to change a sampling rate is slow and risky. A remote sampler solves this by asking a central server, at regular intervals, “what rules should I use now?” The server answers with the current policy, and the sampler updates itself while the application keeps running.
sequenceDiagram
participant App as Application with remote sampler
participant Srv as Central sampling server
participant Op as Operator or engineer
Op->>Srv: Update the sampling policy for checkout
App->>Srv: Poll for current policy every interval
Srv-->>App: Return updated rules and rates
App->>App: Apply new rules to future spans
Well-known remote sampling systems
Jaeger Remote Sampler
Applications fetch per-service and per-operation sampling strategies from a Jaeger component. Jaeger also supports adaptive strategies, which adjust rates automatically based on traffic.
X-Ray Remote Sampler
Applications fetch rules from AWS X-Ray, which combine a small guaranteed number of traces per second with a percentage of the rest.
Benefits and costs
Benefits
- Change rates instantly, with no restart
- Set different rates for different services and operations
- React to incidents by temporarily raising rates
- Keep all policy in one place for the whole company
Costs
- An extra component to run, secure and monitor
- Applications depend on a polling loop, and need safe defaults if the server is unreachable
- Rates take a short time to spread to every instance
- Support differs by language and by version
Remote samplers should always have a fallback, such as a modest ratio, for the time before the first policy arrives and for when the server cannot be reached. Otherwise, a service might start up keeping everything, or nothing, by accident.
13Consistent Probability Sampling: Making Rates Line Up
A newer idea that makes sampling easier to combine and easier to count.
The plain ratio sampler has two small weaknesses. First, if two services use different ratios, their decisions may not nest neatly. Second, once a trace is kept, it is hard for later tools to know how likely each trace was to be kept, which makes it difficult to scale counts back up and estimate true totals.
Consistent probability sampling addresses both. The idea is that the sampler records, inside the trace state, a small piece of information that describes the sampling probability that was used, often as a threshold value, and sometimes a randomness value. Every later service and tool can read that note.
Nesting Decisions
A trace kept at a lower rate is always also kept at a higher rate. Different services can use different rates without breaking each other’s traces more than necessary.
Honest Counting
Because each kept trace says how likely it was to be kept, tools can estimate real totals: if the probability was one in ten, each kept trace stands for ten.
Mixed Strategies
Different parts of a system can use different rates, and tools can still combine the results correctly.
Think of a survey company. If it surveys one in ten households in one town and one in fifty in another, the results must be weighted to compare the towns fairly. If every record carries a note saying “this household was chosen with one-in-ten odds,” the weighting is easy. The trace state note is that note.
This area is newer than the classic samplers. Support differs between languages, Collectors and backends, and details of the names and rules have been evolving. Check the current status for your versions before relying on it, and test that your backend reads the information correctly.
14The Public Edge: Who Should You Trust?
The sampled flag arrives in a request header. Anyone can write a header.
Inside your own network, trusting the incoming sampled flag is usually fine. But at the public edge, where requests arrive from the open internet, the trace header may have been written by anyone. A mischievous or careless caller could set the sampled flag to “on” on every request. If your front door simply obeys, you will record and store every one of those traces, and you will pay for them.
Using the five slots to protect the edge
This is exactly why the parent-based sampler has separate slots for remote parents. At your public edge, you can change what happens when the incoming parent is remote.
flowchart TD
A["Public request arrives with trace header"] --> B{"Is the parent remote?"}
B -->|Yes| C{"Is the incoming sampled flag on?"}
C -->|Yes| D["Remote parent sampled slot uses a ratio or rate limit, not always on"]
C -->|No| E["Remote parent not sampled slot stays off"]
B -->|No| F["Root sampler decides as usual"]
D --> G["Your own decision is stored in the new flag"]
E --> G
F --> G
| Where | Remote Parent Sampled Slot | Why |
|---|---|---|
| Internal service behind your gateway | Always on, the usual default | You trust your own front door, and you want whole traces |
| Public gateway or edge | A ratio sampler or a rate-limited sampler | Strangers should not be able to force unlimited recording |
| Partner-facing API | Always on for trusted partners, ratio for others | Trust can depend on who is calling |
Another option: start a fresh trace and link back
Some teams go further at the public edge. They ignore the incoming trace identity for sampling and parenting, start a brand new trace of their own, and record the caller’s trace identity as a link. This gives the caller a way to find the related trace, without letting them control your costs or trace structure.
Production Example: The Flag Flood
A public API suddenly saw its tracing bill triple. Investigation showed that one client library, used by a partner, always set the sampled flag on every request. Because the gateway’s remote-parent slot was set to always on, every request from that partner was fully recorded. Changing that one slot at the edge to a ratio sampler returned the bill to normal, without any change on the partner’s side.
15Samplers and the Other Signals
The trace sampler touches only traces, but its effects spread further than you might expect.
Metrics
Metrics recorded through the metrics API are not affected by the trace sampler. A counter of orders still counts every order. But metrics derived from stored traces, such as request counts computed from spans, will be too low in proportion to how many traces you dropped. To avoid this, record real metrics in the SDK, or compute span-based metrics in a step that sees every span, before any tail sampling in the Collector.
Exemplars
Exemplars are example trace IDs attached to metric points, so that you can jump from a graph spike to a real trace. By default, SDKs usually attach exemplars only from spans that were sampled, because linking to a trace that was never stored would lead nowhere. So the sampler also decides which metric points can carry an exemplar.
Logs
Log records written while a span is active often carry its trace ID and span ID. If the sampler dropped that span, the log still carries the IDs, but there is no stored trace to open. This is normal. It is one more reason to keep more traces for important routes, since a log line with a link to nowhere is frustrating when you need it most.
flowchart LR
S["Sampler decision on each span"] --> T["Traces: only sampled spans are exported"]
S --> E["Exemplars: usually only from sampled spans"]
S --> L["Logs: still carry IDs even when the trace is dropped"]
M["Metrics API"] --> MB["Metrics: not affected by trace sampling"]
Think of a newspaper that prints only some of the photos taken at an event. The caption list for the whole event, saying how many people attended, is still accurate, since it was counted at the gate. But a story that says “see photo on page 4” is a dead end if that photo was not printed. Metrics are the gate count. Exemplars and log links are the “see photo” notes.
16Sampler versus Collector Sampling: Different Jobs
Both reduce data. They work at different times, with different strengths.
| Question | Sampler in the SDK | Sampling in the Collector |
|---|---|---|
| When does it decide? | When a span starts | After spans arrive, often after the whole trace is complete |
| What does it know? | Only start-time facts | The full story, including errors and total time, if tail sampling is used |
| Can it keep all errors? | No | Yes, with a suitable policy |
| Does it reduce load on the application? | Yes, dropped spans are never recorded | No, the application has already recorded and sent everything |
| Cost to run | Almost nothing | Memory, delay and extra servers |
| Where is it configured? | In the application or agent settings | In the Collector configuration |
flowchart LR
A["Application with parent-based sampler"] -->|"Only sampled spans are sent"| C["Collector"]
C --> T["Tail sampling keeps errors and slow traces"]
T --> B["Backend"]
An important warning about combining them
If the SDK sampler drops a trace, the Collector never sees it, so tail sampling cannot rescue it. If you want the Collector to keep every error, the SDK must not drop that trace at the start. Teams that use tail sampling for errors often set the SDK to keep everything, or to drop only obvious noise such as health checks, and then let the Collector do the real reduction. The price is that applications must record and send everything that is not dropped.
Stacking a ten percent SDK sampler in front of a Collector policy that says “keep all errors” does not keep all errors. It keeps all errors among the ten percent that survived. Always do the arithmetic across both layers.
17Performance and Overhead
The sampler is on the path of every span, so it must be light.
A good sampler costs a tiny amount of time per span, usually a few simple comparisons. Even so, at very high volumes, small costs add up, so it is worth understanding where the savings and costs lie.
Dropped Spans Are Nearly Free
A non-recording span has almost no cost: no attributes are stored, no memory is held and nothing is exported.
The Decision Itself
Running a ratio check or a short list of rules takes a very small, steady amount of time per span.
Work Before the Decision
The facts passed to the sampler, such as attributes at start, must be created before the decision. Computing expensive attributes before sampling wastes effort when the span is dropped.
Custom Sampler Mistakes
A custom sampler that calls the network, reads files or locks shared state can slow every request.
Practical tips
- Keep attributes passed at span start small and cheap. Add heavy details after creation, only if the span is recording.
- Many APIs let code check whether a span is recording before preparing costly details. Use this.
- Test a custom sampler under load, with and without it, before rolling it out.
- Prefer simple, stateless rules over complicated shared counters.
18Advantages, Disadvantages and Trade-offs
An honest look at what an SDK sampler gives and what it costs.
Advantages
- Very cheap: dropped spans cost almost nothing
- Simple to run: no extra servers or memory needed
- Reduces load at the source: less CPU, memory and network in the application
- Consistent across services: the sampled flag shares one decision
- Flexible: built-in, rule-based, remote and custom options
- Standard: works across languages through common settings
Disadvantages
- Blind to outcome: cannot know about errors and slowness at the start
- Drops are permanent: a dropped trace cannot be recovered later
- Rare events may vanish: low rates can miss rare problems entirely
- Needs a restart to change: unless a remote sampler is used
- Counting needs care: numbers from sampled traces are estimates
- Easy to misconfigure: broken traces and surprising bills are common
Key trade-offs
| Decision | Option A | Option B |
|---|---|---|
| Keep rate | High, for detail and higher cost | Low, for lower cost and risk of missing rare cases |
| One rate or many | One rate, simple to explain | Rule-based rates, better value but more to maintain |
| Static or remote | Static settings, simple and predictable | Remote policy, flexible but with an extra moving part |
| Trust at the edge | Follow callers, for whole end-to-end traces | Treat callers as hints, for cost safety |
| Sampler only or with Collector | Sampler only, cheap and simple | Sampler plus tail sampling, smarter but heavier |
19Security and Privacy Considerations
Sampling saves money. It does not make telemetry safe by itself.
Forced Recording
Callers can set the sampled flag to make you record more. Use the remote-parent slots, ratios or rate limits at public edges.
Kept Traces Still Need Cleaning
The sampler removes whole traces, not sensitive values inside the ones it keeps. Redact personal data and secrets.
Rule Inputs Can Be Sensitive
If a custom rule looks at customer details or addresses, avoid logging those details in the sampler’s description or debug output.
Protect the Policy Server
Whoever can change a remote sampling policy can silence your tracing or flood your bill. Use authentication, encryption and access control.
Guard Forced-Keep Switches
A header or baggage value that forces tracing on is useful for engineers, but outsiders must not be able to use it. Check it only for trusted callers.
Retention Still Applies
Even a small sample may contain personal data. Apply retention and deletion rules to what you keep.
A common wrong idea is “we sample only one percent, so privacy risk is tiny.” One percent of a very large number is still a lot of records, and each may expose a real person’s data. Treat the traces you keep with the same care as any other personal data store.
20Monitoring, Logging and Troubleshooting
If you cannot see what the sampler is doing, you cannot trust your traces.
- Kept versus total: compare the number of spans or traces created with the number kept. The real share should match your plan.
- Share of complete traces: if many traces have only one or two spans, a service may not be using parent-based sampling.
- Per-service keep rates: check each service on its own. One service with a very different rate is a clue.
- Sampler description in debug output: turn on debug logging briefly and read the active sampler’s description at startup.
- Edge behaviour: watch the share of requests with the incoming sampled flag on, and compare with the share you actually keep.
- Cost trend: a sudden jump in trace volume after a release often points to a sampler setting that was lost or changed.
- Error coverage: compare error counts from metrics with error traces you hold. If the gap is large, head sampling is dropping most errors.
- Remote sampler health: if used, track fetch failures and the age of the policy each instance is using.
Common symptoms and likely causes
| Symptom | Likely Cause | What to Try |
|---|---|---|
| Traces have missing middle steps | Services sample independently, without parent-based wrapping | Use a parent-based sampler in every service |
| No traces appear | Always off, a very low ratio, or an unexpected setting | Check the sampler setting and ratio, and read the debug description |
| Bill is higher than planned | Default always-on sampler still in use, or remote parents forcing recording | Set a sampler on purpose and review the remote-parent slots at the edge |
| Every service keeps a different share | Different ratios or ignored arguments | Standardise settings and check the argument is actually read |
| A rule does not seem to work | The fact it uses is only known after the span starts | Use a fact available at start, or move the rule to the Collector |
| Important errors are missing | Head sampling dropped them before the error happened | Raise rates on critical routes, or add tail sampling with SDK keeping more |
| Setting change has no effect | The application was not restarted, or a different setting is overriding it | Restart and confirm with the debug description, or use a remote sampler |
21Design Patterns and Anti-Patterns
Habits worth copying and traps worth avoiding.
Good design patterns
One Owner of the Decision
Let the first service in each user request start traces and decide. Everyone else follows with parent-based sampling.
Cheap Noise Rules at the Door
Use a rule-based root sampler to drop health checks and similar noise right at the start.
Edge Trust Policy
Configure remote-parent slots at public edges so outsiders cannot force unlimited recording.
Higher Rates for Quiet Services
Keep all or most traces on low-traffic services and sample heavily only where volume is large.
Layered Reduction
Light SDK sampling for noise, and Collector policies for errors and slow traces.
Central Policy
Manage sampler settings through templates, the Operator or a remote policy server, not by hand in each service.
Anti-patterns
The Mistake
Using a plain ratio sampler in every service, without a parent-based wrapper.
Why It Hurts
Each service decides alone. Any small difference in ratio or algorithm creates partial traces.
Better Approach
Wrap the ratio sampler in a parent-based sampler, and let only the front door roll the dice.
The Mistake
Writing a rule that depends on the response code or an error.
Why It Hurts
A sampler decides before those facts exist, so the rule never triggers or behaves randomly.
Better Approach
Use start-time facts only in the sampler, and use Collector tail sampling for outcome-based rules.
The Mistake
Trusting the sampled flag from the public internet.
Why It Hurts
Anyone can force you to record and pay for as many traces as they like.
Better Approach
Treat remote sampled parents as hints at the edge, using ratio or rate-limited slots, or start a fresh trace and link back.
The Mistake
A custom sampler that calls a database or the network on each decision.
Why It Hurts
It adds delay to every span, and a slow or broken dependency can slow down the whole application.
Better Approach
Keep decisions local and instant. If you need outside data, fetch it in the background and cache it.
The Mistake
A very low rate on a low-traffic service.
Why It Hurts
You keep almost nothing, so when something goes wrong, there is no trace to look at.
Better Approach
Keep all or most traces for low-traffic services, and sample heavily only where volume demands it.
The Mistake
Counting traffic or errors from stored traces after sampling, with no scaling.
Why It Hurts
Numbers come out far too small, leading to wrong dashboards and wrong alerts.
Better Approach
Record real metrics in the SDK, or compute them before sampling, and treat trace-based counts as estimates.
22Best Practices and Common Mistakes
A practical checklist for a sampler you can trust.
Best practices
- Choose on purpose. Do not leave the default on a busy production system without deciding.
- Always use a parent-based sampler. It is the simplest way to keep traces whole.
- Pick a single decision owner. One front door sets the rate. Everyone else follows.
- Drop noise early. Use rules for health checks and other low-value traffic.
- Raise rates for what matters. Checkout, payments and sign-in are worth more than static pages.
- Protect the edge. Set remote-parent slots so that outsiders cannot force recording.
- Keep it fast and simple. Custom samplers should be quick, stateless and well tested.
- Check the arithmetic across layers. If you also use tail sampling, work out what survives the SDK first.
- Measure kept versus total. Confirm the real keep rate matches your plan.
- Document the policy. Write down the rates, the rules and the reasons, so people know why a trace may be missing.
- Review regularly. Traffic changes, and so should your rates.
Common mistakes beginners make
- Believing a ten percent rate means ten percent of problems are visible. Random sampling may miss rare errors completely.
- Using the ratio sampler alone and expecting it to follow the parent.
- Confusing a sampler with a span processor, or with Collector sampling.
- Writing rules that depend on facts not known at the start.
- Mixing up “recording” and “sampled.”
- Forgetting that changing environment variables requires a restart.
- Not realising that a dropped trace is gone forever.
- Assuming that sampling removes privacy risk.
23Real-World and Industry Examples
How samplers show up in tools and platforms you may already know.
Google Dapper
Google’s early tracing system, described in a well-known research paper, kept only a tiny fraction of requests on its busiest services. It showed that even a very low sampling rate reveals useful patterns when traffic is large, and it influenced the idea of deciding early, with a simple rule, at the front door.
Jaeger
Jaeger, which began at Uber and is now a CNCF project, popularised remote sampling. Applications fetch sampling strategies for each service and operation from a central place, and Jaeger offers adaptive strategies that adjust rates with traffic. OpenTelemetry SDKs include a sampler that can talk to this system.
AWS X-Ray
AWS X-Ray lets you define sampling rules with a small guaranteed number of traces per second and a percentage of the rest, so every route gets some visibility while total volume stays under control. Remote sampler implementations for OpenTelemetry can read these rules.
The Default Behaviour of OpenTelemetry SDKs
Across most languages, the standard default is a parent-based sampler with always-on as its root. Many teams run this in production for months and only notice the cost when traffic grows. The most common first improvement is a single settings change to parent-based with a ratio.
Honeycomb and Tail-Sampling Proxies
Honeycomb’s Refinery and similar proxies do tail-based sampling after traces are complete. Teams often pair them with a light head sampler in the application to drop obvious noise, giving a good example of the layered approach: cheap decisions first, smart decisions later.
A typical journey for a growing company
Keep Everything at First
With low traffic, use the default parent-based always-on setup and learn what traces look like.
Switch to a Parent-Based Ratio
As volume and cost grow, set parent-based sampling with a ratio at the front door, and let everything else follow.
Add Rules
Drop health checks and other noise, keep all traces for the most valuable routes and lower the rate for the rest.
Protect the Edge and Centralise
Set remote-parent policy for public gateways, and manage policy through templates, the Operator or a remote policy server.
Add Tail Sampling Where Needed
Keep errors and slow traces with Collector policies, and review the combined arithmetic of both layers.
24Frequently Asked Questions
Quick answers to questions beginners ask most.
25Summary and Key Takeaways
If you remember only a few things, remember these.
Key Takeaways
- A sampler is the doorman for your spans. It lives in the SDK, runs once for each new span, and decides whether to drop it, record it, or record and sample it, using only facts known at the start.
- Dropped spans are nearly free. A dropped span becomes a non-recording span that costs almost nothing but still carries the trace identity, so the decision can be shared.
- Always on and always off are the simple ends. The ratio sampler is a fair lottery based on the trace ID, but on its own it ignores the parent.
- Parent-based is the one to use. It follows the caller’s decision through five slots, so one decision at the front door is shared by every service and traces stay whole.
- Samplers cannot see the outcome. They cannot keep errors or slow requests on purpose. Use Collector tail sampling for that, and do the arithmetic across both layers.
- Treat the edge with care. The incoming sampled flag is a hint from a stranger, so set the remote-parent slots at public edges to protect your costs.
- Choose, measure and secure. Pick a sampler on purpose through standard settings, watch kept versus total, protect metrics accuracy, and remember that sampling does not remove sensitive data from the traces you keep.