How to Cut Trace Volume by 98% Without Losing Signal (OpenTelemetry + Datadog)

How to Cut Trace Volume by 98% Without Losing Signal (OpenTelemetry + Datadog)

Enterprise Developer Platforms

What this stack does: Reduces OpenTelemetry trace ingestion volume by dropping low-value traces after completion, while preserving errors, latency outliers, and critical business paths. Demonstrated reduction: 1.87 million traces to approximately 27,000 — a 98% cut with full Span Metrics accuracy maintained.

Purpose: Control APM cost and storage at the collector layer, upstream of Datadog ingestion, without sacrificing visibility into failures and degraded requests.

Prerequisites:

  • OpenTelemetry Collector (contrib distribution) v0.90+
  • Datadog Agent or Datadog OTLP exporter configured
  • Access to collector configuration YAML
  • Baseline understanding of your service’s P99 latency and error rate

Step 1 — Understand why tail-based sampling, not head-based

Head-based sampling decides at trace initiation: the root span is retained or dropped before downstream spans exist. This means errors deep in a call stack may be sampled out before they’re observed.

Tail-based sampling buffers all spans for a trace and evaluates them after completion. This enables decisions based on actual outcomes — errors, latency violations, or custom attributes — at the cost of memory overhead. For production systems where errors and slow requests are the signal you need to keep, tail-based is the correct choice.

Step 2 — Configure the Tail Sampling Processor

processors:
  tail_sampling:
    decision_wait: 30s          # Buffer duration; tune to your p99 latency + safety margin
    num_traces: 50000           # Max traces buffered in memory
    policies:
      - name: errors-policy
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow-requests
        type: latency
        latency: { threshold_ms: 750 }
      - name: critical-checkout
        type: string_attribute
        string_attribute: { key: "http.route", values: ["/checkout", "/payment"] }
      - name: dev-environment
        type: string_attribute
        string_attribute: { key: "deployment.environment", values: ["dev", "staging"] }
      - name: force-sample
        type: boolean_attribute
        boolean_attribute: { key: "force_sample", value: true }
      - name: probabilistic-fallback
        type: probabilistic
        probabilistic: { sampling_percentage: 1 }
      - name: drop-health-checks
        type: composite
        composite:
          max_total_spans_per_second: 0
          policy_order: [ drop-all ]
          composite_sub_policy:
            - name: drop-all
              type: string_attribute
              string_attribute: { key: "http.route", values: ["/health", "/metrics", "/ping", "/ready"] }

Policy order matters. Drop policies execute last — the drop-health-checks policy must appear after any keep policies or it overrides them.

Step 3 — Wire it into the collector pipeline

service:
  pipelines:
    traces:
      receivers:  [otlp]
      processors: [memory_limiter, tail_sampling, batch]
      exporters:  [datadog]

Set memory_limiter before tail_sampling — the buffer can grow during traffic spikes, and memory protection upstream prevents OOM conditions.

Step 4 — Set decision_wait to match your p99 latency

The decision_wait parameter defines how long the collector holds a trace before making a retention decision. Set it at your service’s p99 latency plus a 20% buffer. A value too low drops slow traces before they’re complete; too high creates unbounded memory growth during traffic spikes. Start at 30 seconds for most services and tune from metrics.

Step 5 — Validate Span Metrics are unaffected

Tail-based sampling affects trace storage, not Span Metrics computation. Span Metrics (RED: request rate, error rate, duration) are computed on the full traffic volume before sampling decisions are applied. Verify this in your Datadog APM Service Catalog after the first 24 hours of production configuration — all service health indicators should reflect unsampled traffic.

The Trap

The most common configuration mistake is applying drop policies before keep policies in the policy list. Because tail-based sampling evaluates policies in order and stops at the first match, a health-check drop policy placed before an error-code keep policy will silently drop error traces that happen to originate from a /health endpoint. Always structure your policy order: specific keep rules first, general keep rules second, probabilistic fallback third, drop rules last.

Cited References

Source: Datadog

Content created with AI assistance and reviewed for accuracy.

💬

Join the conversation

Stack Insiders is our free community for readers who want to go deeper — share resources, ask questions, and connect with others across every vertical we cover.

Join Stack Insiders →

Newsletter coming soon.

Curated digests across AI, biohacking, photography, travel, and more. Be the first to know when we launch.