Keep the traces that matter and drop the boring ones — here is the architecture.

Head-based sampling makes its decision before it knows anything interesting about a request. That is exactly backwards. The traces worth keeping are the slow ones and the failed ones — and you cannot tell which those are until the request is done.

Decide at the tail

Tail-based sampling buffers the spans of a request until it completes, then decides whether to keep it. Slow, errored, or otherwise unusual traces are kept in full; the fast, boring majority is dropped. You store a fraction of the volume and almost none of the signal.

The catch: buffering

Someone has to hold the spans until the trace finishes. That means a stateful sampling layer — usually in the collector — that groups spans by trace id and applies the policy on completion.

tail_sample
| keep where duration > p99(duration)
| keep where status == "error"
| keep 0.01 otherwise

Tune the policy, not the rate

The power of tail sampling is in the policy. Keep everything anomalous, keep a thin baseline for context, and drop the rest. The result is a trace store that is small enough to afford and complete enough to trust when it matters.