Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
Monitoringintermediate

Distributed Tracing

Instrumenting database calls so a slow request can be attributed correctly, and correlating traces with database-side diagnostics.

2 min readIntermediateUpdated Edit this page

Server-side metrics measure what the database did. Tracing measures what the request experienced — and the gap between the two is where connection pools, retries and N+1 queries hide.

What a database span should carry

with tracer.start_as_current_span("db.query") as span:
    span.set_attribute("db.system", "postgresql")
    span.set_attribute("db.name", "shop")
    span.set_attribute("db.operation", "SELECT")
    span.set_attribute("db.sql.table", "orders")
    # The normalised statement, never the one with literal values.
    span.set_attribute("db.statement", "SELECT * FROM orders WHERE customer_id = $1")
    span.set_attribute("db.pool.wait_ms", pool_wait_ms)

Instrument the pool separately

The single most valuable database tracing attribute is connection acquisition time, as a span or an attribute of its own. It distinguishes three situations that look identical from the outside:

ObservationMeaning
Short acquisition, long queryThe query is slow — optimise it
Long acquisition, short queryThe pool is exhausted — see Connection Exhaustion
Long acquisition, long queryThe database is saturated; queries hold connections longer

Finding N+1 patterns

A trace showing one request with 200 nearly identical short database spans is the clearest possible diagnosis of an N+1 query. No server-side metric reveals it — each individual query is fast, and the total looks like ordinary traffic.

Alert on span count per request rather than only on duration.

Correlating with the database

Propagate the trace id into the database session so slow query logs and traces can be joined:

-- PostgreSQL: appears in log_line_prefix with %a
SET application_name = 'checkout-service:trace-4f2a9c';
-- Or as a comment the engine records in its statistics.
SELECT /* trace_id=4f2a9c */ * FROM orders WHERE customer_id = $1;

Comment-based propagation is supported by several APM tools and lets a slow query in pg_stat_statements or the slow log be traced back to the request that issued it.

Sampling

Tracing every request is expensive. A workable arrangement:

  • Sample a small percentage of normal traffic for baseline behaviour.
  • Always sample errors.
  • Always sample requests exceeding a latency threshold — tail-based sampling, where the decision is made after the request completes.

Tail-based sampling is what makes tracing useful for performance work: you keep the slow requests, which are the ones worth looking at.