Observability and reliability
Distributed tracing
A trace is the full path of one request across services, built from timed spans. Trace context has to be passed on at every hop, or the waterfall breaks into pieces.
A trace is the full journey of one request across services. A span is one timed step inside that journey, such as the HTTP handler, a SQL query or a call to a payment provider. Spans nest. A parent span owns its children. The resulting tree is the waterfall you read during an incident, and it shows where the time went.
The big idea
Think of a restaurant order that passes through several stations. Each station stamps the ticket with its start and end time, and the ticket carries the same order number throughout. The order number is the trace ID. Each stamp is a span. Without the order number on every ticket, the kitchen gets a pile of unrelated slips.
Walking through it
A checkout request crosses four services. Each box is a span, and the numbers are the time each one took:
The waterfall makes the slow step obvious. Here is how to find it during a regression. Say p99 for POST /v1/checkout jumped from 180 ms to 900 ms after a midday deploy:
- The metrics show the regression. The rate is steady, the errors are flat, and p99 is up.
- In the trace backend, filter for
service=checkout, the route, and duration over 500 ms, since the deploy. - Open a slow trace. The gateway takes 12 ms, the checkout handler 880 ms, inventory 11 ms, and the payments call 860 ms. Inside payments, the Postgres span takes 840 ms on a
SELECT ... FOR UPDATE. - Compare with a fast trace from earlier. The same query took 40 ms.
- The hypothesis is lock contention or a missing index from a schema migration. Check the recent migrations and the blockers in
pg_stat_activity. - Mitigate first. Roll back the migration, or switch off the new code path, and confirm p99 recovers. Find the root cause afterward.
Without a span around the database call, that hour goes to grepping logs and blaming the wrong service.
Context has to travel
For the trace to stitch across services, each service must forward the trace context. The W3C standard header is traceparent:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01The parts are the version, the trace ID (32 hex characters), the parent span ID (16 hex characters), and the flags, which say whether the trace was sampled. Frameworks and service meshes often inject the header automatically. Custom HTTP clients, queues and workers often need it added by hand. If one hop drops the header, the waterfall splits into orphan traces.
Async work needs the same care. Put the context in the message headers for Kafka, SQS or Pub/Sub. A job that starts with a fresh trace ID cannot show which user click queued it.
Common mistakes
- No spans around database and cache calls. The gap then shows up as "the app is slow" with no cause.
- Losing the header at a queue. The worker's trace starts from nothing, and the link to the user request is gone.
- Only generic spans. "HTTP 200 in 90 ms" does not say which domain step was slow. Add a few manual spans for business steps.
Check yourself
- A trace shows the gateway and checkout, but the payments service appears as a separate, unrelated trace. What is missing?
The
traceparentheader was not forwarded on the call from checkout to payments. - Why does a span around the Postgres call matter in this waterfall? It shows that the 840 ms went to one query, which points to the lock, not to the application code.
Key takeaways
- A trace is a tree of timed spans for one request.
- The
traceparentheader and message metadata connect spans across services and queues. - In an incident, filter for slow traces, open one, and find the widest span.
Further learning
Deciding which traces to keep is in trace sampling. The instrumentation library is in OpenTelemetry.
Google's Dapper paper is the origin of production tracing, and it introduced the trace and span model most systems still use.
Dapper, a Large-Scale Distributed Systems Tracing Infrastructureresearch.google
Related
Observability: logs and traces
Structured logs, distributed tracing and OpenTelemetry, so you can ask new questions of a running system instead of guessing which dashboard to open.
Trace sampling
You rarely keep every trace at scale. Head sampling decides at the start and is simple. Tail sampling decides at the end, so it can keep the errors and the slow requests.
OpenTelemetry
A vendor-neutral standard for producing traces, metrics and logs. Instrument once in the app, send to a collector, and choose the backend later without rewriting code.