Trade-offs in Sampling Strategy Design at Scale in OpenTelemetry

Trade-offs in Sampling Strategy Design at Scale in OpenTelemetry

Every request can leave a trail of data behind it. At a million requests per second, keeping all of it is impossible and keeping too little is dangerous. This guide explains how to choose what to keep, where to decide, and what you give up with each choice.

Picture a busy airport that photographs every single passenger at every single door, every single minute. After one day the storage room is full, the security team cannot watch the footage, and the bill for cameras is bigger than the bill for planes. Now picture the opposite airport, where cameras are switched off to save money, and a serious incident happens with no footage at all. Both airports made a bad design choice. Good design lives somewhere in between.

Software systems face the same problem. Every request that enters a modern application can produce a detailed record of everything it touched. This record is called a trace. Keeping every trace is wonderfully complete and terribly expensive. Keeping too few traces is cheap and leaves you blind during an outage. The craft of deciding which traces to keep is called sampling, and in OpenTelemetry (the open standard for collecting this kind of data) it is one of the most important and most misunderstood design areas.

This article walks you through the whole topic from zero. You will learn what sampling is, how OpenTelemetry carries a sampling decision from one service to another, the two big families of strategies, and the trade-offs that appear when traffic grows to millions of requests. Every term is explained when it first appears, and every big idea comes with an everyday comparison.

1Why We Sample: The Problem of Too Much Data

Before choosing a sampling strategy, you need to feel the size of the problem. The numbers grow faster than most people expect.

What a trace is, in plain words

When a customer taps “Place Order” in a shopping app, that one tap may touch a dozen services. One service checks the login, another reads the cart, another charges the card, another reserves stock, another sends an email. A trace is the full story of that single request as it travels through all those services. Each step inside the story is called a span. A span records what happened, how long it took, and whether it succeeded or failed.

Traces are powerful because they answer the hardest question in distributed systems: “Why was this one request slow, or why did it fail?” Logs tell you what one service saw. Metrics tell you averages. Only a trace shows the whole journey in one picture.

The arithmetic of volume

Now do the math for a large platform. Suppose it handles one million requests every second, and each request creates thirty spans, and each span is about five hundred bytes when sent over the network. These are illustrative round numbers, but they are realistic for big consumer platforms.

30 million
spans created every second
(1M requests x 30 spans)
15 GB/s
raw span data leaving
applications every second
1.3 PB
per day if everything
is stored

A petabyte is one thousand terabytes. At 1.3 petabytes per day, the storage alone would cost a fortune, the network links between data centers would be crowded with telemetry, and no human could ever search through it quickly. If you keep only one request in a hundred, the same system produces about thirteen terabytes per day. That is still big, but it is the kind of big that a real company can afford.

Everyday Analogy

A factory makes ten thousand light bulbs an hour. The quality inspector cannot open every box. Instead, she pulls a handful from each batch and tests them. If the handful is chosen well, it tells her the truth about the whole batch. Sampling in telemetry is the same idea: test a well-chosen handful, learn about the whole.

The hidden costs beyond storage

Storage is the cost people notice first, but it is rarely the only one. Every span has to be created, filled with attributes, converted into a wire format, and sent. That uses CPU and memory inside your application. The data then crosses the network, and in cloud environments, data that crosses between availability zones (separate data center buildings inside one region) is billed per gigabyte. The data then lands in a collector, which uses more CPU and memory. Finally it is indexed in a backend, which uses even more compute, and every search over it costs money as well.

There is also a human cost. When a dashboard shows ten million traces, nobody can find the one that matters. A smaller, well-chosen set is easier to explore and easier to trust.

Sampling is not the same as filtering or aggregation

Three different tools reduce telemetry, and beginners often mix them up. Filtering drops data by a fixed rule, for example never recording the health-check requests that load balancers send every few seconds. Aggregation squeezes many events into a few numbers, such as counting requests per minute. Sampling keeps a subset of complete, detailed events and drops the rest. A mature design uses all three, each where it fits best.

i
Production Example

Google described its internal tracing system, Dapper, in a well-known research paper. In its early days it kept roughly one request out of every 1,024. Because Google serves enormous traffic, even that tiny fraction produced plenty of traces to find common problems. The lesson is that at huge scale, a very small sample can still be useful, but a sample that small can miss rare events.

Keep that last sentence in mind. The rest of this guide is really about one question: how do you shrink the data by 99 percent and still keep the traces you will wish you had during the worst night of the year?

2Core Concepts: Traces, Spans, and Decisions

A short vocabulary lesson. Once these words are clear, everything else in the guide becomes easy to follow.

Data

Trace

The complete story of one request across all services. It is identified by a unique trace ID, a long random number shared by every span in the story.

Data

Span

One step inside a trace, such as “call the payment service”. It has a start time, a duration, a status, and attributes (labels like the customer region).

Data

Span Context

The small identity card a span carries: trace ID, span ID, and flags. It travels with the request so the next service can join the same story.

Component

SDK

The OpenTelemetry library inside your application code. It creates spans, applies the sampler, and sends data out.

Component

Collector

A separate program that receives telemetry, processes it, and forwards it to a backend. Think of it as a sorting office between your apps and storage.

Component

Sampler

The small piece of logic that answers one question for each new trace: keep it or drop it?

Propagation: how services share a story

For many services to contribute to one trace, they must agree on the trace ID. The first service creates it, and every call it makes to another service carries it along inside an HTTP header (a small note attached to a web request). Passing this note from service to service is called context propagation. The standard format for the note is defined by the W3C, the same organization that standardizes the web, and the header is named traceparent.

The sampling decision

A sampling decision is the verdict for a trace: keep or drop. The sampling rate (or probability) is the fraction kept. A rate of 10 percent means one trace in ten is kept on average. Two timing choices exist for when the verdict is made.

  • Head sampling decides at the very beginning of the trace, when the first span is created, before anyone knows how the request will turn out.
  • Tail sampling decides at the end, after most or all spans have arrived, so the verdict can depend on what actually happened.
Everyday Analogy

Head sampling is a bouncer at a club door who lets in every tenth person without asking anything. Tail sampling is a talent scout who watches the entire show first and then decides which performers to sign. The bouncer is fast and cheap. The scout is smarter but needs a seat, a notebook, and time.

Three outcomes inside the SDK

Inside the SDK, a sampler returns one of three answers. Drop means the span is not recorded at all. Record only means the span is recorded locally, so processors can look at it, but it is not marked as sampled and is normally not exported. Record and sample means the span is recorded, marked as sampled, and exported. Most people only ever deal with drop and record and sample, but the middle option exists for special cases such as computing local metrics from spans.

One more subtle point: even when a span is dropped, the SDK still creates a lightweight placeholder with a valid span context. That placeholder lets the trace ID and the “not sampled” verdict continue travelling to downstream services. Dropping is cheap, but it is not absolutely free.

3How OpenTelemetry Carries a Decision

Understanding the mechanics explains why some designs work smoothly and others produce broken, half-empty traces.

The sampled flag in the traceparent header

The traceparent header has four parts separated by dashes: a version number, the trace ID, the ID of the parent span, and a set of trace flags. One bit inside those flags is the sampled flag. When it is on, it tells every downstream service “the head of this trace decided to keep it”. When it is off, it says “this trace was dropped”. A second header, tracestate, carries optional extra key-value pairs that individual vendors or tools can use.

Everyday Analogy

At a concert, the person at the door stamps your wrist once. Every guard inside only looks at the stamp. Nobody inside re-decides whether you belong. The sampled flag is that stamp. It makes the first decision stick for the whole evening.

The built-in samplers

OpenTelemetry SDKs ship with a small set of samplers that you combine like building blocks.

SamplerWhat it doesTypical use
Always OnKeeps every traceDevelopment, low-traffic systems, or when a later stage will sample
Always OffDrops every traceTurning tracing off without removing code
Trace ID Ratio BasedKeeps a fixed fraction, chosen by looking at the trace IDSimple percentage sampling at the head
Parent BasedObeys the sampled flag if a parent exists, and uses a separate “root” sampler if notThe standard wrapper that keeps whole traces together

The parent-based sampler is the most important one to understand. If a request arrives with a parent that was sampled, the new span is kept. If a request arrives with a parent that was not sampled, the new span is dropped. Only when a request has no parent at all (a brand-new trace) does the root sampler get to make a fresh choice. The default in OpenTelemetry SDKs is parent-based with always-on as the root, which means that, out of the box, nothing is sampled away. You only start dropping data when you configure it, usually through environment variables that name a sampler and an optional ratio.

flowchart LR
    A["Incoming request with traceparent header"] --> B{"Parent span context present?"}
    B -- "No" --> C["Root sampler decides"]
    B -- "Yes, sampled flag on" --> D["Keep: record and export span"]
    B -- "Yes, sampled flag off" --> E["Drop: do not record span"]
    C -- "Keep" --> D
    C -- "Drop" --> E
    D --> F["Child calls inherit the decision and forward the header"]
    E --> F
Figure 1: How a parent-based sampler turns one early decision into a whole-trace decision.

Read the diagram from left to right. A request arrives. If it already belongs to a trace, the service simply follows the flag. If it is the start of a trace, the root sampler rolls the dice once. Whatever the outcome, the decision is written into the header and forwarded, so every later service repeats the same verdict without any coordination or shared database.

Why the ratio sampler needs care

The ratio-based sampler looks at the trace ID, which is a random number, and keeps the trace if that number falls below a threshold. Because the decision depends only on the trace ID, two services using the same ratio will reach the same answer for the same trace. That is the idea of a consistent decision. The trouble is that early versions of the specification did not describe the exact calculation, so different language SDKs could disagree on edge cases. Newer specification work defines explicit randomness and threshold values that can be carried in the trace state, making decisions comparable across languages and across services that use different rates. Check what your SDK versions support before relying on cross-service consistency.

!
Common Trap

If one service uses a plain ratio sampler as its root while its neighbors use the parent-based wrapper, or if every service ignores the incoming flag and samples independently at 10 percent, the chance that all ten services keep the same trace is a tiny fraction of one percent. You get traces with big holes in them. Always wrap probability samplers in a parent-based sampler unless you have a deliberate reason not to.

4Head Sampling: Deciding at the Start

Head sampling is the simplest, cheapest, and most widely used approach. Its weakness is that it decides while knowing almost nothing.

How it works

When the first span of a trace is created, usually at an edge service such as an API gateway (the front door that receives traffic from outside), the sampler is asked for a verdict. It sees only a small amount of information: the span name, its kind, some initial attributes, and the trace ID. It cannot see whether the request will fail, how long it will take, or whether it belongs to an important customer. After the verdict, it writes the sampled flag into the outgoing header, and every downstream service follows.

The head sampler must predict the future with only the first page of the story.

Why teams love it

The savings begin at the source. A dropped trace creates no full spans, no attribute data, no network traffic, and no collector work. The sampler is stateless, meaning it remembers nothing between requests, so it needs no memory or coordination and scales perfectly. If you double your servers, you double your sampling capacity without thinking about it. Operationally it is the easiest design to run, debug, and explain.

Why teams outgrow it

The blind spot is outcomes. If you keep one percent of traces at random, you also keep only one percent of the failing ones, and one percent of the slow ones. The very traces you most want are treated exactly like the boring ones.

Scale changes this picture in an interesting way. Suppose a platform handles one million requests per second and one in a thousand fails. That is one thousand failures every second, and a one percent head sample still keeps ten failing traces every second, which is plenty to spot a common bug. The danger is for rare problems. A bug that hits one request in ten million happens once every ten seconds in absolute terms, but a one percent sample catches it once every thousand seconds. And for a smaller, quieter service with perhaps ten requests per second, a one percent rate keeps one trace every ten seconds, which may be too little to learn anything. The same percentage behaves very differently at different traffic levels, so one global rate is almost always a mistake.

Advantages

  • Lowest cost: savings happen before data is created or sent
  • Stateless, so it scales horizontally with no coordination
  • Simple to configure, test, and reason about
  • Decision travels with the request, keeping traces whole
  • Predictable, steady data volume that is easy to budget

Drawbacks

  • Blind to errors, slowness, and customer importance
  • Rare problems are sampled away along with everything else
  • Changing the rate usually needs a config change or restart
  • Statistics from the kept sample need weighting to be accurate
  • A wrong rate is expensive to discover after the fact

Making head sampling a little smarter

Although the head sampler is blind to outcomes, it is not blind to everything. You can write a custom sampler that looks at the attributes available at span creation. For example, it can keep all traces for internal test accounts, keep a higher fraction for a new feature that was just released, and keep a tiny fraction for noisy, low-value endpoints. Some teams also use a remote sampling configuration, where a central service tells each SDK what rate to use per service or per endpoint, so rates can change without redeploying code. This costs very little at runtime and improves usefulness a lot, but it still cannot see the outcome.

i
Production Example

Envoy, the proxy created at Lyft and now used by many companies at the edge of their networks, exposes percentage-based settings for how often it starts a trace. Operators commonly dial this to a low percentage for the busiest public routes. This is head sampling in its purest form: the front door decides, and everything behind it obeys.

5Tail Sampling: Deciding at the End

Tail sampling trades money and complexity for intelligence. It lets you say “keep every trace that failed” or “keep every trace slower than two seconds”.

How it works

In tail sampling, applications record and send all spans, usually to an OpenTelemetry Collector running a component called the tail sampling processor. The processor groups incoming spans by trace ID and holds them in memory. It starts a timer for each new trace. When the timer ends, called the decision wait, it assumes the trace is complete enough and runs a list of policies over it. If any policy says keep, the whole trace is passed on to the backend. If none do, the trace is discarded.

Everyday Analogy

A restaurant critic does not decide whether a meal deserves a review after seeing the menu. She waits until the dessert plates are cleared. Tail sampling waits for the whole meal. The price of that wisdom is that someone has to keep the table reserved the entire time.

The kinds of policies

Outcome

Status code

Keep traces that contain an error status. The most popular policy because failures are what people investigate.

Outcome

Latency

Keep traces whose total duration passes a threshold, such as two seconds, so slow requests are never lost.

Content

Attribute match

Keep traces with a given text or number attribute, for example a premium customer tier or a specific region.

Volume

Probabilistic

Keep a random percentage of everything else, giving a baseline picture of normal behavior.

Volume

Rate limiting

Keep at most a fixed number of spans per second so a surge cannot flood the backend.

Logic

And, composite, and condition policies

Combine policies, give each a budget, or write custom matching rules, so you can say “errors, but capped at a fixed budget”.

Policies are evaluated together, and a trace is kept if any policy votes to keep it, unless you build combinations that say otherwise. A common recipe is: keep all errors, keep all slow traces, keep all traces for a few special customers, and keep a small random baseline of everything else.

sequenceDiagram
    participant SvcA as Service A
    participant SvcB as Service B
    participant TS as Tail Sampling Collector
    participant BE as Tracing Backend
    SvcA->>TS: Span 1 of trace T1
    SvcB->>TS: Span 2 of trace T1 with error status
    Note over TS: Buffer spans in memory and start the decision wait timer
    SvcB->>TS: Span 3 of trace T1
    Note over TS: Timer ends, evaluate every policy on the full buffered trace
    TS->>BE: Keep trace T1 because the error policy matched
    SvcA->>TS: Late span 4 of trace T1
    TS->>BE: Forward late span using the remembered decision
Figure 2: A tail sampling collector buffers spans, waits, decides, and then handles stragglers.

The diagram shows the life of one trace. Spans from different services arrive at different moments. The collector keeps them together and does nothing until the timer ends. Then it makes the verdict with full knowledge. The last two lines show a real-world nuisance: a span that arrives after the decision. Collectors can remember recent verdicts in a small cache so late spans follow the same fate, but that memory is limited, and very late spans may be treated as new, incomplete traces.

The memory bill

Buffering is the heart of the cost. Consider a stream of 100,000 new traces per second with a decision wait of 30 seconds. At any moment about three million traces are waiting in memory. With thirty spans per trace and five hundred bytes per span on the wire, that is roughly 45 gigabytes of raw data, and the in-memory form of spans is often several times larger than their wire form. This memory must be spread across many collector machines, and the plan must survive restarts and traffic spikes.

Advantages

  • Keeps the traces people actually need: errors, slow requests, important customers
  • Policies can be changed centrally without touching application code
  • Can combine a smart keep-list with a small random baseline
  • Applications stay simple: they always record and send

Drawbacks

  • Stateful and memory-hungry, so it needs careful capacity planning
  • All spans of one trace must reach the same collector instance
  • Applications still pay the cost of creating and sending every span
  • Traces appear in the backend only after the decision wait
  • Late spans and very long traces can be cut or fragmented
!
Common Trap

Tail sampling does not reduce the cost of creating or shipping spans. It only reduces what you store. If your biggest bill is network traffic between zones or the CPU inside your applications, tail sampling alone will not fix it. That is why large systems often combine ideas, which the next chapters explain.

6Architecture: Tail Sampling at Scale

One collector can tail sample a small system. A large system needs a fleet, and a fleet creates a new puzzle: how do all spans of one trace end up in the same place?

The routing puzzle

Imagine a trace that touches five services. Each service runs on many servers, and each server sends its spans to whichever collector is nearest or least busy. Without special care, the five spans of one trace land on five different collectors. Each collector sees only a fragment, so none of them can judge whether the trace contained an error or was slow. The decision becomes meaningless.

The answer is a two-layer design. The first layer receives everything and forwards each span to a second layer using the trace ID as the routing key. A rule such as “trace ID modulo number of machines” guarantees that every span with the same trace ID reaches the same second-layer machine. In OpenTelemetry, a component called the load balancing exporter does this job, and it can discover the second-layer machines through a fixed list, DNS records, Kubernetes service discovery, or AWS Cloud Map.

Everyday Analogy

A big post office receives letters addressed to thousands of streets. A first sorting hall reads the postal code and drops each letter into the right regional bin. Only then does a regional clerk, who now sees every letter for her region together, make decisions about them. The first hall does not think. It only routes. That is the first collector layer.

flowchart TB
    subgraph Apps["Application Tier"]
        S1["Service A with SDK"]
        S2["Service B with SDK"]
        S3["Service C with SDK"]
    end
    subgraph Agents["Tier 0: Agent Collectors on each node"]
        AG1["Agent Collector 1"]
        AG2["Agent Collector 2"]
    end
    NLB["Internal Network Load Balancer"]
    subgraph T1["Tier 1: Routing Collectors"]
        R1["Routing Collector A with load balancing exporter"]
        R2["Routing Collector B with load balancing exporter"]
    end
    subgraph T2["Tier 2: Tail Sampling Collectors"]
        TS1["Tail Sampler 1"]
        TS2["Tail Sampler 2"]
        TS3["Tail Sampler 3"]
    end
    BE["Tracing Backend and Storage"]
    MB["Metrics Backend"]
    S1 --> AG1
    S2 --> AG1
    S3 --> AG2
    AG1 --> NLB
    AG2 --> NLB
    NLB --> R1
    NLB --> R2
    R1 -->|"route by trace ID hash"| TS1
    R1 -->|"route by trace ID hash"| TS2
    R2 -->|"route by trace ID hash"| TS2
    R2 -->|"route by trace ID hash"| TS3
    R1 -->|"request, error, duration metrics from all spans"| MB
    R2 -->|"request, error, duration metrics from all spans"| MB
    TS1 --> BE
    TS2 --> BE
    TS3 --> BE
Figure 3: A common three-tier collector design. Metrics are made before sampling; traces are kept after it.

Walk through the figure from top to bottom. Applications send spans to a small agent collector on their own machine, which absorbs bursts and adds helpful labels such as the machine name. Agents forward to a network load balancer that spreads work across the routing collectors. Routing collectors do the trace-ID routing and also create metrics from every span before any dropping happens. Tail sampling collectors hold the buffers, apply policies, and send the winners to the backend.

The trade-offs hidden inside this design

Cost

More machines

Two or three collector layers mean more servers to pay for and patch. At very large scale, the fleet itself is a notable line item.

Network

Extra hops

Every span crosses the network several times before a decision. If layers sit in different zones, transfer charges multiply.

Resize

Hash ring changes

When a tail sampler is added or removed, the routing math changes, and in-flight traces get split across old and new owners, producing a burst of half-traces.

Skew

Hot spots

One enormous trace, such as a batch job with a million spans, lands on a single machine and can exhaust its memory.

Failure

Lost buffers

If a tail sampler crashes, every trace in its memory vanishes with it, including the ones that would have been kept.

Delay

Visibility lag

Traces show up in the backend only after the decision wait plus transit time, which slows live debugging.

i
Tip

Place the memory limiter first in every collector pipeline, so a surge makes the collector refuse data politely instead of crashing. Put the tail sampling step after processors that add context the policies depend on, and put batching after sampling so batches contain only the traces worth sending.

7Probability, Consistency, and Weighting

Random sampling sounds easy until two services use different rates, or until someone tries to count requests from a sample.

Consistent probability sampling

Suppose service A samples at 50 percent and service B at 10 percent. If each makes independent random choices, then B will keep roughly 10 percent of the traces that A kept, and the combined result is a trace that exists in A but is missing in B most of the time. A consistent design fixes this by using one shared random number per trace, and each service compares that number against its own threshold. A trace kept at 10 percent is then guaranteed to also be kept by any service sampling at 50 percent. Lower-rate decisions become subsets of higher-rate decisions, so traces never have holes caused by mismatched rates.

OpenTelemetry’s newer probability sampling specification works this way. It defines a randomness value (derived from the trace ID or carried explicitly) and a threshold value, and it records the threshold in the trace state so that anyone reading a span can tell how likely that span was to be kept. The collector’s probabilistic sampler processor also offers modes that implement these ideas. Support differs by language and version, so treat this area as one to verify during upgrades.

Everyday Analogy

Everyone in a school gets a lottery ticket number from 0 to 99 when they enroll. The art club says “numbers under 10 may join”, and the sports club says “numbers under 50 may join”. Because both clubs use the same number, every art club member is automatically eligible for sports. If each club drew its own fresh random number, the lists would overlap only by chance.

Why weights matter

Here is a subtle but critical point. If you keep 1 trace in 100, then each kept trace stands for about 100 real ones. If you later count the kept traces and report “we served 5,000 requests”, you have under-reported by a factor of one hundred. The number you want is the count multiplied by the inverse of the probability, called the adjusted count. When the rate varies, each trace carries its own weight, and totals are computed by adding weights, not by counting rows.

This becomes more important with biased sampling. If you keep all errors but only 1 percent of successes, then your kept data contains far more errors than reality. Anyone who computes an “error rate” from the kept traces will see a number that looks catastrophic. Correct answers need either the stored weights or metrics computed before sampling.

Sampling styleRepresentative of real traffic?Good forNeeds weights?
Uniform randomYes, on averageEstimating typical latency and request mixSimple: one weight for all
Outcome-biased (errors and slow traces kept)No, over-represents troubleDebugging specific failuresYes, different weights per class
Per-service or per-customer ratesNo, deliberately skewedFairness and budget controlYes, one weight per group
Hybrid baseline plus biased extrasBaseline part onlyBoth debugging and statisticsYes, and the baseline must be identifiable
i
Production Example

Honeycomb’s tail sampling proxy, Refinery, attaches a sample rate to every kept event, and Honeycomb’s query engine multiplies by that rate when computing counts. This is the weighting idea running in production: the data can be heavily sampled, yet the totals shown on a chart still approximate reality.

8Keeping Metrics Honest

The most common sampling disaster is not a missed trace. It is a dashboard that quietly lies because it was built from sampled data.

The lying dashboard

Many teams build their first charts, such as requests per second, error percentage, and 99th percentile latency (the time that 99 out of 100 requests beat), directly from stored spans. This works fine while every span is kept. The day sampling is switched on, every number changes. Request counts fall by 99 percent, the error rate jumps if errors are favored, and percentile latencies shift because slow traces are over-represented. Alerts that used to be accurate turn noisy or silent overnight.

The fix: measure first, sample second

The robust design is to calculate metrics from the complete stream of spans before any dropping happens. OpenTelemetry provides a component called the span metrics connector, which watches spans as they pass through a collector and produces counters and histograms for request count, error count, and duration, grouped by service, operation, and status. Because it runs in the early, routing layer of the collector fleet, it sees all the data, and its numbers stay exact no matter how aggressively traces are sampled later. Traces then become the place you go to explain a number, and metrics become the place you go to see it.

Everyday Analogy

A toll booth counts every car that passes with a simple clicker, but only photographs one car in fifty. The clicker gives an exact count of traffic. The photographs give detail about particular cars. Nobody estimates the traffic count by counting photographs.

Advantages of metrics before sampling

  • Accurate rates, errors, and latency regardless of sampling
  • Alerts stay stable when sampling rules change
  • Trace volume can be cut harder without losing visibility
  • Cheap: metrics are small compared with traces

Drawbacks and cautions

  • The routing layer must process every span, so it needs real CPU
  • Choosing metric labels carelessly creates cardinality explosions
  • Latency histograms need sensible bucket boundaries
  • Spans dropped at the head by the SDK never reach this layer
!
Common Trap

If the SDK already drops 90 percent of spans through head sampling, then a span-based metrics component downstream sees only 10 percent of the traffic. In that case, either generate the metrics inside the application using regular metric instruments, or scale the counts by the known rate. Do not assume that “downstream sees everything” when an upstream stage is dropping data.

Cardinality deserves its own warning. Cardinality is the number of different label combinations a metric can have. If you attach a customer ID or a full web address with random numbers in it as a label, each new value creates a new time series, and the metrics backend can run out of memory or send you a shocking bill. Metrics labels should be few and bounded: service, operation name, status class, and perhaps a region.

9Adaptive, Rate-Limited, and Dynamic Strategies

A fixed percentage is easy to explain but terrible at handling traffic that rises, falls, and spikes. Dynamic strategies aim for a steady amount of data instead of a steady percentage.

Why fixed percentages struggle

Say you fix the rate at 1 percent. On a quiet night, you keep a trickle. On a sales day with ten times the traffic, you suddenly store ten times the data, and the bill and the backend load rise with it. Meanwhile an obscure endpoint called twenty times a day gets no traces at all, because one percent of twenty is nearly nothing. Percentages follow traffic, but budgets and learning needs do not.

Target-rate sampling

The alternative is to say “keep about N traces per second per service or per endpoint”. A busy endpoint gets a tiny probability so it stays near N. A quiet endpoint gets a probability near 100 percent so it also reaches N or as close as possible. This is called rate-limited or adaptive sampling. It shares the budget fairly across endpoints, and it guarantees that quiet but important paths always have some traces.

flowchart LR
    A["Collectors count spans per service and operation"] --> B["Strategy calculator compares counts with the target rate"]
    B --> C["New sampling probabilities are published"]
    C --> D["SDKs poll for the strategy and apply new probabilities"]
    D --> A
Figure 4: The feedback loop behind adaptive sampling. Each cycle nudges probabilities toward the target.

The loop works like a thermostat. Sensors count what is being kept, a calculator compares it with the goal, new settings go out, and the next round of measurements shows whether the room has warmed up. The Jaeger tracing project, which originated at Uber, popularized this pattern: its SDKs can poll a central strategy endpoint, and its adaptive mode calculates per-service, per-operation probabilities from observed throughput. OpenTelemetry SDKs can use a remote sampler in this style, and AWS X-Ray has its own version, where a rule gives a reservoir of one trace per second plus a fixed percentage of further requests (five percent by default). Amazon’s distribution of OpenTelemetry can talk to this X-Ray rule system.

Everyday Analogy

A cafeteria gives each table a fixed number of free samples per hour, not a fixed percentage of whatever the kitchen cooks. Busy tables do not drain the supply, and quiet tables still get a taste. The manager adjusts the numbers every few minutes by watching what is left on the trays.

The price of dynamic behavior

Advantages

  • Backend load and bill stay near a predictable budget
  • Rare endpoints are never starved of traces
  • Rates can change at runtime without redeploying applications
  • Natural protection during traffic spikes

Drawbacks

  • More moving parts: a strategy service, polling clients, and a feedback loop
  • Probabilities change constantly, so weights must travel with every trace
  • Harder to debug: “why was this trace dropped?” has a time-dependent answer
  • Feedback loops can oscillate if tuned badly, and clients may run on stale settings if the strategy service is unreachable

Rate limiting as a safety net inside tail sampling

Dynamic thinking also matters inside tail sampling. A policy like “keep every error” is wonderful on a normal day and dangerous in an outage, because when a database fails, ninety percent of traces become errors, and the “keep all errors” rule floods the backend at exactly the moment you need it to stay healthy. The remedy is to give the error policy a budget, for example by combining it with a rate limit through a composite policy, so errors get priority but cannot exceed a ceiling. A partial view of a disaster that you can actually query beats a complete view that crashes your backend.

10The Big Trade-off Map

Every sampling design is a bundle of compromises. This chapter lays them out side by side so you can see which dials you are really turning.

The eleven dials

Trade-off dialOne extremeThe other extremeWhat decides the right spot
1. Cost versus completenessKeep everything: perfect detail, huge billKeep almost nothing: cheap, blindBudget, incident cost, regulation
2. Early versus late decisionHead: cheap, blind to outcomesTail: smart, expensive, statefulHow much you need to catch rare failures
3. Stateless versus statefulStateless SDK sampler: trivial to scaleStateful collector buffers: capacity planningTeam skills and operational maturity
4. Consistency versus independenceParent-based: whole traces, one decisionIndependent per service: flexible, broken tracesWhether people read cross-service traces
5. Representative versus interestingUniform: honest statistics, misses raritiesBiased: great debugging, skewed statisticsWhether weights and pre-sampling metrics exist
6. Metric accuracy versus savingsMetrics from sampled spans: wrongMetrics before sampling: exact, needs capacityWhere in the pipeline metrics are created
7. Freshness versus intelligenceHead: traces visible at onceTail: visible after the decision waitHow live your debugging needs to be
8. Static versus dynamic ratesFixed config: predictable and debuggableAdaptive: steady budget, complexTraffic variability and fairness needs
9. Simplicity versus resilienceOne collector tier: easyMulti-tier with replicas: survives failuresCost of losing data during outages
10. Openness versus convenienceOpenTelemetry components: portableVendor sampling features: easy, locked inExit strategy and in-house skills
11. Trust versus controlObey every incoming sampled flagRe-decide at the edge, ignore outsidersWhether untrusted callers can reach your services

Notice that dials interact. Choosing tail sampling (dial 2) forces you into stateful collectors (dial 3), makes metrics-before-sampling much more attractive (dial 6), and adds delay (dial 7). Choosing adaptive rates (dial 8) makes weights unavoidable (dial 5). Good design is not picking the best end of each dial, because there is no best end. It is choosing a coherent set of positions that fit your traffic, team, and budget.

Everyday Analogy

Buying a car is like this. Speed, fuel cost, safety, space, and price all pull in different directions. Nobody buys the car that is best at everything, because it does not exist. You choose which compromises hurt least for the roads you drive.

11Choosing a Strategy for Your Scale

The right answer for ten requests per second is the wrong answer for a million. Match the design to the traffic, not to the trend.

Traffic levelRecommended shapeWhy
Up to a few hundred requests per secondKeep everything, or use a high head rateCosts are small, and complete data makes debugging easy. Sampling would hurt more than help.
Thousands of requests per secondParent-based head sampling with a moderate root rate, plus metrics from spans or SDK instrumentsSimple and cheap. Add tail sampling only if rare failures are being missed.
Tens of thousands per secondCollector-based tail sampling for errors and latency, with a small random baselineThe value of catching rare failures now justifies the extra machines.
Hundreds of thousands to millions per secondLayered design: low head rate for noisy paths, routing tier, tail sampling tier, metrics before sampling, adaptive budgetsNo single technique is both cheap enough and smart enough at this size.
flowchart TD
    Q1{"Must every error and slow trace be kept?"}
    Q1 -- "No" --> H["Parent based head sampling with a probability root sampler"]
    Q1 -- "Yes" --> Q2{"Can your team run stateful collector fleets?"}
    Q2 -- "No" --> V["Use a managed tail sampling service or biased head sampling"]
    Q2 -- "Yes" --> T["Two tier collectors with tail sampling policies"]
    H --> M["Always create metrics before sampling"]
    V --> M
    T --> M
Figure 5: A simple decision path. Every branch ends at the same safety rule: measure before you sample.

A hybrid worth knowing

Large organizations often use a hybrid. The SDK applies a parent-based sampler with an “always on” root in most services, so nothing is lost at the source, while a very noisy service, such as a high-volume cache lookup, gets its own low head rate. All traffic flows to the collector fleet, where tail sampling decides. The advantage is simplicity in applications. The disadvantage is that you pay to move and buffer all that data.

A second hybrid reverses the order: a head sampler cuts volume by a large factor first, for example to ten percent, and tail sampling then picks the best of what remains. This is cheaper, but you must accept a hard limit: ninety percent of errors were thrown away before the smart stage ever saw them. Tail sampling can only choose among traces that survived the head. Teams that choose this path should say out loud that they are accepting partial error coverage, and should lean on metrics for exact error counts.

i
Production Example

Platforms that have grown from small to very large, such as ride-hailing and streaming companies, commonly describe the same journey: start with keep-everything, move to low fixed head rates when the bill hurts, then add central, smarter sampling when engineers start saying “the trace I needed was dropped”. The sequence follows pain, and each step is justified by a problem that was actually felt.

12Failure Scenarios and Recovery

Sampling systems fail in surprising, quiet ways. Most failures do not crash anything. They just make your data wrong. Knowing the common ones lets you design around them.

Scenario 1: The incident that floods the backend

A database fails, and nearly every trace now contains an error. The “keep all errors” policy keeps nearly everything, and the backend receives ten or fifty times its normal load. Queries slow down, ingest queues back up, and engineers cannot even open the traces they need. Recovery: prevent it by giving error policies a cap, and by having a “break glass” configuration that shifts to a strict rate limit in emergencies. After the incident, review whether the budget was right.

Scenario 2: Late spans and long traces

Some work does not finish quickly. A request that puts a message on a queue may be processed minutes later by another service. A batch job may produce spans for an hour. If the decision wait is thirty seconds, then the tail sampler judges the trace before the later spans arrive. The result is a trace that looks short and healthy, gets dropped, and takes the important late error with it. Recovery: use span links (a way to connect related but separate traces) so queue-based work forms its own trace, set the decision wait with typical durations in mind, and consider a longer wait only for pipelines that need it, since a longer wait costs memory.

Scenario 3: A tail sampler dies

The process crashes or is restarted during a deployment. Everything it was holding in memory disappears, including the traces it would have kept. Recovery: accept that tail sampling is best effort. Use rolling deployments that replace one machine at a time, and schedule restarts at quiet hours. Keep metrics outside the sampler so numbers survive. For the highest-value traces, such as payment failures, consider an extra copy of that narrow slice that bypasses the buffer.

Scenario 4: Scaling events split traces

When the sampling fleet grows during a traffic peak, the routing math changes. Spans of traces already in flight begin going to a new machine while their earlier spans sit on the old one. Neither side sees the full trace, and both may drop it. Recovery: prefer scaling in small steps, use stable routing (consistent hashing, which moves only a small slice of keys when the machine list changes), and pre-scale before known peaks instead of reacting during them.

Scenario 5: Back pressure moves upstream

A slow backend makes the tail sampling layer slower. Its queues fill, and then the routing layer’s queues fill, and finally the agents start dropping or blocking. If applications wait for the collector, a telemetry problem can slow down real customer requests. Recovery: make telemetry paths non-blocking, use bounded queues with drop-oldest or drop-newest rules, and always rank customer traffic above telemetry in any resource contention.

Scenario 6: The silent drift

Someone changes a policy, a ratio, or a service name, and the share of kept traces shifts without anyone noticing. A month later, a chart shows something strange, and no one can explain it. Recovery: keep sampling configuration in version control, review changes like code, and monitor the effective sampling rate as a first-class metric (the next chapters explain how).

!
Common Trap

Teams often test sampling with light traffic and a perfect network. Real failures come from bursts, restarts, slow backends, and late spans. Rehearse them on purpose: restart a sampler under load, send a burst of errors, and replay a slow queue. It is far cheaper to discover the weakness in a drill than in an outage.

13Security, Privacy, and Trust

Sampling is usually discussed as a cost topic, but it touches security in three real ways: who can influence your decisions, what the sampler can see, and what must never be dropped.

Untrusted callers can set the sampled flag

The traceparent header comes from the caller. If your public service obeys it blindly, an outsider can send millions of requests with the sampled flag turned on and force you to record and pay for every one. This is both a cost attack and a pressure attack on your telemetry pipeline. Defense: at the edge, ignore incoming sampling flags from untrusted sources and make a fresh decision with your own root sampler, while continuing to honor flags from your own internal services. Many gateways and proxies can strip or overwrite these headers.

Everyday Analogy

A theater lets people in with a ticket stub. If a stranger can print stubs that say “free entry for everyone in my group”, the theater fills up with people who never paid. The theater must check stubs against its own records at the door.

The sampler can see sensitive data

Tail sampling policies read span attributes, such as customer tier or an error message. Those attributes may hold personal data. The collector fleet is therefore a place where sensitive information is held in memory, and it should be protected like any other production data store: encrypted connections between tiers, restricted access to machines, and careful logging that never prints span contents. Where possible, remove or hash sensitive attributes in an earlier stage, before they spread into buffers, but be careful to do this after any policy that needs them.

Some data must never be sampled

Sampling is a statistical tool. Audit trails, financial records, and security events often have legal requirements that say “keep all of it”. Using a sampled tracing pipeline for those records is a mistake, because a dropped trace is a lost record. Defense: keep compliance data in a separate pipeline with its own retention rules, and treat tracing as a debugging aid, not as the system of record.

Fairness and bias

If rates are tuned by service or by customer, a heavy user or a loud service may consume the budget and starve the others. Per-tenant caps help here. Likewise, if policies keep “interesting” traces, remember that interesting is a judgment, and any judgment can reflect blind spots. A small uniform baseline, kept alongside the clever policies, acts as a check against your own assumptions.

14Monitoring the Sampling System Itself

A sampler that no one watches is a sampler that is slowly going wrong. Treat the sampling pipeline as a production service with its own dashboards and alerts.

What to watch

Volume

Spans in versus spans out

The ratio is your effective sampling rate. Plot it over time and alert when it moves outside the range you expect.

Memory

Traces in buffer

How many traces the tail sampler holds right now, and how close that is to its configured limit.

Quality

Traces dropped too early

Counts of traces pushed out of memory before their decision. A rising number means your buffer is too small for your traffic.

Quality

Late span arrivals

How late the stragglers are. If many arrive after the decision, the decision wait is too short.

Latency

Decision time

How long policy evaluation takes. Slow rules (complex conditions) can create a backlog.

Health

Queue size and failed sends

Exporter queues and failed or refused spans show back pressure before it spreads upstream.

The OpenTelemetry Collector exposes its own internal metrics, and the tail sampling processor adds counters for traces sampled and not sampled, decision latency, traces dropped too early, and late spans. Exact metric names depend on the version, so confirm them in the documentation for the release you run.

Which alerts are worth paging for

Page a human only for conditions that threaten data or customers: collectors refusing data, buffers at their limit, or the effective rate collapsing to zero. Send quieter notifications for drift conditions: the effective rate changing by more than a set percentage, error-class traces suddenly exceeding their budget, or spans from a service disappearing. The goal is to learn about a bad sampler from an alert and not from an engineer who cannot find a trace in the middle of an outage.

i
Tip

Add a tiny canary: a synthetic request that is always marked as keep, sent every minute. If the canary trace stops appearing in the backend, you know right away that the pipeline, not the application, is broken.

15Design Patterns and Anti-patterns

Experienced teams keep arriving at the same good habits and the same expensive mistakes. Learn from their scars.

Patterns that work

Pattern

Decide once, at the edge

Let the first service make the decision, and let every other service obey it using a parent-based sampler.

Pattern

Measure before you sample

Create request, error, and latency metrics from the full span stream, so dashboards and alerts never depend on the sample.

Pattern

Keep a clean baseline

Retain a small, uniform random slice of everything so you always have an honest picture of normal behavior.

Pattern

Budget your priorities

Give errors, slow traces, and special customers separate caps so one category cannot starve the others.

Pattern

Central, versioned configuration

Hold rates and policies in source control and roll them out like code, with review and rollback.

Pattern

Store weights with data

Record each trace’s probability so counts and averages can be corrected later.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What it looks like

Every service samples independently with its own random ratio, ignoring the incoming sampled flag.

Why it hurts

The chance that all services keep the same trace becomes small, so most stored traces have missing pieces. Debugging turns into guessing, and engineers lose trust in tracing altogether.

Do this instead

Use a parent-based sampler everywhere, with a single root decision at the edge.

ANTI-PATTERN 02Avoid
What it looks like

Dashboards for request counts and error rates are built from stored, sampled spans.

Why it hurts

Every sampling change moves the numbers. Alerts either fire falsely or stay silent, and no one notices until an outage has already been missed.

Do this instead

Produce metrics from the complete stream before sampling, and use traces only for explanation.

ANTI-PATTERN 03Avoid
What it looks like

A head sampler drops 90 percent of traffic, and a tail sampler downstream is expected to “keep all errors”.

Why it hurts

Nine out of ten errors were already discarded before the tail sampler saw them, but the team believes error coverage is complete.

Do this instead

Choose one stage as the real decision-maker. If both exist, document exactly what the first stage throws away.

ANTI-PATTERN 04Avoid
What it looks like

Tail sampling policies are uncapped, and buffer sizes are left at defaults that were never checked against real traffic.

Why it hurts

The first big incident floods the backend, or the buffer silently evicts traces before decisions, so the sampling system is least reliable exactly when it matters most.

Do this instead

Size buffers from measured trace rates and durations, add caps to every keep-all policy, and test with a burst before production.

ANTI-PATTERN 05Avoid
What it looks like

One global sampling percentage is applied to every service and endpoint.

Why it hurts

Busy endpoints dominate the budget, and quiet but critical ones get no traces. The system looks cheap and still fails to answer questions.

Do this instead

Use per-service or per-operation targets, or adaptive sampling, and review them regularly.

16Real-World Examples

The same trade-offs show up in famous systems. Seeing them in the wild makes the abstract ideas stick.

Google Dapper: a tiny fixed rate at giant scale

Dapper, the tracing system Google described publicly, started with a uniform rate of about one request in a thousand. Because Google’s traffic is gigantic, that thin slice still provided enough examples to find common latency problems, and it kept overhead on production services very low. The trade-off was visible: low-traffic services produced too few traces, which pushed the designers toward adaptive rates that adjust to traffic.

Uber and Jaeger: adaptive budgets per endpoint

Uber created Jaeger to trace thousands of microservices, and the project’s sampling model supports per-service and per-operation strategies, with an adaptive mode that targets a number of traces per second for each operation. The trade-off is added machinery (a strategy service and a storage-backed throughput history) in exchange for fairness: busy endpoints cannot crowd out quiet ones.

Honeycomb Refinery: tail sampling with weights

Refinery is a proxy that holds spans for a trace, applies rules such as “keep errors” or “keep slow requests”, and forwards survivors with a recorded sample rate. It also offers dynamic samplers that change rates based on traffic patterns. The trade-off is a stateful fleet that must route by trace ID, in return for keeping the traces that matter and still letting queries compute honest totals.

AWS X-Ray: simple, rule-based sampling

X-Ray’s default rule keeps the first request each second and five percent of the rest. Teams can add rules for specific services, paths, or methods, and the central rule service hands each application a share of the budget. The trade-off is simplicity and managed operation against flexibility: rules are about requests, not about what happened later, so outcome-based keeping needs a different tool.

Envoy at the edge: the front door decides

Edge proxies often make the first sampling choice, and every service behind them follows the flag. This keeps traces whole and costs almost nothing, but it also means the edge configuration controls observability for the entire company. A mistake there, like setting a rate far too low, affects everyone at once, which is why many companies keep that configuration under strict review.

17Connecting Sampled Traces with Metrics and Logs

Traces rarely live alone. Teams jump between charts, logs, and traces, and sampling can break those jumps if you are not careful.

Dangling links

Many systems write the trace ID into every log line, so an engineer can click from a log message to the matching trace. When sampling drops the trace, the log line still carries an ID that leads nowhere. Engineers see “trace not found” and assume tracing is broken. This is not a bug in the tool, but it is a real experience cost of sampling.

There are three ways to reduce the pain. First, include the sampled flag in the log line, so the viewer can tell “this trace was dropped on purpose” from “something is broken”. Second, let log pipelines keep logs for sampled traces longer than logs for dropped traces. Third, if logs are tail sampled too, use the same decision for both, so the log and the trace are kept or dropped together.

Exemplars: pointing from numbers to examples

An exemplar is a sample trace reference attached to a metric data point. A chart of slow requests per minute can include a link to one real slow trace from that minute. For this to work, the linked trace must have been kept. A good design therefore makes sure that the traces chosen as exemplars are sampled ones, since linking a number to a dropped trace creates the same dangling-link problem.

Everyday Analogy

A museum catalog lists a thousand paintings, but only some are on display. If the catalog says “see the painting in Room 4” for a painting that is in storage, visitors get frustrated. Good catalogs mark which items are on display. A good telemetry design marks which traces are kept.

Sampling and other signals

Because metrics are exact and traces are sampled, a healthy workflow goes in one direction: notice a problem in a metric, narrow it down by service and operation, and then open a kept trace to see why. If your sampling keeps errors and slow requests, there will usually be a matching trace waiting. If it does not, the right reaction is to adjust the sampling policy, not to blame the engineer.

18A Phased Rollout Plan

Switching on sampling in one big step is risky. A phased plan keeps surprises small and gives you evidence at each stage.

1

Measure the baseline

Record how many spans per second you produce, how big they are, and how much the pipeline costs. You cannot judge savings without a starting point.

2

Move metrics before sampling

Create request, error, and latency metrics from the full span stream and move dashboards and alerts onto them. Do this before dropping anything.

3

Filter the obvious noise

Remove health checks and other low-value spans with plain rules. This often saves a surprising amount with almost no risk.

4

Introduce parent-based head sampling

Set a conservative root rate at the edge, with every service honoring it. Record the rate with the data so counts can be corrected.

5

Add tail sampling for errors and slow traces

Start with one small tier, a long enough decision wait, and capped policies. Watch the buffer and late-span metrics closely.

6

Scale out with routing

When one tail sampler is no longer enough, add the routing tier, test scaling events, and rehearse restarts.

7

Tune with evidence

Review which traces engineers actually opened, which questions could not be answered, and which policies never matched. Adjust rates and policies, then repeat on a regular schedule.

19A Worked Cost Example

Words about trade-offs are useful, but numbers make them real. This chapter compares four designs for the same imaginary platform, so you can see how each dial changes the bill.

The imaginary platform

Suppose a company serves 200,000 requests per second. Each request creates 20 spans, and each span is 500 bytes on the wire, so one trace is about 10 kilobytes. That means 2 gigabytes of span data leave the applications every second, or about 172.8 terabytes per day. Assume that 0.2 percent of requests fail (400 failures per second), that 1 percent are slow enough to matter, and that the team wants a 1 percent random baseline of ordinary traffic. These figures are made up for teaching, but the method works with your own numbers.

DesignData leaving appsCollector memory neededStored per dayError coverageOperational load
A. Keep everything2 GB/sSmall172.8 TB100%Low, but very high bill
B. Head sampling at 5%0.1 GB/sNone8.6 TB5% of errorsVery low
C. Tail sampling only2 GB/sAbout 60 GB of raw spans, several times more in memoryAbout 3.8 TB100% of errorsHigh: routing tier, stateful fleet
D. Head 20% then tail sampling0.4 GB/sAbout 12 GB of raw spansAbout 0.8 TB20% of errorsMedium to high

How the numbers were built

For design C, the tail sampler keeps all errors (0.2 percent), all slow traces (1 percent), and a random baseline (1 percent), which adds up to roughly 2.2 percent of 172.8 terabytes, or about 3.8 terabytes. Memory comes from the trace arrival rate times the decision wait: 200,000 traces per second times 30 seconds is six million traces held at once, and six million times 10 kilobytes is 60 gigabytes of raw data. Because the in-memory form is often three times larger or more, plan for hundreds of gigabytes across the fleet. For design D, only one trace in five enters the system, so every figure is one fifth as large, and the stored amount is 2.2 percent of 34.6 terabytes, close to 0.8 terabytes.

What the table does not show

Data transfer is the hidden line. In design C, 2 gigabytes per second cross the network at least once, and often twice or three times, because spans move from apps to agents, from agents to routing collectors, and from routing collectors to sampling collectors. If a hop crosses availability zones and a cloud provider charged one cent per gigabyte for it (check current prices, because they differ and change), that single hop would cost about 1,728 dollars per day, or roughly 630,000 dollars per year. Placing collector tiers in the same zone as the traffic they serve, and compressing telemetry on the wire, can remove a large share of that bill.

Everyday Analogy

Moving house is a good comparison. Throwing away most of your belongings before the move (head sampling) makes the truck cheap, but you may throw away something you needed. Carrying everything to the new place and sorting it there (tail sampling) is safer for your belongings, but you pay for a bigger truck, a bigger sorting room, and a lot more lifting.

Reading the lesson

No design wins every column. Design B has the smallest bill and the weakest error coverage. Design C has the strongest coverage and the heaviest machinery. Design D sits between them and is often chosen by teams who accept losing some errors in exchange for a much smaller fleet, as long as exact error counts come from metrics created before sampling. The right choice depends on what a missed trace costs you. If one missed payment failure costs more than the fleet, choose C. If tracing is a convenience and metrics carry the alerting, B or D is likely enough.

i
Tip

Always run this calculation with your own measured numbers before buying machines. The three inputs that matter most are the new-trace rate, the typical spans per trace, and the longest decision wait you really need. Small changes in the decision wait change the memory bill directly, because memory grows in a straight line with it.

20Advanced Situations at Scale

Real platforms are messier than diagrams. Here are the situations that break simple designs and how teams handle them.

Many regions, many clusters

A request may start in one region and call a service in another. If each region runs its own tail sampling fleet, then the spans of that request are split between two fleets, and neither sees a complete trace. There are three common answers. The first is to accept regional traces: each region judges what it sees, and cross-region calls become linked but separate traces. The second is to route by trace ID to a single global fleet, which gives whole traces but pays for cross-region data transfer and adds delay. The third is to make the sampling decision at the head (where the request enters) and carry it in the flag, so each region obeys it without needing to see the whole trace. Many companies choose the third for cross-region traffic and the first for everything else.

flowchart LR
    U["User request"] --> E["Edge Gateway in Region 1 makes the root decision"]
    E --> S1["Services in Region 1"]
    S1 -->|"traceparent with sampled flag"| S2["Services in Region 2"]
    S1 --> F1["Tail Sampling Fleet in Region 1"]
    S2 --> F2["Tail Sampling Fleet in Region 2"]
    F1 --> B["Tracing Backend"]
    F2 --> B
Figure 6: Each region runs its own sampling fleet, and the head decision travels across regions in the header.

In the figure, the gateway in region one makes the first choice. Region two receives the flag and follows it. Each fleet then applies its own policies to what it sees. The backend can still join the pieces by trace ID when someone searches, even though no single fleet held the whole trace. The price is that outcome-based rules in region two cannot see what happened in region one.

Long-running and asynchronous work

Queues, schedulers, and batch jobs stretch a trace across minutes or hours. Waiting that long in a tail sampler is unaffordable, so teams usually treat each unit of asynchronous work as its own trace and connect it to the original request with a span link. The sampling decision for the new trace is then made when the work begins. A useful trick is to copy a “this origin was important” attribute onto the linked trace, so a policy can keep the follow-up work whenever the original request was interesting.

Short-lived processes and serverless functions

Functions that live for a few hundred milliseconds may end before their telemetry is sent. Spans that were never flushed are lost, whatever the sampler decided. Head sampling helps here, because it reduces how much needs to be flushed, and a local collector extension or agent next to the function can accept data quickly and send it onward. Whatever you choose, test that a sampled span actually reaches the backend after the function freezes.

Mixed propagation formats

Older systems may use other header formats, such as the B3 headers that came from Zipkin. Each format carries the sampling decision a little differently, and converting between them at gateways can lose or misread flags. If your company has a mixture, test a trace that crosses every boundary and confirm that a “dropped” verdict stays dropped and a “kept” verdict stays kept. Standardizing on the W3C format reduces this risk over time.

Multi-tenant platforms

If one telemetry pipeline serves many teams or customers, a single noisy tenant can fill the buffers or the backend budget. Give each tenant its own quota and its own tail sampling policies, and report each tenant’s effective rate to them, so they understand why some traces are missing. Fairness is not only a technical choice here. It is also a service promise that should be written down.

Service meshes and sidecars

In a service mesh, sidecar proxies create spans for every network hop, sometimes doubling the span count. Their sampling rate is configured separately from the application SDKs. If the mesh samples at 1 percent and the application at 100 percent of parents, the result depends on who starts the trace. Decide which layer is the owner of the root decision, and configure the other layer to follow it.

!
Common Trap

Teams sometimes add tail sampling in the collector and forget that the mesh or gateway is already head sampling at a low rate. The two stages multiply: if the gateway keeps 5 percent and your tail policy keeps 20 percent of what it sees, the overall chance for an ordinary trace is 1 percent. Write down the combined effect of every stage in one place.

21Operating the Strategy Day to Day

A sampling design is not finished when it is deployed. It needs owners, rules for changing it, and a habit of checking that it still serves the people who depend on it.

Who owns the decision

Sampling sits between teams. Application owners care about seeing their traces, the platform team cares about cost and stability, and finance cares about the bill. Without a clear owner, rates drift upward after every incident (“keep more, just in case”) and downward after every invoice. Name a small group that owns the sampling policy, publishes it, and accepts requests for exceptions with a reason and an end date.

Questions to ask before every change

Question

What gets dropped that was kept before?

Describe the traces that will disappear. If you cannot, you do not yet understand the change.

Question

What happens to the metrics?

Confirm that dashboards and alerts do not depend on the data you are about to reduce.

Question

How will we know it worked?

Pick the numbers you will watch (effective rate, buffer fullness, stored volume) and the values that mean success.

Question

How do we undo it?

Know the rollback step and how long it takes, before you start.

Question

Who needs to be told?

Teams that investigate incidents should hear about changes to what they will find in the trace search.

Question

Does it interact with other stages?

Check gateways, meshes, SDK settings, and collector policies for combined effects.

Testing a change safely

The safest way to test a new rule is to run it on a small slice first. Apply the new policy to one service or one percent of traffic, compare the kept volume with your forecast, and watch the sampler’s own metrics for surprises such as growing buffers or rising decision delay. Where possible, run the new policy in a side pipeline that only counts what it would have kept, without exporting anything, and compare that count with the real pipeline. If the counts match your expectations, promote the rule to everyone.

Regular review

Set a calendar reminder to review the sampling system every quarter. Look at which policies never match anything, which services are always at their budget, which traces engineers actually opened, and which complaints of “I could not find the trace” were real. Retire rules that no longer help, and add rules where gaps appeared. Sampling that is never reviewed becomes a collection of old guesses.

Documentation people can find

Write a short public page that answers four questions for any engineer: what is kept, what is dropped, how to ask for an exception, and how to force-keep a trace while debugging. A common technique is a special request header or attribute that a policy recognizes and always keeps, with a rate limit and an access check so it cannot be abused. That small feature saves many hours of frustration and many escalations.

Everyday Analogy

A library cannot keep every newspaper forever, so it publishes a retention policy: which papers stay, which are discarded after a year, and how to request an exception. Readers do not complain about missing papers when the policy is clear and the exceptions process works. They complain when old copies simply vanish without explanation.

Myths that cause bad decisions

Myth: “Sampling at one percent means we see one percent of the problems.” It means you see one percent of the traces, which is a different thing. A problem that affects thousands of requests every second will appear in many sampled traces. A problem that affects one request a day may never appear. Think in absolute counts of affected requests, not in percentages alone.

Myth: “Tail sampling is always better than head sampling.” Tail sampling is better at catching specific outcomes, but it costs memory, machines, network traffic, and delay, and it does nothing for application overhead. For many systems, a well-tuned head sampler plus accurate metrics gives most of the value at a small fraction of the cost.

Myth: “If a trace is dropped, the request was not recorded anywhere.” Logs and metrics still describe that request. Sampling removes only the detailed trace. This is exactly why metrics created before sampling matter, and why logs should say whether their trace was kept.

Myth: “We can fix a bad sampling rate after the incident.” Sampling decisions cannot be replayed. A trace that was dropped last night is gone. If you are about to launch something risky, raise the rates before the launch, not after the first failure, and lower them again when the risk has passed.

Myth: “The sampler is just a setting, so anyone can change it.” Every change shifts the cost, the visibility, and the trustworthiness of statistics. Treat it as production configuration with review, a rollback plan, and a clear owner.

Myth: “A random sample is always fair.” Random sampling is fair across requests, but not across users, endpoints, or teams. A heavy endpoint generates most of the sampled traces, and a rare endpoint generates almost none. Fairness usually needs per-endpoint or per-tenant budgets on top of randomness.

A short runbook for a bad day

When the sampling system itself misbehaves, a calm checklist beats improvisation. First, check whether customer traffic is affected. If telemetry is slowing real requests, shed telemetry load immediately by lowering head rates or disabling the heaviest exporters, because customers always come first. Second, look at the effective sampling rate and the buffer fullness charts to see whether the problem is too much input, too little capacity, or a stuck backend.

Third, decide what to protect. During a large incident, the most valuable data is usually a modest set of error traces from the affected services, plus accurate metrics. Switch to the emergency configuration that caps every keep-all policy, and confirm that the span metrics are still flowing. Fourth, communicate: tell the incident channel that some traces are being dropped on purpose and how to find the ones that are kept. Engineers who know the rules will stop wasting time searching for missing traces.

Finally, after the incident, write down what the sampling system did, what it dropped, and what you wish it had kept. Turn each wish into a concrete policy change with an owner. Over many incidents, this habit is what slowly turns a generic sampling setup into one that fits your company’s real failures.

A sampling policy nobody can explain is a sampling policy nobody will trust.

22Frequently Asked Questions

Q1Is head sampling or tail sampling better?
Neither is better in general. Head sampling is cheap and simple but cannot see outcomes. Tail sampling sees outcomes but costs memory, machines, and delay. Most large systems use some of both, with metrics created before either.
Q2Can I change the sampling rate without redeploying applications?
Yes, if you use collector-based tail sampling, where policies live in the collector configuration, or a remote sampling setup, where SDKs poll a central service for rates. A plain ratio set through environment variables normally needs a restart.
Q3Why do I see traces with missing spans?
The usual causes are services sampling independently instead of following the parent, a router that splits one trace across multiple collectors, spans arriving after the decision wait, or the tail sampler evicting traces from memory early. Check each in that order.
Q4What decision wait should I use for tail sampling?
Start from the longest normal request duration in your system and add a safety margin. Longer waits catch more late spans but hold more traces in memory. Measure the late-span metrics and adjust, rather than guessing.
Q5Does sampling make my application faster?
Head sampling does, because dropped spans are not fully built or sent. Tail sampling does not, because applications still create and send every span. Only the storage side shrinks.
Q6How do I get correct request counts from sampled data?
Do not count sampled traces. Create metrics from the full span stream before sampling. If you must use sampled traces, multiply each by the inverse of its sampling probability.
Q7Should I trust the sampled flag from outside callers?
No. At your public edge, ignore incoming sampling decisions and make your own. Keep honoring flags from trusted internal callers so traces stay whole.
Q8What happens to a trace that crosses services with different sampling rates?
With a parent-based sampler, the decision made at the start wins, and the other rates do not matter for that trace. With independent samplers, the trace is likely to be fragmented. Consistent probability sampling reduces the damage when rates must differ.
Q9Can I keep sampling off for some critical traffic?
Yes. Use a custom head sampler or a tail policy that keeps all traces for specific attributes, such as a payment route. Give it a budget so a spike cannot overwhelm the pipeline, and use a separate pipeline for data that must never be dropped for legal reasons.
Q10How much does a tail sampling fleet cost compared with the savings?
It depends on volume. At small scale, the fleet may cost more than the storage you save. At large scale, the savings usually dominate. Estimate memory from trace rate times decision wait times trace size, add overhead for in-memory representation, and compare with your storage and ingest bill.
Q11Is sampling the same as reducing log levels?
No. Reducing log levels removes detail from every request. Sampling keeps full detail for some requests and none for others. They solve different problems and are often used together.
Q12What is the single most important thing to do first?
Move your dashboards and alerts onto metrics that are produced before sampling. After that, every sampling change becomes a cost decision instead of a visibility risk.

23Summary and Key Takeaways

Sampling in OpenTelemetry is the art of keeping the traces you will need while paying for only a fraction of the data. There is no free option. Head sampling gives low cost and simplicity but cannot see outcomes. Tail sampling gives outcome-aware choices but needs memory, routing, and operational discipline. Consistency keeps traces whole, weights keep statistics honest, and metrics created before sampling keep dashboards truthful. The best systems combine these pieces deliberately, watch them closely, and revisit the choices as traffic grows.

Key Takeaways

  • Sampling is a set of trade-offs, not a single setting: cost, completeness, delay, state, consistency, and trust all move together, so pick a coherent combination.
  • Head sampling is cheap and blind; tail sampling is smart and costly: head decisions happen at the start with no knowledge of outcomes, while tail decisions need buffers, routing by trace ID, and spare memory.
  • Keep traces whole: wrap probability samplers in a parent-based sampler so the first decision carries through every service, and use consistent probability methods when rates must differ.
  • Measure before you sample: create request, error, and latency metrics from the full span stream, so dashboards and alerts never depend on a sample.
  • Weight and cap: store each trace’s sampling probability so counts can be corrected, and give keep-all policies a budget so incidents cannot flood your backend.
  • Do not trust outsiders and do not sample what the law says to keep: reset sampling decisions at the public edge, and keep audit and compliance data in a separate pipeline.
  • Operate the sampler like a service: watch the effective rate, buffer fullness, and late spans, rehearse failures, and roll out changes in phases with evidence.