---
title: Distributed Tracing
url: https://doc.liz6.com/en/distributed-systems/08-observability/01-distributed-tracing
locale: en
area: distributed-systems
tags:
- distributed-systems
- observability
date: 2026-06-30
modified: 2026-07-16
description: 'After a request spans over a dozen microservices, how do you know where the time was spent? Distributed tracing assigns a trace ID to each request. Cross-service call relationships pass the span context (trace ID + parent span ID + sampling flag) via context propagation, piecing together fragments scattered across nodes into a complete call chain. Sampling strategies are key to cost control: full sampling is too expensive, fixed-ratio sampling misses tail errors, and tail-based sampling decides which traces to retain after completion.'
---

# Distributed Tracing

> After a request spans over a dozen microservices, how do you know where the time was spent? Distributed tracing assigns a trace ID to each request. Cross-service call relationships pass the span context (trace ID + parent span ID + sampling flag) via context propagation, piecing together fragments scattered across nodes into a complete call chain. Sampling strategies are key to cost control: full sampling is too expensive, fixed-ratio sampling misses tail errors, and tail-based sampling decides which traces to retain after completion.

## Dapper (Google 2010) → OpenTelemetry

Google's Dapper paper inspired the open-source ecosystem: Zipkin (Twitter) → Jaeger (Uber) → OpenTracing + OpenCensus → **OpenTelemetry** (CNCF, current standard).

## Trace and Span

<svg viewBox="0 0 720 406" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Trace and Span Structure: A Trace consists of multiple Spans, each recording several core fields">
  <rect width="720" height="406" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Trace and Span Structure: A Trace consists of multiple Spans</text>
  <rect x="50" y="38" width="620" height="38" rx="8" fill="#4f46e5"/>
  <text x="64" y="61" font-size="12.5" fill="#ffffff"><tspan font-weight="700">Trace</tspan> — The collection of all spans for an end-to-end request</text>
  <line x1="70" y1="76" x2="70" y2="101" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="70" y1="101" x2="90" y2="101" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="90" y="84" width="580" height="34" rx="8" fill="#0d9488"/>
  <text x="104" y="105" font-size="12.5" fill="#ffffff"><tspan font-weight="700">Span</tspan> — A record of an RPC call, containing the following 7 fields</text>
  <line x1="110" y1="118" x2="110" y2="327" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="110" y1="147" x2="130" y2="147" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="134" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="151" font-size="12" font-weight="700" fill="#115e59">trace_id</text>
  <text x="260" y="151" font-size="11" fill="#475569">Unique ID for the trace</text>
  <line x1="110" y1="177" x2="130" y2="177" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="164" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="181" font-size="12" font-weight="700" fill="#115e59">span_id</text>
  <text x="260" y="181" font-size="11" fill="#475569">Unique ID for this span</text>
  <line x1="110" y1="207" x2="130" y2="207" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="194" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="211" font-size="12" font-weight="700" fill="#115e59">parent_id</text>
  <text x="260" y="211" font-size="11" fill="#475569">ID of the caller span (root span has no parent)</text>
  <line x1="110" y1="237" x2="130" y2="237" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="224" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="241" font-size="12" font-weight="700" fill="#115e59">operation</text>
  <text x="260" y="241" font-size="11" fill="#475569">"GET /api/users" / "SELECT * FROM users"</text>
  <line x1="110" y1="267" x2="130" y2="267" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="254" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="271" font-size="12" font-weight="700" fill="#115e59">start/end</text>
  <text x="260" y="271" font-size="11" fill="#475569">Timestamps</text>
  <line x1="110" y1="297" x2="130" y2="297" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="284" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="301" font-size="12" font-weight="700" fill="#115e59">status</text>
  <text x="260" y="301" font-size="11" fill="#475569">OK / ERROR (including error details)</text>
  <line x1="110" y1="327" x2="130" y2="327" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="314" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="331" font-size="12" font-weight="700" fill="#115e59">attributes</text>
  <text x="260" y="331" font-size="11" fill="#475569">Custom tags (http.status_code, db.statement, ...)</text>
  <rect x="50" y="352" width="620" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="64" y="368" font-size="12" fill="#3730a3">Key structure: parent_id stitches spans scattered across service nodes back into a tree—</text>
  <text x="64" y="384" font-size="12" fill="#3730a3">The root span has no parent; child spans record the caller's span_id.</text>
</svg>

<svg viewBox="0 0 720 370" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Span Call Hierarchy Tree: Parent-child structure of a /api/order request">
  <rect width="720" height="370" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Span Call Hierarchy Tree: Parent-child structure of a /api/order request</text>
  <rect x="50" y="40" width="620" height="30" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="64" y="60" font-size="12.5" font-weight="700" fill="#3730a3">Trace: user request → /api/order</text>
  <line x1="70" y1="70" x2="70" y2="102" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="70" y1="102" x2="90" y2="102" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="90" y="82" width="560" height="40" rx="8" fill="#4f46e5"/>
  <text x="104" y="100" font-size="12.5" font-weight="700" fill="#ffffff">Span A · GET /api/order</text>
  <text x="104" y="115" font-size="10.5" fill="#e0e7ff">root span (no parent)</text>
  <line x1="110" y1="122" x2="110" y2="212" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="110" y1="158" x2="150" y2="158" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="150" y="138" width="500" height="40" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="164" y="156" font-size="12" font-weight="700" fill="#3730a3">Span B · auth-service.ValidateToken</text>
  <text x="164" y="171" font-size="10.5" fill="#4f46e5">Validate user token</text>
  <line x1="110" y1="212" x2="150" y2="212" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="150" y="192" width="500" height="40" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="164" y="210" font-size="12" font-weight="700" fill="#3730a3">Span C · order-service.CreateOrder</text>
  <text x="164" y="225" font-size="10.5" fill="#4f46e5">Create order</text>
  <line x1="170" y1="232" x2="170" y2="268" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="170" y1="268" x2="210" y2="268" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="210" y="248" width="440" height="40" rx="8" fill="#0d9488"/>
  <text x="224" y="266" font-size="12" font-weight="700" fill="#ffffff">Span D · postgres: INSERT INTO orders</text>
  <text x="224" y="281" font-size="10.5" fill="#ccfbf1">Actual database write operation (leaf span)</text>
  <rect x="50" y="308" width="620" height="44" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="64" y="328" font-size="12" fill="#115e59">parent_id stitches spans from auth-service / order-service / postgres back into a tree:</text>
  <text x="64" y="344" font-size="12" fill="#115e59">The root span (A) has no parent; child spans record the caller's span_id, determining the hierarchy.</text>
</svg>

## Context Propagation

Cross-service trace propagation is critical—each service must know "which span of which trace I am":

```
HTTP: traceparent: 00-<trace_id>-<span_id>-<trace_flags>
W3C Trace Context (standard): traceparent header
gRPC: metadata (grpc-trace-bin)
Kafka: message headers
```

`trace_flags`: bit 0 = SAMPLED (whether sampled), other bits reserved.

Internal implementation: The tracer creates a new span (as a child of the caller span) when calling downstream services, placing `trace_id` + `new_span_id` into outbound headers. Downstream middleware/libraries (HTTP client, gRPC stub, DB driver) extract the trace context when receiving the response, automatically creating the child span.

## Sampling

Full collection → overhead is unacceptable (~1μs overhead per span + network/storage costs). Sampling strategies are needed:

- **Head-based (probabilistic)**: Decide whether to sample at the start of the trace (random 1/100 or 1/1000). Simple, but may miss traces containing errors.
- **Tail-based (intelligent)**: Cache all spans (local buffer), and decide whether to retain them after the trace completes based on the result (ERROR? latency > SLO?). Ensures errors are not lost, but has high memory overhead. OpenTelemetry's `tailsampling` processor implements this strategy.

## Jaeger Deployment (Reference)

```bash
# Agent (one per host, receives spans sent by apps via UDP)
# Collector (central, receives from agents → writes to storage)
# Storage (Cassandra/Elasticsearch)
# Query (UI + API)
```

## References

- **Paper**: "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure" (Google, 2010)
- **OpenTelemetry**: opentelemetry.io/docs/specs/otel/trace
- **Jaeger**: jaegertracing.io

*Keywords: distributed tracing, Dapper, OpenTelemetry, Span, trace context propagation, W3C Trace Context, sampling*
