Tailsampling Processor
contrib, k8s
Maintainers: @portertech, @Logiraptor, @jmacd
Source: opentelemetry-collector-contrib
Supported Telemetry
Overview
The tail sampling processor samples traces based on a set of defined policies. All spans for a given trace MUST be received by the same collector instance for effective sampling decisions. Before performing sampling, spans will be grouped bytrace_id. Therefore, the tail sampling processor can be used directly without the need for the groupbytraceprocessor.
This processor must be placed in pipelines after any processors that rely on context, e.g. k8sattributes. It reassembles spans into new batches, causing them to lose their original context.
Please refer to config.go for the config spec.
The following configuration options are required:
policies(no default): Policies used to make a sampling decision
always_sample: Sample all traceslatency: Sample based on the duration of the trace. The duration is determined by looking at the earliest start time and latest end time, without taking into consideration what happened in between. Supplying no upper bound will result in a policy sampling anything greater thanthreshold_ms.numeric_attribute: Sample based on number attributes (resource and record) bymin_valueand/ormax_valueprobabilistic: Sample a percentage of traces. Read a comparison with the Probabilistic Sampling Processor.status_code: Sample based upon the status code (OK,ERRORorUNSET)string_attribute: Sample based on string attributes (resource and record) value matches, both exact and regex value matches are supportedtrace_state: Sample based on TraceState value matchestrace_flags: Sample if the sampled trace flag was set on any span in the tracerate_limiting: Sample based on the rate of spans per second.bytes_limiting: Sample based on the rate of bytes per second using a token bucket algorithm implemented by golang.org/x/time/rate. This allows for burst traffic up to a configurable capacity while maintaining the average rate over time. The bucket is refilled continuously at the specified rate and has a maximum capacity for burst handling.span_count: Sample based on the minimum and/or maximum number of spans, inclusive. If the sum of all spans in the trace is outside the range threshold, the trace will not be sampled.boolean_attribute: Sample based on boolean attribute (resource and record).ottl_condition: Sample based on given boolean OTTL condition (span and span event).and: Sample based on multiple policies, creates an AND policynot: Sample based on the opposite result a single policy, creates a NOT policydrop: Drop (not sample) based on multiple policies, creates a DROP policycomposite: Sample based on a combination of above samplers, with ordering and rate allocation per sampler. Rate allocation allocates certain percentages of spans per policy order. For example if we have set max_total_spans_per_second as 100 then we can set rate_allocation as follows- test-composite-policy-1 = 50 % of max_total_spans_per_second = 50 spans_per_second
- test-composite-policy-2 = 25 % of max_total_spans_per_second = 25 spans_per_second
- To ensure remaining capacity is filled use always_sample as one of the policies
sampling_strategy(default =trace-complete): Controls decision timing and evaluation scope.trace-completeevaluates accumulated trace data on timer handling;span-ingestevaluates each incoming batch on ingest, finalizing terminal outcomes immediately and non-terminal traces on cleanup. See Sampling Strategies for details.decision_wait(default = 30s): Time before timer handling for a trace. Whensampling_strategyistrace-complete, this controls decision timing. Whensampling_strategyisspan-ingest, this controls pending cleanup finalization timing.decision_wait_after_root_received(default = 0s): Additional root-span-based acceleration for timer handling. Whensampling_strategyistrace-complete, this can make decisions earlier. Whensampling_strategyisspan-ingest, this can finalize pending traces earlier on cleanup.0sdisables it.num_traces(default = 50000): Number of traces kept in memory.expected_new_traces_per_sec(default = 0): Expected number of new traces (helps in allocating data structures)decision_cache: Options for configuring caches for sampling decisions. You may want to vary the size of these caches depending on how many ākeepā vs ādropā decisions you expect from your policies. For example, you may allocate a largernon_sampled_cache_sizeif you expect most traces to be dropped. Additionally, if using, configure this as much greater thannum_tracesso decisions for trace IDs are kept longer than the span data for the trace.sampled_cache_size(default = 0): Configures amount of trace IDs to be kept in an LRU cache, persisting the ākeepā decisions for traces that may have already been released from memory. By default, the size is 0 and the cache is inactive.non_sampled_cache_size(default = 0) Configures amount of trace IDs to be kept in an LRU cache, persisting the ādropā decisions for traces that may have already been released from memory. By default, the size is 0 and the cache is inactive.
sample_on_first_match: Make decision as soon as a policy matchesdrop_pending_traces_on_shutdown: Drop pending traces on shutdown instead of making a decision with the partial data already ingested.maximum_trace_size_bytes: The maximum size a trace can reach in bytes, traces larger than this size will be immediately dropped from the tail sampling processor in order to protect the system.
Sampling Strategies
Thesampling_strategy setting controls both decision timing and what data evaluators use:
trace-complete(default): evaluates on the timer path using accumulated trace data (afterdecision_wait, or earlier after root arrival whendecision_wait_after_root_receivedis set). This is the most flexible mode for policies, but with later decisions and higher in-memory/storage pressure.span-ingest: evaluates each incoming batch at ingest time without re-evaluating previously ingested batches. Terminal outcomes (sampled/dropped) finalize immediately; non-terminal outcomes stay pending and are finalized asnot sampledduring cleanup without policy re-evaluation.
- Policy compatibility:
trace-completesupports stateful policies;span-ingestrejects them. - Timer controls: in
trace-complete,decision_waitanddecision_wait_after_root_receivedaffect decision timing; inspan-ingest, they affect pending cleanup/finalization timing. - Late spans: decision caches remain important in both modes for spans that arrive after in-memory trace data is gone.
Policy Decision Flow
Each policy will result in a decision, and the processor will evaluate them to make a final decision:- When thereās a ādropā decision, the trace is not sampled;
- When thereās an āinverted not sampleā decision, the trace is not sampled; Deprecated
- When thereās a āsampleā decision, the trace is sampled;
- When thereās a āinverted sampleā decision and no ānot sampleā decisions, the trace is sampled; Deprecated
- In all other cases, the trace is NOT sampled
and or composite policy, the resulting decision will be either sampled or not sampled. The āinvertedā decisions have been deprecated, please make use of either
- the
droppolicy to explicitly not sample select traces, or - the
notpolicy to sample based on the opposite of the sampling decision of a policy (e.g., if a policy returns a āsampleā decision ->notreturns a ānot sampleā decision)
Bytes Limiting Policy
Thebytes_limiting policy uses a token bucket algorithm implemented by golang.org/x/time/rate to control the rate of data throughput based on the accurate protobuf marshaled size of traces calculated using the OpenTelemetry Collectorās built-in ProtoMarshaler.TracesSize() method. This policy is particularly useful for:
- Volume control: Limiting the total amount of trace data processed per unit time
- Burst handling: Allowing short-term spikes in data volume while maintaining long-term rate limits
- Memory protection: Preventing downstream systems from being overwhelmed by large traces
Configuration
Thebytes_limiting policy supports the following configuration parameters:
bytes_per_second: The sustained rate at which bytes are allowed through (required)burst_capacity: The maximum number of bytes that can be processed in a burst (optional, defaults to 2xbytes_per_second)
Token Bucket Algorithm
The policy implements a token bucket algorithm where:- Tokens represent bytes: Each token in the bucket represents one byte of trace data
- Continuous refill: Tokens are added to the bucket at the configured
bytes_per_secondrate - Burst capacity: The bucket can hold up to
burst_capacitytokens for handling traffic bursts - Consumption: When a trace arrives, tokens equal to the trace size are consumed from the bucket
- Rejection: If insufficient tokens are available, the trace is not sampled
Example Configuration
- A sustained throughput of 1 MB/second (1,048,576 bytes/s)
- Burst traffic up to 5 MB (5,242,880 bytes) before rate limiting kicks in
- Smooth handling of variable trace sizes and timing
A Practical Example
Imagine that you wish to configure the processor to implement the following rules:-
Rule 1: Not all teams are ready to move to tail sampling. Therefore, sample all traces that are not from the team
team_a. - Rule 2: Sample only 0.1 percent of Readiness/liveness probes
-
Rule 3:
service-1has a noisy endpoint/v1/name/{id}. Sample only 1 percent of such traces. -
Rule 4: Other traces from
service-1should be sampled at 100 percent. - Rule 5: Sample all traces if there is an error in any span in the trace.
-
Rule 6: Add an escape hatch. If there is an attribute called
app.force_samplein the span, then sample the trace at 100 percent. -
Rule 7: Force spans with
app.do_not_sampleset totrueto not be sampled, even if the result of the other rules yield a sampling decision.
Scaling collectors with the tail sampling processor
This processor requires all spans for a given trace to be sent to the same collector instance for the correct sampling decision to be derived. When scaling the collector, youāll then need to ensure that all spans for the same trace are reaching the same collector. You can achieve this by having two layers of collectors in your infrastructure: one with the load balancing exporter, and one with the tail sampling processor. While itās technically possible to have one layer of collectors with two pipelines on each instance, we recommend separating the layers in order to have better failure isolation.Probabilistic Sampling Processor compared to the Tail Sampling Processor with the Probabilistic policy
The probabilistic sampling processor and the probabilistic tail sampling processor policy work very similar: based upon a configurable sampling percentage they will sample a fixed ratio of received traces. But depending on the overall processing pipeline you should prefer using one over the other. As a rule of thumb, if you want to add probabilistic sampling and⦠ā¦you are not using the tail sampling processor already: use the probabilistic sampling processor. Running the probabilistic sampling processor is more efficient than the tail sampling processor. The probabilistic sampling policy makes decision based upon the trace ID, so waiting until more spans have arrived will not influence its decision. ā¦you are already using the tail sampling processor: add the probabilistic sampling policy. You are already incurring the cost of running the tail sampling processor, adding the probabilistic policy will be negligible. Additionally, using the policy within the tail sampling processor will ensure traces that are sampled by other policies will not be dropped.FAQ
Q. Why am I seeing high values for the error metricsampling_trace_dropped_too_early?
A. This is likely a load issue. If the collector is processing more traces in-memory than the num_traces configuration
option allows, some will have to be dropped before they can be sampled. Increasing the value of num_traces can
help resolve this error, at the expense of increased memory usage.
Monitoring and Tuning
See documentation.md for the full list metrics available for this component and their descriptions.Dropped Traces
A circular buffer is used to ensure the number of traces in-memory doesnāt exceednum_traces. When a new trace arrives, the oldest trace is removed. This can cause a trace to be dropped before itās sampled. To reduce the chance of this happening, either increase num_traces or decrease decision_wait. Both of those options increase memory usage.
Number of Traces Dropped
decision_wait.
To track how long traces remain in the buffer use:
decision_wait. Values close to decision_wait are at risk of being dropped if trace volume increases.
Slow Sampling Evaluation
decision_wait, increasing the chance of traces being dropped before sampling.
Itās therefore recommended to consume this componentās output with components that are fast or trigger asynchronous processing.
Late-Arriving Spans
A spanās arrival is considered ālateā if it arrives after its traceās sampling decision is made. Late spans can cause different sampling decisions for different parts of the trace. There are two scenarios for late arriving spans:- Scenario 1: While the sampling decision of the trace remains in the circular buffer of
num_traceslength, the late spans inherit that decision. That means late spans do not influence the traceās sampling decision. - Scenario 2: (Default, no decision cache configured) After the sampling decision is removed from the buffer, itās as if this component has never seen the trace before: The late spans are buffered for
decision_waitseconds and then a new sampling decision is made. - Scenario 3: (Decision cache is configured) When a ākeepā decision is made on a trace, the trace ID is cached. The component will remember which trace IDs it sampled even after it releases the span data from memory. Unless it has been evicted from the cache after some time, it will remember the same ākeep traceā decision.
- Calculate the percentage of spans arriving late with
otelcol_processor_tail_sampling_sampling_late_span_age{le="+Inf"} / otelcol_processor_tail_sampling_count_spans_sampled. Note thatcount_spans_sampledrequires enabling theprocessor.tailsamplingprocessor.metricstatcountspanssampledfeature gate. - Visualize lateness as a histogram to see how much it can be reduced by increasing
decision_wait.
Sampling Decision Frequency
Sampled Frequency To track the percentage of traces that were actually sampled, use:Tracking sampling policy
To better understand which sampling policy made the decision to include a trace, you can enable tracking the policy responsible for sampling a trace via theprocessor.tailsamplingprocessor.recordpolicy feature gate.
When this feature gate is set, this will add additional attributes on each sampled span:
| Attribute | Description | Present? |
|---|---|---|
tailsampling.policy | Records the configured name of the policy that sampled a trace | Always, unless trace was sampled by the decision cache |
tailsampling.composite_policy | Records the configured name of a composite subpolicy that sampled a trace | When composite policy used |
tailsampling.cached_decision | Records whether a trace was sampled by the decision cache | When decision cache used |
Disable invert decisions
The invert sampling decisions (InvertSampled and InvertNotSampled) have been deprecated, however, they are still available. To disable them before their complete removal, you can use the processor.tailsamplingprocessor.disableinvertdecisions feature gate. When this feature gate is set, sampling policy invert_match will result in a Sampled or NotSampled decision instead of InvertSampled or InvertNotSampled. This applies to the string, numeric, and boolean tag policy.
If you disable invert decisions, you can make use of a drop policy to explicitly not sample select traces or a not policy to sample based on the opposite of a sampling decision.
Policy Evaluation Errors
Attributes
| Attribute Name | Description | Type | Values |
|---|---|---|---|
decision | The sampling decision | string | sampled, not_sampled, dropped |
policy | Name of the policy | string | |
sampled | Whether the sampling decision was sampled or not, false can mean either not sampled or dropped | bool |
Last generated: 2026-04-13