Cost-Aware Sampling Strategy Design in OpenTelemetry

Cost-Aware Sampling Strategy Design in OpenTelemetry

How to keep the traces that matter, throw away the ones that do not, and stay inside your observability budget without flying blind.

Imagine your company runs an online store. Every second, thousands of shoppers click, search, add things to carts, and pay. Behind each click, dozens of small programs talk to each other. If you wrote down every single conversation in full detail, you would fill a warehouse of notebooks every day, and you would pay a huge bill just to keep those notebooks. Yet on most days, nearly all of those notebooks would describe boring, healthy, fast requests that nobody will ever read.

This is the daily reality of distributed tracing. Tracing is wonderful because it shows the full journey of one request through many services. But it produces a mountain of data, and that data costs real money to send, store, and search. The craft of deciding which traces to keep and which to let go, while watching the bill, is called cost-aware sampling. This guide walks through it from the very first idea to the production design, step by step, using OpenTelemetry, the open standard that most modern systems now use for telemetry.

1What Cost-Aware Sampling Strategy Design Really Means

Three ordinary words hide one big idea. Let us take them apart one at a time.

Sampling: keeping a part instead of the whole

Sampling means choosing a small portion of something to represent the whole. A cook tastes one spoon of soup to judge the whole pot. A pollster asks a few thousand people instead of a whole country. In tracing, sampling means saving only some traces and discarding the rest.

The trade is simple. You give up the ability to look at every single request, and in return you pay much less for sending, storing, and searching data. A good sample still tells you the truth about your system. A bad sample hides problems or tells lies.

Cost-aware: knowing the price of every choice

Many teams pick a sampling rate by feeling. They say “let us keep ten percent” because ten sounds like a nice round number. Nobody checks what that costs, and nobody checks whether the right traces survived. Cost-aware design flips this. You begin with a budget, an amount of money or data you are willing to spend each day. Then you decide how to spend that budget so that every dollar buys the most useful traces.

Strategy design: a plan, not a single setting

A strategy is a full plan that answers several questions together. Which traces are worth always keeping? Which are noise that should be dropped immediately? How much of the ordinary, healthy traffic should we keep as a baseline? What happens when traffic doubles, or when a big outage causes a flood of errors? Who can change the plan, and how do we know the plan is working? Design means answering these questions on purpose, writing the answers down, and checking them with real numbers.

Analogy

Think of a museum with a small storage room. Thousands of objects arrive every day, but the room holds only a few hundred. A careless museum keeps the first objects that arrive and turns the rest away. A thoughtful museum decides in advance: always keep rare and broken-and-repaired pieces that teach us something, keep a few ordinary pieces as examples of everyday life, and throw away exact duplicates. Cost-aware sampling is the thoughtful museum for your traces.

1 to 5%
typical share of healthy traces kept as a baseline in busy systems
100%
share of failed or very slow traces most teams try to keep
1 budget
the number every decision is measured against
i
Tip

The percentages above are common starting points, not rules. The right numbers depend on your traffic, your error rate, and your budget. Later chapters show how to work them out for your own system.

2Telemetry Basics: Traces, Spans, and Context

Before we choose which traces to keep, we need to know exactly what a trace is and how it travels.

Trace and span

A trace is the full story of one request. When a shopper presses the “Pay” button, that single press may touch a web server, a cart service, a payment service, a fraud checker, a database, and a message queue. The trace records the whole journey from start to finish.

A trace is made of spans. A span is one step in the journey, such as “the payment service called the bank” or “the database ran a query”. Each span has a start time, an end time, a name, and some labels called attributes, for example the name of the service or whether the step succeeded. Spans are linked in a tree: a parent span can have many child spans, and together they form the trace.

Analogy

A trace is like the tracking history of a parcel. The whole history is the trace. Each scan, such as “left the warehouse”, “arrived at the sorting hub”, “out for delivery”, is a span. Every scan carries the same parcel number so that you can join the history together later. In tracing, that parcel number is the trace ID.

Context propagation: carrying the parcel number

How does the cart service tell the payment service that they belong to the same trace? It passes a small piece of information along with every call. This is called context propagation. The most common format is the W3C Trace Context standard, which uses an HTTP header named traceparent. That header carries four things: a version number, the trace ID, the ID of the calling span (the parent), and a small set of flags.

One flag is especially important for this guide: the sampled flag. It says “the first service decided to keep this trace, so everyone downstream should keep it too”. If every service respected this flag, then either the whole trace is kept or the whole trace is dropped. If services ignore it and each flip their own coin, you end up with traces that have holes in them. We will come back to this many times.

The pieces of OpenTelemetry

Piece 1

API

The small, stable set of calls that application code uses to create spans. It does nothing heavy by itself.

Piece 2

SDK

The engine inside each application that actually makes spans, applies the sampler, batches data, and sends it out.

Piece 3

Collector

A separate program that receives telemetry, processes it, and forwards it. Most sampling power lives here.

Piece 4

OTLP

The OpenTelemetry Protocol, the common language that SDKs, Collectors, and backends use to exchange data.

Piece 5

Exporter

The part that ships data to a destination, such as a tracing backend, another Collector, or object storage.

Piece 6

Processor

A step inside the Collector that changes or filters data on its way through, for example adding labels or dropping spans.

Sampling can happen in two places: inside the SDK in your application, and inside the Collector. That single fact shapes the whole design. Deciding early (in the SDK) is cheap but blind. Deciding late (in the Collector) is smart but needs more machines. The next chapters explain why.

3Where the Money Goes

You cannot control a cost you cannot see. Let us look at every place a trace costs something.

Many people think the only cost of tracing is the monthly invoice from a vendor. In truth, a trace costs money at every stop on its journey. Cost-aware design starts by listing those stops.

Cost areaWhat you pay forWhich sampling lever helps
Application CPU and memoryCreating spans, adding attributes, batchingHead sampling (spans that are never created cost almost nothing)
Network between app and CollectorBytes sent over the network, sometimes across availability zonesHead sampling, compression, trimming attributes
Collector computeMachines that receive, buffer, and process spansEarly filtering, careful tail sampling limits
Cross-zone traffic between Collector tiersPer-gigabyte charges when data hops between zones in a cloudDrop noise before the hop, compress
Ingestion at the backendPer-gigabyte or per-span fees charged when data arrivesTail sampling, rate limits
Storage and retentionDisk or object storage for as many days as you keep dataKeep fewer traces, shorter retention for low-value ones
Queries and indexingSearch indexes and the compute used to answer questionsKeep fewer, richer traces
Human timeEngineers digging through noise to find the real problemKeep the right traces, not just fewer ones

A worked example with simple numbers

Let us build a small, made-up platform and calculate its trace volume. These numbers are for teaching only. They are not anyone’s real price list.

  • The platform handles 5,000 requests per second on average.
  • Each request becomes one trace with about 40 spans.
  • Each span averages about 500 bytes, so one trace is about 20 kilobytes.

In one day there are 86,400 seconds. That gives 432 million traces, or about 17.3 billion spans. At 20 kilobytes per trace, the total is about 8,640 gigabytes (8.64 terabytes) per day if you keep everything. Now suppose a backend charges an illustrative ten cents per gigabyte ingested.

StrategyShare keptData per dayIngest cost per day (illustrative)
Keep everything100%8,640 GB$864
Fixed 1% random sample1%86.4 GB$8.64
Smart mix: all errors, all slow, 1% of the restabout 3%about 259 GBabout $26

Look at the last two rows. The smart mix costs about three times more than a plain one-percent sample, yet it is still more than thirty times cheaper than keeping everything. And it keeps the traces that actually explain failures. This is the heart of cost-aware design: you do not simply chase the lowest bill. You chase the best value per byte.

!
Watch out

Sampling in the Collector reduces backend and storage cost, but the application still pays CPU to create every span, and the network still carries every span to the Collector. If your biggest bill is network or compute, you need early cuts as well as late ones.

4A Short History of Trace Sampling

The ideas in modern OpenTelemetry did not appear overnight. Each one answered a pain someone felt before.

1

Around 2010: Google Dapper and uniform sampling

Google described Dapper, its internal tracing system. In its early production use, it traced roughly one request in every 1,024. Google reported that this was enough for high-traffic services because the same patterns repeat many times, and later described adjusting the rate automatically to hit a target volume of traces per unit of time.

2

Early to mid 2010s: open source tracers spread

Zipkin, first built at Twitter, and Jaeger, first built at Uber, brought Dapper-style tracing to everyone. Jaeger added remote sampling, where a central service tells every application how aggressively to sample each operation, so the rate can change without redeploying code.

3

Late 2010s: cloud providers add rules

AWS X-Ray introduced centrally managed sampling rules. Each rule has a small guaranteed number of traces per second (a reservoir) plus a percentage of everything above it. This idea, a floor plus a percentage, is still one of the best budget tools available.

4

2019 onward: OpenTelemetry unites the field

Two earlier projects, OpenTracing and OpenCensus, merged into OpenTelemetry. Standard SDK samplers arrived, and the Collector gained a tail sampling processor so that decisions could be made after seeing the whole trace.

5

Recent years: consistent probability sampling

The OpenTelemetry community has been working on a way to record the exact sampling probability inside the trace state, so that different services and tools can sample at different rates and still produce correct counts. Parts of this work are still maturing, so check what your SDK version supports.

Notice the direction of travel. The field moved from “keep a random slice” to “keep a slice that is chosen with a purpose, controlled from the center, and measurable afterwards”. Cost-aware sampling is where that road leads.

5Core Sampling Concepts You Must Know

A handful of ideas explain almost everything about how sampling behaves. Learn these and the rest feels easy.

Sampling probability and sampling rate

The probability is the chance that a given trace is kept. A probability of 0.01 means one trace in a hundred. People also say “a rate of one percent” or “one in a hundred”. They all mean the same thing. Some systems use the word rate for traces per second instead, so always check which one a tool means.

Adjusted count: giving each kept trace a weight

If you keep one trace in a hundred, then each kept trace stands in for about a hundred. That number is the adjusted count, simply one divided by the probability. When you later count “how many checkout requests happened”, you add up the adjusted counts instead of the raw number of stored traces. Without this step, your charts would shrink every time you lowered the sampling rate, and nobody could trust them.

Analogy

An exit poll asks one voter in fifty. When it finds that thirty voters picked the red party, it reports about fifteen hundred, because each answer speaks for fifty people. The adjusted count is that “speaks for fifty” weight attached to every kept trace.

Determinism and consistency

A sampler is deterministic when the same trace ID always gets the same answer. This is usually done by turning the trace ID into a number and keeping the trace if the number falls below a threshold. Because every service sees the same trace ID, they all reach the same decision without talking to each other. A sampler is consistent when a trace kept at a low rate is also kept at every higher rate. This lets different parts of a system use different rates and still agree about which traces form a complete set.

Parent-based decisions

A parent-based sampler looks at the incoming request. If a parent span already decided to keep the trace, it keeps its part too. If the parent decided to drop, it drops. Only when there is no parent does it make its own decision. This is what keeps traces whole across many services.

Recording versus sampling

These two words are easy to mix up. A span that is recorded exists in memory and can collect attributes. A span that is sampled is also marked to be exported. A span can be recorded but not sampled, which is useful when something local, such as a metric, wants to look at it even though the trace will not be saved.

Stratification: separate pots for separate kinds of traffic

If you sample everything at one percent, a rarely used but important endpoint with ten calls a day might never appear. Stratified sampling gives each kind of traffic its own rule, for example “always keep refund requests, keep 0.1 percent of health checks, keep 5 percent of search”. Cost-aware design relies on this heavily.

Term

Probability

The chance a trace is kept, such as 0.01 for one in a hundred.

Term

Adjusted count

One divided by the probability. The weight a kept trace carries when counting.

Term

Reservoir

A guaranteed minimum number of traces per second, kept before any percentage applies.

Term

Decision wait

How long a tail sampler waits for the rest of a trace before deciding.

Term

Policy

One rule inside a tail sampler, such as “keep traces with errors”.

Term

Budget

The cap on data or money per day that the whole strategy must respect.

6Head Sampling versus Tail Sampling

This is the biggest fork in the road. The difference is simply when the decision is made.

Analogy

Head sampling is a bouncer at the door who lets in one guest in a hundred, chosen before anyone has said a word. Tail sampling is a reviewer at the exit who reads every guest’s full story and keeps only the interesting ones. The bouncer is cheap but cannot know who will turn out to be interesting. The reviewer needs a big desk and a lot of patience, but picks far better.

flowchart LR
  subgraph H["Head sampling: decide at the start"]
    A1["Request arrives"] --> A2["SDK sampler decides"]
    A2 -->|"keep"| A3["Record all spans"]
    A2 -->|"drop"| A4["Create no data"]
    A3 --> A5["Export to backend"]
  end
  subgraph T["Tail sampling: decide at the end"]
    B1["Request arrives"] --> B2["Record all spans"]
    B2 --> B3["Collector buffers the whole trace"]
    B3 --> B4{"Do policies match"}
    B4 -->|"yes"| B5["Export to backend"]
    B4 -->|"no"| B6["Discard the trace"]
  end
    
Figure 1: Head sampling decides before any data exists. Tail sampling decides after the whole trace has been seen.

In Figure 1, the top path shows head sampling. The decision happens the instant a request begins. If the answer is “drop”, the application barely does any work, because no spans need to be built or sent. The bottom path shows tail sampling. Every span is created and sent to a Collector, which holds the pieces of a trace in memory until the trace looks finished. Only then do the policies decide whether to keep it.

The samplers that ship with OpenTelemetry SDKs

Every OpenTelemetry SDK includes a small family of built-in samplers. They are chosen through code or through simple settings such as OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG.

  • Always on keeps every trace. Good for development, expensive in production.
  • Always off keeps none. Useful to switch tracing off for one service.
  • Trace ID ratio keeps a fixed fraction, decided by the trace ID so that all services agree.
  • Parent-based wraps another sampler and obeys the parent’s decision when one exists. The popular production choice is parent-based wrapped around a trace ID ratio sampler.

Side by side

QuestionHead samplingTail sampling
When is the decision made?At the first span, before the work happensAfter most or all spans have arrived
Can it keep “all errors”?No, the error has not happened yetYes
Can it keep “all slow requests”?NoYes
Saves application CPU?YesNo, spans are still created
Saves network to the Collector?YesNo
Saves backend and storage cost?YesYes
Extra infrastructure?NoneA Collector tier with enough memory
Main riskThrows away the rare, valuable traces by chanceComplexity, memory use, incomplete traces

Why head sampling is loved

  • Simple to set up and easy to explain.
  • Saves money in the application and on the network.
  • Needs no extra servers.
  • Predictable cost, since volume tracks the rate.

Why head sampling disappoints

  • Blind to errors and slowness at decision time.
  • A one percent sample keeps only one percent of the failures too.
  • Rare endpoints may vanish completely.
  • Cannot adapt to what the trace turned out to be.
i
Tip

You do not have to choose one. The best production designs combine both: head sampling or filtering to remove obvious noise cheaply, and tail sampling to pick the valuable traces from what remains. The chapters ahead show how to join them safely.

7Inside Tail Sampling: How the Collector Decides

Tail sampling feels like magic from the outside. Inside, it is a patient bookkeeper with a very big desk.

The step-by-step life of a trace inside the sampler

The tail sampling processor in the OpenTelemetry Collector works roughly like this. Spans arrive one at a time and from many services. The processor looks at the trace ID on each span and files the span into a bucket for that trace, held in memory. When the first span of a new trace shows up, a timer starts. The processor then waits for a configured period, called the decision wait, which defaults to thirty seconds. When the wait is over, it runs the trace through its policies. If the policies say “keep”, all the spans of that trace are passed on to the next step. If not, they are thrown away and the memory is freed.

Analogy

Picture a mail room where letters for the same case file arrive over several days. The clerk keeps one tray per case number. When a tray has been open long enough, the clerk reads the whole case and decides whether to send it to the archive or shred it. The trays are the in-memory buckets, the open time is the decision wait, and the clerk’s checklist is the set of policies.

The policies you can combine

Policy typeWhat it asks, in plain wordsTypical cost use
Status codeDoes any span show an error?Keep failures
LatencyDid the whole trace take longer than a limit?Keep slow requests
String, numeric, boolean attributeDoes a label have a certain value, such as a premium customer tier or a risky route?Keep business-critical traffic
ProbabilisticKeep a fixed percentage of whatever reaches meBaseline of healthy traffic
Rate limitingKeep at most a set number of spans per secondHard ceiling on volume
Span countDoes the trace have an unusual number of spans?Keep suspiciously large or tiny traces
Trace stateDid an upstream service leave a hint in the trace state?Honor decisions made earlier
Condition expressionDoes a custom rule written in the OpenTelemetry transformation language match?Flexible special cases
And, compositeCombine rules together, or split a total budget across several rules by percentageBudget allocation
DropExplicitly throw away matching tracesRemove known noise

One rule of thumb is worth memorizing: by default, a trace is kept if any policy says “keep”. The policies behave like a list of reasons to save a trace. If you want “keep only when both A and B are true”, you use an explicit “and” policy. Newer versions add extra policies and options, so always read the release notes for the version you run.

Memory: the number that surprises everyone

Tail sampling holds traces in memory, so memory sets the limit. Let us use the platform from Chapter 3, which creates about 5,000 new traces every second, each about 20 kilobytes on the wire. With a thirty-second wait, about 150,000 traces are waiting at any moment. That is roughly 3 gigabytes of data as it travels on the network. Objects held in memory are usually bigger than the same data on the wire, so a careful designer plans for two to three times that amount, which means something like 6 to 9 gigabytes spread across several machines.

The processor also has a cap on the number of traces it holds, which defaults to 50,000. If more traces arrive than the cap allows, the oldest ones are pushed out before their time is up. These are decided too early, usually with too little information. A well-run system watches for this and sizes the cap and the machines to match real traffic, as Chapter 15 explains.

Late spans and the decision cache

Some spans arrive after the decision has been made, perhaps because a service was slow or a message queue delayed them. Without help, these late spans would look like the start of a brand new trace and be judged all alone. To prevent this, the processor can remember recent decisions in a decision cache. A late span for a trace that was already kept is kept too, and a late span for a dropped trace is dropped. This keeps results tidy without keeping everything in memory forever.

One trace, one machine

There is a rule that cannot be bent: all spans of one trace must reach the same tail sampling Collector. If half the spans go to machine A and half to machine B, neither can see the whole story. The usual solution is a first tier of Collectors that use the load balancing exporter. This exporter looks at the trace ID of every span and uses a hashing method to choose the destination machine, so the same trace ID always lands on the same machine. It can discover its destinations from a fixed list, a DNS name, or the Kubernetes service list.

!
Watch out

When the sampling tier grows or shrinks, the mapping from trace ID to machine changes for some traces. Spans from the same trace may briefly land on different machines, and those traces get judged in pieces. Make scaling changes slowly, and prefer fewer, larger machines over constantly resizing pools.

i
Tip

Order matters inside a Collector pipeline. A common safe order puts the memory limiter first, then cheap filtering and enrichment, then the sampling processor, and the batch processor last. Batching before sampling would group spans in ways that make routing and decisions harder.

8The Reference Architecture, Component by Component

Now we put all the pieces together into one picture that works for millions of requests per minute.

flowchart TD
  APP["Applications with OpenTelemetry SDK"] -->|"OTLP"| AGT["Agent Collector on each node"]
  AGT --> GW["Gateway Collector tier"]
  GW --> SM["Span metrics connector"]
  SM --> MET["Metrics backend"]
  GW --> LB["Load balancing exporter using trace ID"]
  LB --> TS1["Tail sampling Collector 1"]
  LB --> TS2["Tail sampling Collector 2"]
  LB --> TS3["Tail sampling Collector 3"]
  TS1 --> HOT["Hot trace backend"]
  TS2 --> HOT
  TS3 --> HOT
  GW -.->|"optional raw copy"| COLD["Cold archive in object storage"]
  CTRL["Budget controller and shared config"] -.-> TS1
  CTRL -.-> TS2
  CTRL -.-> TS3
    
Figure 2: A two-tier Collector design with span metrics taken before sampling and a budget controller steering the sampling tier.

Read Figure 2 from the top. Applications send spans to a small agent Collector running next to them. The agent forwards to a gateway tier. At the gateway, two things happen in parallel. First, a span metrics connector turns spans into counts and timings, so your dashboards see all traffic. Second, spans flow into the load balancing exporter, which routes each trace to one of the tail sampling Collectors. Those Collectors apply the policies and send the survivors to the hot trace backend. An optional side path stores a compressed raw copy in cheap object storage. A budget controller adjusts shared settings.

ComponentJobCost lever it controls
SDK with parent-based samplerCreates spans, respects upstream decisions, may apply a light head sampleApplication CPU, network volume
Agent CollectorClose to the app. Limits memory, adds machine and cluster labels, removes obvious noise, compressesBytes leaving the node
Gateway tierCentral entry point. Scrubs sensitive fields, creates span metrics, forwards onwardMetric accuracy, security
Load balancing exporterSends every span of a trace to the same sampling machineCorrectness of tail decisions
Tail sampling tierHolds traces and applies policies and budgetsBackend and storage cost
Hot trace backendStores kept traces for searchingIngest, storage, query
Cold archiveCheap long-term copy of raw or semi-raw data, queried rarelyLets you sample the hot path harder
Budget controllerMeasures volume and updates settingsKeeps the bill on target

Why two Collector tiers instead of one

A single tier cannot be both a friendly, easy-to-scale receiver and a stateful sampler that needs every span of a trace in one place. Splitting them gives each its own job. The gateway can add and remove machines freely because it holds nothing important in memory for long. The sampling tier is stateful, so it is scaled with more care. This is the same reason big shops separate a “front desk” from a “records room”.

Why take metrics before sampling

If you computed request counts and error rates only from the traces you kept, your charts would be wrong, because you deliberately kept an unfair share of errors. By creating metrics from every span before sampling, your dashboards stay true to the real traffic. Chapter 12 covers this in depth.

i
Tip

Smaller systems do not need this much machinery. A team with a few hundred requests per second can start with head sampling in the SDK plus a single Collector. Grow into the two-tier design only when volume or the need for error-based decisions demands it.

9Designing the Strategy: From Value to Rules

A sampling strategy is a set of answers to the question “which traces are worth their price?”

Sort traces into value tiers

Tier 0

Noise

Health checks, load balancer pings, readiness probes, and chatty internal polling. Drop these first and drop them early.

Tier 1

Must keep

Failed requests, very slow requests, and traces linked to security or payment problems.

Tier 2

High interest

Business-critical routes such as checkout, new releases under watch, and requests from key customers.

Tier 3

Baseline

Ordinary healthy traffic. Keep a small random share so you always know what normal looks like.

Analogy

A hospital emergency room sorts patients at the door. Some need help immediately, some can wait, and a few just need a quick check. Nobody sorts by arrival time alone. Sorting by value is the same idea applied to traces.

The design recipe

1

Measure what you have

Find the number of traces per second, spans per trace, bytes per span, and the share of errors and slow requests. Without these numbers, every later step is guessing.

2

Set the budget

Agree on a daily limit for kept data or money. Include headroom, since traffic is not flat. A common habit is to plan a normal day well below the limit, so that spikes have somewhere to go.

3

Remove the noise

Filter health checks and similar chatter at the agent. Every span removed here saves cost in every later stage.

4

Define the must-keep rules

Write the error rule, the latency rule, and the critical-route rules. Give each its own cap so that none can eat the whole budget.

5

Choose the baseline

Pick a small percentage for healthy traffic and spend what is left of the budget on it. This part flexes with traffic.

6

Protect the metrics

Make sure counts and error rates come from all traffic before sampling.

7

Test with real traffic

Run the rules in a staging system or in a shadow pipeline, and check that the volume matches the plan and that known bad requests were kept.

8

Review on a schedule

Traffic, products, and teams change. Look at the numbers monthly and after any large launch.

The decision flow

flowchart TD
  S["Trace complete or wait time over"] --> N{"Is it noise such as a health check"}
  N -->|"yes"| D1["Drop"]
  N -->|"no"| E{"Does it contain an error"}
  E -->|"yes"| Q1{"Error budget left"}
  Q1 -->|"yes"| K1["Keep"]
  Q1 -->|"no"| R1["Keep a small share only"]
  E -->|"no"| L{"Slower than the latency limit"}
  L -->|"yes"| K2["Keep within the slow budget"]
  L -->|"no"| V{"Business critical route"}
  V -->|"yes"| K3["Keep at a higher percentage"]
  V -->|"no"| B["Keep at the baseline percentage"]
    
Figure 3: A decision flow that sends each trace through noise, error, latency, and importance checks, each with a budget.

Figure 3 shows the order of questions. Noise is thrown away first because it has no value at any price. Errors come next because they are the most valuable, but even they have a budget, since a bad outage can create a flood of failures. Slow requests follow. Business-critical routes get a higher keep rate than ordinary traffic. Everything else lands in the baseline.

10Budget Mechanics: Quotas, Reservoirs, and Feedback Loops

A budget is only real if something enforces it. This chapter shows the tools that do the enforcing.

When the wishes are bigger than the budget

Return to our made-up platform. Suppose the daily budget is 150 gigabytes of kept traces. Our wishes, using one percent of errors, one percent of slow traces, a little for critical routes, and one percent baseline, add up to about 278 gigabytes. They do not fit. A budget forces choices, and good design makes those choices on purpose:

BucketWhat we would likeWhat we allowHow we cut
ErrorsAbout 86 GB50 GBKeep every new kind of error, cap repeats of the same error
Slow tracesAbout 86 GB40 GBKeep the slowest ones first, cap the rest
Critical routesAbout 20 GB20 GBKeep as planned
BaselineAbout 86 GB30 GB (about 0.35%)Lower the percentage
Reserve for spikes–10 GBUntouched on normal days

The total is 150 gigabytes. Notice that no bucket was removed. Each was trimmed in the way that hurts least. Repeated copies of the same error add little knowledge, so trimming them is cheap in information terms.

Reservoir plus percentage

AWS X-Ray popularized a clean pattern: a reservoir plus a fixed rate. In the default rule, one request per second is always traced, and a small percentage of the requests beyond that are also traced. The reservoir guarantees that even quiet endpoints produce some traces. The percentage keeps volume under control on busy ones. You can copy this shape with the OpenTelemetry Collector by combining a rate-limited policy with a probabilistic one.

Splitting one budget across many rules

The composite policy lets you set a maximum number of spans per second for the whole group and then hand out shares by percentage to sub-rules. For example, “forty percent for errors, thirty percent for slow traces, thirty percent for the rest”. When a rule does not use its whole share, the unused part can flow to the others. This is the Collector’s most direct form of a budget.

Error storms: the cost spike that arrives with the outage

Here is the most important trap in cost-aware design. If your rule says “keep every error”, then during a big outage the number of errors can jump twentyfold. Your bill spikes at exactly the moment everyone is already stressed. Worse, a flood of near-identical failures teaches you almost nothing new. The fix is to give the error bucket a ceiling and a smart way of spending it: keep the first few examples of each kind of failure, then thin out the repeats. The Collector’s built-in policies mainly cap total rates. Per-kind throttling is the strength of dynamic samplers such as those in Honeycomb’s Refinery, which adjust the keep rate for each group of similar events so that rare ones are kept and common ones are thinned.

Per-team budgets and showback

In a company with many teams, a single shared budget invites a tug of war. The loudest team wins, and quiet teams lose their traces. A fairer habit is to give each team a share of the budget, based on traffic and importance, and to show every team its own usage every week. This is called showback, because you show the cost without charging it. When teams can see that one chatty loop in their code costs more than their whole error budget, they usually fix it without being asked. Some companies go one step further to chargeback, where the cost is moved to the team’s own budget. Both depend on one simple thing: every span must carry a team label, so that usage can be added up per owner.

The feedback loop

Analogy

A home thermostat does not know the weather forecast. It simply measures the room, compares it with the target, and turns the heating up or down. A sampling feedback loop does the same with volume. It measures the data kept so far today, compares it with the budget line, and nudges the baseline percentage up or down.

flowchart LR
  M["Measure bytes kept per hour"] --> C{"Compare with the budget line"}
  C -->|"over budget"| DN["Lower the baseline percentage"]
  C -->|"under budget"| UP["Raise the baseline percentage"]
  C -->|"on target"| HOLD["Hold steady"]
  DN --> APPLY["Push new settings to Collectors"]
  UP --> APPLY
  HOLD --> M
  APPLY --> M
    
Figure 4: A simple control loop that keeps daily volume near the budget by nudging only the baseline share.

Two design rules keep such a loop safe. First, move only the baseline, never the must-keep rules, so that failures stay visible. Second, change settings in small steps and not too often, because tail sampling holds state and wild swings confuse the people reading charts. Settings can be pushed with a Collector management protocol such as OpAMP, or with plain configuration files delivered by your deployment system.

!
Watch out

When the percentage changes over time, remember to record the percentage in force with every kept trace, or the adjusted counts from Chapter 5 will be wrong. A sample taken at one percent in the morning and half a percent in the evening cannot be counted as if both were the same.

11Data Flow and Lifecycle of a Trace

Follow a single checkout request from the shopper’s tap to long-term storage, and watch where each cost is saved.

1

The edge decides who is trusted

The request reaches the front gateway. The gateway starts a fresh trace and ignores any “keep this trace” flag sent from outside, because outsiders should not control your bill. Chapter 14 explains why.

2

The SDK asks its sampler

Each service runs a parent-based sampler. Because the gateway already set the flag, every service downstream follows the same answer and the trace stays whole.

3

Spans are built and batched

The SDK collects spans in a small buffer and sends them in groups. Grouping lowers network overhead compared with sending one span at a time.

4

The agent trims the noise

The agent Collector on the same machine removes health checks, compresses the data, and adds labels such as the cluster and region.

5

The gateway counts everything

The gateway removes sensitive values and feeds all spans to the span metrics connector. Requests, errors, and durations are counted here, before any sampling.

6

The trace finds its home

The load balancing exporter hashes the trace ID and sends every span of this checkout to the same sampling Collector.

7

The sampling Collector waits and decides

It gathers the spans, waits out the decision wait, and runs the policies. This checkout hit a payment error, so the error rule keeps it, provided the error budget still has room.

8

The trace is stored and searchable

The kept trace goes to the hot backend with its sampling probability attached. An engineer later finds it by searching for the failed payment, and also sees metric charts that were built from all the traffic.

What to shed first when the pipeline is crowded

Even a well-sized pipeline meets a day when traffic is far above plan. It helps to decide in advance, calmly, what gets sacrificed first, rather than letting the machines decide at random. A sensible order is this. First, shrink the baseline of healthy traffic toward zero, since it is the least valuable. Second, thin out repeated copies of the same error. Third, shorten the slow-trace bucket. Last of all, and only in an emergency, touch the rare, unique failures and the critical routes. Writing this ladder down turns a stressful night into a checklist, and it keeps the most precious traces the longest.

What happens when the pipeline is under pressure

Telemetry systems almost always choose availability over perfection. If the sampling tier is overloaded, it is better to shed some telemetry than to slow down the shopper’s checkout. That is why the SDK exporter has a size-limited queue and drops data when the queue is full, and why Collectors use a memory limiter that refuses new data when memory is nearly full. Dropped telemetry under pressure is not elegant, but it protects the real business. Your job is to make this rare, and to notice when it happens, as Chapter 15 describes.

12Keeping the Numbers Honest

Sampling saves money, but it can quietly make your charts lie. Here is how to stop that.

Why biased samples break naive counting

Our smart strategy keeps almost every error and only a thin slice of healthy traffic. If someone counts the stored traces and says “forty percent of requests fail”, they are badly wrong. The sample is deliberately slanted. Counting kept traces answers only the question “what do the kept traces look like?” and never the question “how is the whole system doing?”.

Three ways to keep counts true

Method 1

Metrics from all spans

Use the span metrics connector before sampling to produce request rates, error rates, and latency histograms from every span. Dashboards and alerts should read these, not stored traces.

Method 2

Adjusted counts

When you must count from stored traces, weight each trace by one divided by its keep probability. Backends such as Honeycomb store a sample rate with each event so counts can be multiplied back up.

Method 3

Exemplars

An exemplar is a link from a point on a metric chart to one real trace behind it. You see a latency spike on the chart and click straight to a stored example.

Analogy

A school keeps attendance for every student every day, which is like the metrics. It also keeps full detailed files for a few students who need extra attention, which is like the kept traces. Nobody calculates the school’s attendance rate from the detailed files, because those students were chosen for a reason.

The cost of metrics from spans

Span metrics are not free. Every different combination of labels, such as service, route, status, and customer tier, creates a separate time series, and many backends charge per series. If you add a label with thousands of different values, such as a user ID, the number of series explodes and the bill can be larger than the traces you saved. Choose labels with a small, fixed set of values, and keep high-detail questions for the traces themselves.

Logs that follow their traces

Logs can also be sampled, and a nice trick is to base the choice on the trace ID. If a trace is kept, its logs are kept, and if it is dropped, its logs are dropped. The probabilistic sampler in the Collector supports this for logs, so a kept trace always comes with its log lines and nothing is half-missing. Make sure the same hashing settings are used across tiers, otherwise the two will disagree.

i
Tip

Write down, in one place, which questions are answered by metrics and which by traces. For example: “Is checkout failing more than usual?” is a metric question. “Why did this one customer’s checkout fail?” is a trace question. This simple page ends many arguments about sampling rates.

13Advantages, Disadvantages, and Trade-offs

Every strategy buys something and gives something up. Knowing the exchange rate is half of good design.

What you gain

  • A bill you can predict and defend.
  • Failures and slow requests stay visible, even at low overall rates.
  • Less noise, so engineers find answers faster.
  • Room to grow traffic without growing cost at the same speed.
  • A written plan that teams can discuss and improve.

What you pay

  • More moving parts: extra Collector tiers and routing.
  • Memory and machines for tail sampling.
  • A small delay before a trace appears, equal to the decision wait.
  • Some traces will always be missing, by design.
  • Ongoing care: reviews, alerts, and configuration changes.

Comparing the common strategies

StrategyCost controlKeeps rare, important traces?ComplexityBest for
Fixed percentage at the SDKGood and predictableNoVery lowSmall systems, early stages
Rate limited or reservoirStrong cap per secondPartlyLowSpiky traffic, many small endpoints
Tail policies onlyMedium, depends on capsYesMedium to highSystems where errors matter most
Hybrid: filter, then tail, then baselineStrongYesHighLarge production platforms
Dynamic, feedback-drivenStrongestYesHighestVery large, changing workloads

Trade-offs worth discussing out loud

  • Delay versus quality. A longer decision wait gives better decisions for slow or branching traces, but costs more memory and delays when a trace becomes searchable.
  • Early cuts versus smart cuts. Head sampling saves compute and network but cannot see errors. Tail sampling sees everything but pays for it first.
  • Hot storage versus cold storage. Cheap object storage makes it affordable to keep much more raw data, which may let you sample less. But reading it later is slow, and the processing hops still cost money.
  • Simplicity versus precision. Each extra rule is one more thing to test, explain, and keep working when people leave the team.

14Security and Privacy in Sampling Pipelines

A sampling system looks harmless, but it touches every request and often every secret that rides inside one.

Do not let strangers control your bill

Remember the sampled flag from Chapter 2? It travels in a header that the caller sends. If your public gateway trusts that header, anyone on the internet can send “sampled” on every request and force you to keep one hundred percent of their traffic. Even a small script could multiply your telemetry cost. This is a cost attack, and it is easy to prevent: at the edge, ignore or overwrite the incoming trace headers from untrusted callers and start a new trace with your own decision. For partners you trust, use an allow list.

flowchart LR
  EXT["Outside clients"] -->|"may send trace headers"| EDGE["Edge gateway"]
  PART["Trusted partner"] -->|"allow listed"| EDGE
  EDGE -->|"untrusted headers removed, new trace started"| SVC["Internal services"]
  SVC --> COL["Collectors inside the trust boundary"]
    
Figure 5: The edge gateway decides which incoming trace headers to trust, so outsiders cannot force high sampling.

The sampling tier sees everything

A tail sampling Collector holds full traces in memory, including every attribute. If spans contain email addresses, account numbers, or tokens, that machine is now a place where sensitive data sits for thirty seconds or more. Treat it like a database server: restrict who can log in, encrypt traffic between tiers with TLS, and keep these machines inside your private network.

Remove secrets before they spread

Best practice is to scrub sensitive attributes as early as possible, ideally in the application’s instrumentation and again at the agent or gateway. Collector processors can delete or hash chosen attributes. Doing this before sampling means that even the traces you keep, and the metrics built from them, do not carry secrets. It also makes your policies safer, because rules cannot accidentally depend on private values.

Compliance and retention

Traces are often treated as personal data under privacy laws when they contain user identifiers. Sampling reduces the amount stored, but does not remove the duty. Keep retention periods short for ordinary traces and longer only for a small set kept for investigations. Never use traces as your audit trail. An audit trail must be complete and tamper-evident, while sampled traces are neither.

!
Watch out

Shared multi-team Collectors can leak one team’s data to another through careless routing or policies. If teams have different sensitivity rules, route their telemetry through separate pipelines or at least separate attribute scrubbing rules.

15Monitoring the Sampling Pipeline Itself

A pipeline that watches everything else needs someone watching it. Otherwise it fails silently, and you discover the gap during the outage.

The OpenTelemetry Collector publishes its own metrics about how much data it received, sent, refused, and dropped. The exact metric names change slightly between versions, so treat the table below as the set of questions to ask, then find the matching metric in your version.

QuestionWhere the answer livesWarning sign
How many spans came in, and how many went out?Receiver accepted and exporter sent span countsThe ratio drifts far from the planned keep rate
Is anything being refused?Receiver refused span counts, memory limiter activityAny sustained refusal
Are exports failing?Exporter failed span counts, queue size and capacityQueue near full, failures rising
How many traces are in memory?Tail sampling traces-on-memory gaugeApproaching the configured cap
Are traces judged too early?Tail sampling dropped-too-early counterAny steady growth
Are late spans common?Tail sampling late span ageMany spans older than the decision wait
How slow are decisions?Tail sampling decision latencyLatency rising with load
Which policy keeps most traces?Sampled and not-sampled counts per policyOne policy swallowing the budget
Are we on budget?Bytes or spans exported per hour compared with the lineProjected daily total above the budget

Alerts that matter

  • Budget burn rate. Alert when today’s volume is on track to exceed the daily budget, early enough that someone can act, such as at the halfway point of the day.
  • Error bucket exhausted. If the error cap runs out before the day ends, you are no longer seeing all kinds of failures. Page someone to review.
  • Memory pressure. High memory use in the sampling tier means traces will start being dropped.
  • Silence. A sudden drop in kept traces is as worrying as a spike. It may mean a broken route or a changed service name.

A canary trace for peace of mind

One of the best checks is a canary trace. A small scheduled job sends a synthetic request that is guaranteed to look like an error. Every few minutes you check that this trace arrives in the backend. If it does not, then something in the pipeline between the application and the store is broken, and you learn about it before real users do.

i
Tip

Draw one dashboard that has three simple lines: the budget, the actual volume, and the projected end-of-day total. Managers and engineers can both read it without training, and it turns “are we overspending?” from an argument into a glance.

16Design Patterns and Anti-patterns

Patterns are solutions that many teams discovered the hard way. Anti-patterns are the traps they fell into.

Patterns that work

Pattern

Noise first

Drop health checks and other chatter at the agent. It is the cheapest saving in the whole system.

Pattern

Parent-based everywhere

Every service honors the incoming decision, so traces stay whole across teams and languages.

Pattern

Reservoir plus percentage

A guaranteed floor for quiet endpoints and a percentage above it for busy ones.

Pattern

Per-service budgets

Each team gets a share and sees its own usage, which encourages good habits through simple visibility.

Pattern

Metrics before sampling

Dashboards read counts built from all spans, never from the kept sample.

Pattern

Cold archive

Raw data in cheap storage for rare deep dives, so the hot store can stay lean.

Pattern

Canary trace

A synthetic error trace proves the whole pipeline still works from end to end.

Pattern

Config as code

Sampling settings live in version control with review, and roll out gradually.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What people do

Each service flips its own coin, for example keeping ten percent of its own spans without looking at the parent’s decision.

Why it hurts

With five services each keeping ten percent independently, only one trace in a hundred thousand is complete. The rest are fragments that nobody can read.

Do this instead

Use a parent-based sampler in every service, so one decision at the start covers the whole journey.

ANTI-PATTERN 02Avoid
What people do

“Keep all errors” with no cap and no review of what counts as an error.

Why it hurts

An outage or a noisy client that triggers harmless errors can multiply the bill overnight. Some systems also mark ordinary events, like a missing item in a search, as errors, and the sampler faithfully keeps them all.

Do this instead

Give the error bucket a ceiling, thin out repeated copies, and review which spans are allowed to be marked as errors.

ANTI-PATTERN 03Avoid
What people do

Putting several tail sampling Collectors behind an ordinary round-robin load balancer.

Why it hurts

Spans of one trace scatter across machines. Each machine sees a piece, decides on a piece, and the stored result is a pile of partial traces that were judged on partial evidence.

Do this instead

Use a first tier that routes by trace ID, so every span of a trace reaches the same sampling machine.

ANTI-PATTERN 04Avoid
What people do

Reading business dashboards from stored traces in a sampled system.

Why it hurts

Numbers shift every time someone adjusts a sampling rule, and error rates look much higher than they truly are.

Do this instead

Build metrics from all spans before sampling, and use traces only for investigation.

17Best Practices and Common Mistakes

A short checklist of habits, followed by the slips that appear most often in real teams.

Best practices

  • Start with the budget, then derive rates. Never pick a percentage first and discover its cost later.
  • Standardize resource labels. Service name, environment, team, and version on every span make per-team budgets and targeted rules possible.
  • Keep the policy list short and readable. A handful of clear rules beat dozens of overlapping ones.
  • Set decision wait from data. Look at how long your longest ordinary traces really take, then add a safety margin.
  • Size memory for peak, not average. Traffic spikes arrive when you least want the pipeline to drop data.
  • Test changes on a copy of real traffic first. Compare predicted and actual volumes before promoting a rule.
  • Roll out gradually. Change one tier or one team at a time, and watch the pipeline metrics.
  • Explain the sampling to your users. Engineers who understand why a trace is missing do not waste hours hunting for it.

Common mistakes and quick fixes

MistakeWhat you will noticeFix
Decision wait shorter than real tracesTraces missing their last spans, or judged without the error that appears at the endMeasure real durations and raise the wait, or handle long flows separately
Memory cap too low for trafficTraces dropped too earlyRaise the cap and add machines
Different hash settings across tiersLogs or spans disagree about which traces are keptUse one shared hashing setup everywhere
Rules that depend on missing attributesPolicies never matchCheck attribute names against real data, and test each rule
High-cardinality labels on span metricsMetric bill larger than the trace billLimit labels to small, fixed sets
Trusting incoming sampled flags from the internetSudden volume jumps from one clientOverwrite or ignore untrusted headers at the edge
Changing rates without recording themCounts that no longer add upStore the keep probability with each trace
i
Tip

Long-running work such as batch jobs, queue consumers, and workflows that last minutes or hours does not fit a thirty-second decision wait. A common approach is to let such flows start new, linked traces at each stage, and to sample those by simple rules such as errors and a small percentage, instead of waiting for the entire workflow to finish.

18Real-World and Industry Examples

The ideas in this guide were shaped by real systems. Here is what the public record shows, plus one clearly labeled made-up story.

Google Dapper: one in 1,024 was enough

Google’s Dapper paper described tracing a tiny share of requests, around one in 1,024 at first, on very busy services. Because the same code paths run billions of times, even a thin slice showed the common patterns. Google also described moving toward a target number of traces per unit of time, which is the same budget thinking used in this guide.

Uber and Jaeger: adjusting per operation

Jaeger was created at Uber and later donated to the Cloud Native Computing Foundation. It supports remote sampling, where a central service hands each application its sampling rules, and adaptive sampling, which tries to give each service and operation a steady target of traces per second. A rarely called endpoint gets a higher rate and a busy one gets a lower rate, so both stay visible.

AWS X-Ray: the reservoir and the fixed rate

X-Ray lets you define sampling rules in one central place. Each rule combines a reservoir, a guaranteed number of traces per second, with a fixed percentage for the rest. The default rule traces the first request each second and a small percentage of what follows. Teams change rules in the console without redeploying applications, which makes budget changes quick.

Honeycomb Refinery: tail sampling with per-group fairness

Honeycomb offers Refinery, a proxy that makes sampling decisions after seeing whole traces. It includes samplers that adjust rates by groups of similar events, so frequent, repetitive events are thinned heavily while rare ones are kept. It attaches the sample rate to the kept data so that counts can be rebuilt correctly later. This is the closest public example of the per-kind error throttling described in Chapter 10.

Grafana Tempo and Alloy: cheaper storage, flexible pipeline

Grafana Tempo stores traces in object storage and is built to keep trace storage cost low, which lets teams keep more data before sampling pressure arrives. Grafana Alloy bundles OpenTelemetry Collector components, including tail sampling and load balancing, so the architecture in Chapter 8 can be assembled with Grafana’s tools as well.

An illustrative online retailer (made up for teaching)

Imagine a retailer with 5,000 requests per second, as in our earlier example. Before the redesign, it kept a flat ten percent of everything. The bill was large, and during a payment outage the team still could not find examples of one rare failure. After the redesign, health checks were dropped at the agent, all kinds of payment errors were kept with a daily cap, checkout kept a higher percentage, and ordinary browsing dropped to a small baseline. Metrics came from all spans. In this imagined case the kept volume fell by more than half while the failures that mattered became easier to find. Treat the numbers as a story that shows the shape of the benefit, not as a promise.

19Deploying the Design on AWS

The architecture is the same in any cloud. Here is how its parts map onto common AWS building blocks.

Part of the designCommon AWS choiceThings to watch
Agent CollectorSidecar on Amazon ECS tasks, or a node-level agent on Amazon EKSKeep CPU and memory limits modest, but enough for bursts
Gateway tierA scalable service on EKS or ECS behind a Network Load BalancerLong-lived gRPC connections can stick to a few machines, so check that load spreads evenly
Load balancing and sampling tierA set of Collector pods with stable network names, discovered through DNS or the Kubernetes service listScale slowly, protect against sudden removal, use memory-based scaling signals
Hot trace backendAWS X-Ray, a managed Grafana and Tempo setup, or a vendor of your choiceUnderstand how it charges: per trace, per span, or per gigabyte
Cold archiveAmazon S3, written by a Collector exporter or through a delivery streamUse compression and lifecycle rules to move old data to cheaper classes
Metrics from spansAmazon Managed Service for Prometheus, CloudWatch, or another metrics storeMind label cardinality and per-series charges
Configuration deliveryVersion control plus your deployment pipeline, or a Collector management protocolRoll out in stages, keep an easy rollback

Where cloud cost sneaks in

Cloud providers usually charge for data moving between availability zones. In a tail sampling design, spans from every zone must travel to the machine that owns their trace, so a large share of the traffic crosses zones by necessity. The best defenses are to cut before the hop (drop noise, trim large attributes), compress the data, and avoid sending data that will be thrown away anyway. Check your own price list, because these charges differ by service and region.

Keep the stateful tier calm

  • Avoid interruptible machines such as spot instances for the sampling tier. When a machine disappears, every trace in its memory disappears too.
  • Drain before removing. Give machines time to finish decisions on the traces they hold before they shut down.
  • Limit disruptions. Use settings that keep a minimum number of sampling pods available during upgrades and node replacements.
  • Scale on memory and traces held, not only on CPU, since memory is the real limit.
i
Tip

Make one change at a time and give it a full day of traffic before judging it. Sampling effects, such as the baseline share and the error cap, only show up clearly across a whole daily cycle.

20Under the Hood: Algorithms and Data Structures

Sampling looks like a simple yes-or-no choice. Behind that choice sit a few clever, old ideas from computer science.

Hash and threshold: choosing without talking

How can fifty services agree on which traces to keep without sending each other messages? They all read the same trace ID, turn part of it into a number, and compare that number with a cutoff. If the number is below the cutoff, the trace is kept. For a one percent rate, the cutoff is set so that one percent of all possible numbers fall below it. Because every service sees the same ID and uses the same cutoff, they reach the same answer. This is called deterministic sampling.

Analogy

Think of a school raffle where every ticket has a number from 0000 to 9999, and everybody agrees that tickets from 0000 to 0099 win. Any teacher who looks at a ticket can say “winner” or “loser” without asking anyone. Nobody needs a central list, and the answer never changes.

This trick only works fairly if trace IDs are truly random. Some older ID schemes put a timestamp at the front of the ID, as AWS X-Ray’s format does. A sampler that looked only at that front part would treat traces from different moments unequally. Samplers therefore read the random-looking part of the ID, and you should make sure that any custom ID generator you use produces enough randomness.

Token bucket: the coin jar for rate limits

A rate limit such as “keep at most 100 traces per second” is often built with a token bucket. Imagine a jar that receives a new coin every hundredth of a second, up to a maximum. Each trace you keep costs one coin. When the jar is empty, you must drop. A short burst can be handled with the saved-up coins, but the long-term average can never exceed the refill speed. This is how a reservoir plus a ceiling behaves in practice.

Reservoir sampling: a fair pick from an endless stream

What if you want exactly 100 traces per minute, chosen fairly, without knowing in advance how many will arrive? A classic method called reservoir sampling solves it. Keep the first 100 traces. For the next trace, number 101, flip a weighted coin: with a chance of 100 out of 101, throw it in and replace a randomly chosen trace already in the reservoir. For trace 102, the chance is 100 out of 102, and so on. At the end of the minute, every trace that arrived had exactly the same chance of being in the reservoir. The method needs only as much memory as the number of traces you want to keep.

Time buckets: expiring traces without scanning everything

A tail sampler must act on each trace once its waiting time ends. If it checked every trace in memory every second, it would waste a lot of effort. Instead it groups traces by the second in which they first appeared, in a ring of buckets as long as the decision wait. Every second the ring turns, and only the oldest bucket is examined. The work done is proportional to the number of traces that are due, not to the number held. This conceptual picture matches how the processor behaves, though the details differ between versions.

flowchart LR
  IN["Incoming spans"] --> MAP["Map keyed by trace ID"]
  MAP --> RING["Ring of one second buckets"]
  RING -->|"oldest bucket expires"| EVAL["Evaluate policies"]
  EVAL -->|"keep"| OUT["Forward spans"]
  EVAL -->|"drop"| FREE["Free the memory"]
  EVAL --> CACHE["Decision cache for late spans"]
    
Figure 6: A conceptual view of tail sampling bookkeeping. Spans are filed by trace ID, expire by time bucket, and leave a small decision record behind.

Consistent hashing: seats around a round table

The load balancing exporter has to decide which sampling machine owns each trace ID. A simple method, “take the ID number and divide by the number of machines, keep the remainder”, reshuffles almost every trace whenever a machine is added or removed. A consistent hashing ring avoids this. Picture machines sitting at fixed seats around a round table, with each trace ID landing at a spot on the table and belonging to the next machine clockwise. When a new machine sits down, it takes over only the spots between itself and its neighbor, and the rest stay put. With ten machines, adding an eleventh moves roughly one eleventh of the traces, not nearly all of them.

Concurrency and back pressure

Many network threads write spans into memory at once. Spans from different traces rarely collide, but a single enormous trace with thousands of spans can become a hot spot where many threads fight for the same record. Good systems limit the number of spans kept per trace, evaluate policies on a pool of workers, and refuse new data politely when memory is nearly full. This refusal is called back pressure. It passes the “slow down” signal back up the chain, so each tier protects itself instead of collapsing.

i
Tip

Telemetry systems are usually “best effort”. They accept that a little data may be lost in return for staying fast and available. It is the same bargain as a postal service choosing to keep delivering most of the mail during a storm rather than stopping completely.

21Capacity Planning: A Worked Example

Numbers turn a diagram into a plan. Let us size the pipeline for our made-up platform, step by step.

We keep the same platform as before, with 5,000 traces per second on average, 40 spans per trace, and 500 bytes per span. We also add three new assumptions that you must measure in your own system: peak traffic is twice the average, one sampling machine can handle about 50,000 spans per second, and compression shrinks data to about a quarter of its size.

StepCalculationResult
Average span rate5,000 traces per second times 40 spans200,000 spans per second
Peak span rateTwice the average400,000 spans per second
Raw data rate at peak400,000 spans times 500 bytesAbout 200 MB per second
Compressed rate at peakOne quarter of the raw rateAbout 50 MB per second
Sampling machines needed at peak400,000 divided by 50,000 spans per machine8 machines
Traces held in memory at peak10,000 traces per second times a 30 second wait300,000 traces
Memory in wire size300,000 traces times 20 KBAbout 6 GB
Memory with overheadTwo and a half times the wire sizeAbout 15 GB, roughly 1.9 GB per machine

Eight machines, each with room for several gigabytes, would handle this peak. A careful team would round up for safety, perhaps choosing machines with 8 gigabytes each, and would test the guess with real load.

The surprise: the hop can cost more than the storage

Now look at the zone-crossing traffic. Suppose the pipeline runs in three availability zones. A span’s trace is owned by a sampling machine in a random zone, so about two thirds of spans must cross a zone border. On an average day, the compressed stream is about 25 megabytes per second, and two thirds of it crossing zones is about 17 megabytes per second, or about 1,440 gigabytes per day. If we assume a made-up combined charge of two cents per gigabyte for crossing zones, that is roughly $29 per day.

Compare that with the hot backend from Chapter 10. At 150 gigabytes per day and our made-up ten cents per gigabyte, ingestion costs $15 per day. In this example, the network hop costs about twice as much as the backend ingest after sampling. The lesson is not that these exact numbers are right. The lesson is that sampling the backend is not the only big line on the bill, so a complete design also trims data before it travels.

i
Tip

If twenty percent of spans are health checks and other noise, dropping them at the agent cuts the zone-crossing cost by about twenty percent as well, which in our example saves nearly $6 a day without losing a single useful trace.

!
Watch out

Every price in this chapter is invented for teaching. Real prices differ by provider, region, and contract. Build your own table with your own numbers before making a decision.

22Reducing Volume Without Dropping Whole Traces

Sampling removes whole traces. Several other levers shrink each trace, and they often save more with less risk.

Analogy

Before deciding how many suitcases to carry on a trip, a wise traveler first removes the things that are not needed and packs the rest neatly. Slimming each trace is the packing step. Sampling is the choice of how many suitcases to take.

LeverWhat it doesRisk to watch
Filter noise spansRemoves health checks, readiness probes, and internal polling at the agentMake sure the filter matches only the noise
Switch off chatty instrumentationTurns off libraries that create spans for tiny internal steps, such as every file read or cache lookupYou lose detail that might help in rare cases
Trim attribute valuesCuts very long values, such as full database statements or message bodies, to a safe lengthTruncated text may hide the clue you needed
Set span limitsCaps the number of attributes, events, and links on a span through SDK settingsExtra items are silently dropped
Remove unused attributesDeletes labels that nobody searches onCheck usage first
Compress trafficMakes every network hop and export smallerUses a little CPU
Fix the code behind giant tracesA page that makes hundreds of tiny database calls creates hundreds of spans. Fixing the pattern removes the spans and speeds up the pageNeeds developer time, and pays back twice

Be careful with span-level dropping

It is tempting to keep the trace but drop low-value child spans from the middle of it. The danger is that the surviving spans point to parents that no longer exist, and tracing tools may show broken trees or float the orphans to the top. Unless your tooling clearly supports re-attaching children to a surviving ancestor, prefer removing spans at the source by turning off the noisy instrumentation, which keeps each tree whole.

Spans versus events

A span can carry small notes called events, such as an exception message with its stack trace. Stack traces can be large, and a busy failing service may attach one to every failed call. Limit how many events and how long each may be, and keep the full details in logs that are linked to the trace by its ID. This shifts bulk text to the system that is cheapest at storing it.

i
Tip

To find the best slimming targets, ask your backend which span names and which attributes use the most bytes. The top five items usually account for a surprising share of the total, and a small change to each can beat a whole week of tuning sampling percentages.

23Advanced Topic: Consistent Probability Sampling and Mixed Rates

What happens when different services, or different stages, sample at different rates, and you still want correct counts?

The problem

Imagine the front service keeps ten percent of traces and a downstream billing service keeps one percent. If each makes an independent random choice, then the billing service’s kept traces do not line up with the front service’s kept traces. You get fragments. Now imagine a head sample at ten percent followed by a tail stage that keeps half of what it sees. The overall chance that a healthy trace survives is five percent, and anyone counting later has to know that.

The idea: one luck number per trace

Analogy

Give every trace a single “luck number” between 0 and 1, fixed at birth by its random trace ID. Each service owns a cutoff. A service with a ten percent cutoff keeps traces whose luck number is below 0.10. A service with a one percent cutoff keeps those below 0.01. Every trace kept by the one percent service is also kept by the ten percent service, like smaller Russian dolls nested inside bigger ones. Nothing is lost between layers, and everyone agrees about which traces form a complete set.

This property is what the word consistent means in this setting. It lets teams choose different rates for different needs without breaking traces apart.

Passing the cutoff along

To count correctly later, a downstream tool needs to know the cutoff that was used. The OpenTelemetry community has been defining how to record the sampling threshold inside the trace state, which travels with the trace. With this information, each kept span can say “I stand for this many spans” and a backend can add up correct totals even when different parts of the system used different rates, or when the rate changed during the day.

What to do while support is uneven

Support for these features varies between languages, SDK versions, Collector versions, and backends, and parts of the specification are still maturing. Check what your own versions offer before relying on it. If you cannot rely on it, a simple and safe fallback exists. Use one rule per bucket, as in Chapter 10, and store the keep probability for that bucket as an attribute on every kept trace’s root span. Then count by adding the weight of each kept trace, which gives you the same result with less elegance.

i
Tip

Whatever method you choose, write down the rule: “to count requests from stored traces, multiply each trace by this number”. If a chart ever disagrees with the metrics, that one sentence tells everyone where to look first.

24Frequently Asked Questions

Short answers to the questions people ask most often.

Q1Should I start with head sampling or tail sampling?
Start with parent-based head sampling at a modest rate, plus noise filtering. Add tail sampling when you need guarantees such as “keep all errors”, and when you have the machines to hold traces in memory.
Q2How much should I keep?
Work backwards from the budget. Keep every distinct kind of failure, a sensible share of slow requests, higher rates for critical routes, and a small baseline of healthy traffic. Many busy systems end up with a baseline between a fraction of one percent and a few percent.
Q3Will sampling make debugging harder?
Only if you sample blindly. A strategy that keeps failures and slow requests actually makes debugging easier, because the useful traces are not buried under healthy ones.
Q4Can I change the rates without redeploying applications?
Yes, if the decisions happen in Collectors or through a remote sampling service. SDK settings usually need a restart unless you use a remote sampler.
Q5Why do some traces have missing spans?
Common causes are services that ignore the parent’s decision, spans arriving after the decision wait, traces split across machines, or dropped data under memory pressure. The pipeline metrics from Chapter 15 point to which one it is.
Q6How long should the decision wait be?
Longer than almost all of your normal traces take from first span to last, plus a margin. The thirty-second default suits many web systems. Measure yours, and remember that a longer wait needs more memory.
Q7Does tail sampling work with message queues and background jobs?
It works best for request and response flows. For workflows that last a long time, use linked traces per stage and simpler rules, as described in Chapter 17.
Q8If storage is cheap, do I still need sampling?
Often yes. Storage is only one cost. Network, processing, indexing, queries, and engineer attention all grow with volume. Cheap storage lets you sample less aggressively, but rarely lets you stop.
Q9How do I test a new sampling rule safely?
Run it against a copy of real traffic or in a staging system with realistic load. Check that the volume matches the prediction and that known failing requests were kept. Then roll it out to a small group before everyone.
Q10How do I explain this to people who control the budget?
Show value per dollar. Present the cost of keeping everything, the cost of the chosen plan, and a few real incidents where the kept traces led to a fast fix. A single chart with budget, actual volume, and projected total makes the point quickly.

25Summary and Key Takeaways

Key Takeaways

  • Start from the budget. Decide how much data or money you can spend each day, then choose rules that spend it on the most valuable traces.
  • Cut early and decide late. Remove noise at the agent and use head sampling for cheap savings, then use tail sampling in the Collector to keep errors, slow requests, and critical routes.
  • Keep traces whole. Use parent-based samplers everywhere and route every span of a trace to the same sampling machine by trace ID.
  • Cap every bucket. Give errors, slow traces, and critical routes their own ceilings, so an outage cannot turn into a bill spike.
  • Count from everything, investigate from the sample. Build metrics from all spans before sampling, and record the keep probability so adjusted counts stay correct.
  • Protect and watch the pipeline. Do not trust outside sampling flags, scrub secrets early, and monitor memory, drops, and budget burn with a canary trace as a final check.
  • Review on a schedule. Traffic and products change, so revisit the numbers monthly and after big launches.