Why propagation matters
A trace is only useful if every hop participates. Context propagation passes trace_id and span_id across process boundaries so child spans link to the parent.
Break propagation → orphan spans → useless fragmented traces.
W3C Trace Context
Standard HTTP headers:
traceparent: 00-{trace-id}-{parent-span-id}-{flags}
tracestate: vendor-specific baggage
- trace-id — 128-bit, same for entire request
- parent-span-id — caller's span becomes parent
- flags — sampling bit (
01= sampled)
All modern tracers (OTel, Datadog, Jaeger) support W3C. Legacy: B3 (Zipkin), Jaeger headers — use OTel propagators to translate.
Inject and extract
Client span start → inject context into HTTP headers →
Server middleware extract → child span with parent link
Framework middleware (gRPC interceptors, HTTP filters) must call OTel propagator — don't hand-roll header parsing.
Baggage
Baggage carries arbitrary key-value metadata across services (e.g., tenant_id, experiment_flag).
Unlike span attributes, baggage propagates to all downstream spans automatically.
Caution: baggage rides on every request — keep it small; never put secrets or large blobs.
Async and messaging
Harder cases:
| Pattern | Approach |
|---|---|
| Kafka consumer | Extract trace context from message headers; consumer span links to producer |
| Thread pool | Context must be attached to runnable (OTel Context.attach()) |
| Batch jobs | Link span to triggering trace via Link (not parent) |
Lost context in thread hops is the #1 Java/Python tracing bug.
Sampling deep dive
Head sampling (decision at trace start):
- Probabilistic: 1% of traces
- Parent-based: respect upstream decision
- Rate limiting: max N traces/sec
Tail sampling (decision after trace completes — OTel Collector):
- Keep if: error, latency > threshold, specific attribute
- Requires buffering complete traces — memory cost
Always sample: status=ERROR OR duration>2s
Otherwise: 1% probabilistic
Tail sampling catches the slow checkout you'd miss with pure head sampling.
Coherent sampling
If parent is sampled, children must be sampled (parent-based). Otherwise traces fragment — you see a payment span with no gateway parent.
Interview scenarios
"Trace breaks at the message queue" — producer must inject headers into Kafka record headers; consumer extracts.
"Too many traces, high cost" — tail sample: 100% errors, 1% success; head sample at edge for volume cap.
Cross-reference
- Distributed Systems — retries create duplicate spans; idempotency keys help correlate
- Cardinality topic — span attributes are high-cardinality safe (unlike metric labels)
Link forward
SLOs use trace-derived latency SLIs; Production Debugging uses exemplars to jump from metrics to these traces.