Distributed Tracing

How traces work, head vs tail sampling, and why traces are the highest-leverage signal for microservice debugging.

Overview

Where did the time go?

Distributed tracing answers a question metrics and logs can't: "this user request took 5 seconds; where did the time go across our 12 services?"

The mechanics

  • Trace — One user request crossing service boundaries
  • Span — One unit of work: one service handling part of the request, or one outbound RPC
  • Links — Spans share a trace_id; parent-child links via span_id and parent_span_id
  • Span contents — Name, start time, duration, status (OK / error), attributes such as http.method, db.statement, user.id

A trace is the tree of spans for one request. Drawn as a waterfall, each span's duration is a horizontal bar; the slowest span on the critical path is the bottleneck.

The hard part: sampling

Tracing every request in full is prohibitively expensive at scale: 10K req/sec × hundreds of attributes per span × tens of spans per trace is TB/day.

  • Head sampling — Decide at the gateway: "this trace is sampled at 10%" — Errors and slow requests are rare, so you miss what you most want to see
  • Tail sampling — Wait for the trace to finish; keep 100% of error and slow traces, 1% of normal — Needs a tail-sampling collector (OpenTelemetry tailsampling processor, or Honeycomb's sampler) and ~30s of span buffering

> [!TIP] 2026 best practice: tail sampling for error and slow traces plus head sampling for normal ones, or Honeycomb's similar "dynamic sampling."

OpenTelemetry tracing is ubiquitous

Auto-instrumentation packages (otel-instrumentation-http, otel-instrumentation-pg, and so on) create spans for HTTP, gRPC, database and message-broker calls with no code changes. Business-logic spans are added explicitly. Context propagates via W3C traceparent headers.

Good at, bad at

  • Latency investigations: exact breakdown of where time went — High-volume aggregation (use metrics)
  • Dependency mapping: who calls whom, in what order — Long-running processes: a 1-hour batch job has too many spans
  • Root cause for distributed failures: which downstream call failed — Anomaly detection across populations (use metrics or wide events)
  • N+1 detection: 50 sequential DB spans where one batched call would do

Key concepts

Trace
One user request's end-to-end journey across services. Identified by a single trace_id.
Span
One unit of work (a service's handling of part of a request, or an outbound call). Has name, start, duration, status, attributes.
Parent-child link
Spans form a tree via parent_span_id. The root span is the entry point; child spans are downstream work.
Head sampling
Decide at trace start (e.g., 10% of all requests). Easy but misses error / slow traces proportionally.
Tail sampling
Decide at trace end. 100% of errors / slow + 1% of normal. More useful, requires hold-and-decide infrastructure.
W3C traceparent
HTTP header standard for trace propagation. Every OTel SDK supports it; cross-vendor compatible.

When to use it

  • Latency investigations across services — find the slow span
  • Microservice dependency mapping — trace shows the actual graph, not the diagram
  • New-service rollouts — verify the request path matches your design
  • N+1 / chatty-pattern detection — sequential identical spans are the smell

Interview questions

  • How does head sampling differ from tail sampling — and which should I use?
  • What's the difference between a span attribute and a baggage item?
  • Distributed tracing has overhead. How do you minimise it?

Sign up free to read the full topic

More SRE topics

Start free on Cariara · Coding problems · System design studio