Tracing & Performance

Trace IDs vs Span IDs vs Correlation IDs

Trace IDs, span IDs, and correlation IDs all tie requests together — but differently. Learn what each does and how they enable end-to-end tracing.

When a request touches multiple services, three identifiers work together to stitch it into a single story: a trace ID, multiple span IDs, and correlation IDs that propagate context. The confusion is understandable — they sound like they do the same thing, and they're often used interchangeably in documentation. But they serve different purposes, and understanding the trace id vs span id distinction is essential to reading waterfalls, debugging distributed systems, and building systems that actually correlate failures across services.

This guide clarifies what each identifier does, how they relate, and why they matter when you're chasing a slow request across your infrastructure.

What is a Trace ID?

A trace ID is a unique identifier assigned to the entire user-initiated request as it flows through your system. It is generated once, at the entry point (usually an API gateway, web server, or frontend), and then propagated through every service that touches that request. All events, logs, and spans related to that request carry the same trace ID.

Think of it as a thread number: every piece of work related to one user's request gets the same thread ID, so you can pull all those pieces together later and understand what happened to that one request.

// A trace ID is generated when the request enters the system
{
  "trace_id": "7a8c9b2e1f3d4c6a",
  "timestamp": "2026-07-03T14:22:31Z",
  "service": "api-gateway",
  "request_path": "/api/orders/checkout"
}

The trace ID stays the same as the request bounces from service to service. The API gateway generates it, then passes it to the web service, which passes it to the payment service, which passes it to the inventory service. Every log line, every error, every span written during that request references the same trace ID, which is how you rebuild the full picture of what happened.

What is a Span ID?

A span ID identifies a single, specific unit of work within a trace. While a trace ID represents the entire request journey, a span ID represents one discrete operation — a database query, an RPC call, a function execution, or a message queue operation.

A single trace contains many spans. If your checkout request hits a database, calls an external payment API, and updates inventory, that's at least three spans (plus spans for internal function calls). Each span has its own ID, its own start and end time, its own duration, and its own tags.

// Multiple spans within a single trace
{
  "trace_id": "7a8c9b2e1f3d4c6a",
  "spans": [
    {
      "span_id": "1a2b3c4d5e6f7g8h",
      "operation": "http.server",
      "service": "web-service",
      "duration_ms": 245
    },
    {
      "span_id": "9i0j1k2l3m4n5o6p",
      "operation": "db.query",
      "service": "web-service",
      "duration_ms": 42,
      "parent_span_id": "1a2b3c4d5e6f7g8h"
    },
    {
      "span_id": "7q8r9s0t1u2v3w4x",
      "operation": "http.client",
      "service": "web-service",
      "duration_ms": 183,
      "parent_span_id": "1a2b3c4d5e6f7g8h"
    }
  ]
}

Spans form a tree: each span can have a parent span (the operation that initiated it) and child spans (the operations it triggered). That tree structure is exactly what you see in a span waterfall — it shows the nesting and timing of every operation within that single request.

A span doesn't have to be a network call. It can be a database query, a cache lookup, a queue operation, or even a function that took long enough to be worth tracking. The point is to measure where time actually goes.

What is a Correlation ID?

A correlation ID (sometimes called a context ID or request ID) is an umbrella term for any identifier passed between services to tie logs and events together. In practice, the trace ID itself serves as the correlation ID — it's the identifier that correlates all the work.

However, some teams use "correlation ID" to mean something slightly different: a synthetic identifier generated by the frontend or client and passed to the backend. This is most common in microservices architectures where you want to correlate not just traces, but logs from multiple systems that don't all speak the same observability protocol.

// Frontend generates a correlation ID and includes it in every request
const correlationId = crypto.randomUUID();

fetch('/api/orders/checkout', {
  headers: {
    'X-Correlation-ID': correlationId,
  }
});

In a tracing system like distributed tracing, the correlation ID is almost always the trace ID itself. But in logging-heavy systems or when you're stitching together data from multiple sources, you might use correlation IDs to tie together logs that don't have trace context.

How They Work Together

Here's the hierarchy:

  1. One trace ID per request. Generated at entry, propagated everywhere.
  2. Many span IDs per trace. Each service creates spans for its own work.
  3. Correlation ID = trace ID. In tracing systems, they're the same thing; in logging systems, correlation IDs tie together logs that lack trace context.

The trace ID is the master key; everything else hangs off it. When you look up a trace in your observability tool, you provide the trace ID, and the tool fetches all the spans (and their associated logs, tags, and context) that share that ID.

Trace ID: 7a8c9b2e1f3d4c6a
├─ Span ID: 1a2b3c4d5e6f7g8h (http.server, web-service, 245ms)
│  ├─ Span ID: 9i0j1k2l3m4n5o6p (db.query, 42ms)
│  └─ Span ID: 7q8r9s0t1u2v3w4x (http.client, payment-api, 183ms)
└─ Span ID: 5y6z7a8b9c0d1e2f (http.server, inventory-service, 120ms)

Propagating IDs Across Services

For tracing to work across services, the trace ID (and often the parent span ID) must be passed from one service to the next. This happens through context propagation — the mechanism by which you attach identifiers to outgoing requests so that downstream services know they're part of the same trace.

Most tracing libraries handle this automatically. When you make an HTTP call, the library adds a header like traceparent (W3C standard) or x-trace-id (custom), telling the downstream service, "This is part of trace 7a8c9b2e1f3d4c6a; the caller was span 1a2b3c4d5e6f7g8h."

The downstream service reads that header, creates its own spans under the same trace ID, and does the same thing when it calls the next service. Without this propagation, cross-project tracing would be impossible — each service would have its own trace, and you'd never connect them.

// The tracing library handles propagation automatically
const response = await fetch('https://payment-service/charge', {
  method: 'POST',
  // The SDK automatically adds:
  // traceparent: 00-7a8c9b2e1f3d4c6a-1a2b3c4d5e6f7g8h-01
  // This tells payment-service this is part of trace 7a8c9b2e1f3d4c6a
});

Where You See Them: Reading a Waterfall

When you open a trace in LightTrace, you're looking at one trace ID and all its spans. The waterfall visualization shows the span hierarchy — which operations ran serially, which in parallel, and which one consumed the most time.

Every row is a span. The span ID isn't usually displayed (it's there, but hidden), because what matters is the operation name, the service, the duration, and the hierarchy. But the span IDs are how the tool knows which span is the parent and which are children.

If a slow database query hides inside what looks like a fast endpoint, the waterfall reveals it because the query span — with its own span ID — is nested under the endpoint span, showing that the slowness comes from there, not from network latency or downstream services.

Trace ID and span ID work best when they're automatically injected by your instrumentation. If you're manually passing IDs around your codebase, you're doing extra work for the same result. Use a tracing library that handles propagation for you.

Why This Matters for Production Debugging

When something is slow or broken in production, these IDs are how you go from "a request failed" to "service A made 200 database calls when it should have made five, and that's the bottleneck."

You see the trace ID in an error message or a dashboard. You look it up. The waterfall shows every span, every service, every database query, every RPC call. The span IDs are wired into the parent-child relationships, so you can immediately spot where time went.

Compare throughput vs latency at the span level, spot the N+1 query problem, or see that one service is starving waiting for another. All of this is possible because every unit of work has a span ID, and every request has a trace ID tying it together.

Without trace IDs and span IDs, you're back to reading logs and hoping they mention the same request. With them, your observability tool does the work of correlation for you.

Start tracking errors in minutes

See trace IDs, span IDs, and full cross-service waterfalls in action — point your SDK at LightTrace and trace your next slow request.

Trace IDs and span IDs are the scaffolding of distributed tracing. A trace ID stitches together one request across all services; span IDs break that request into discrete operations so you can measure and analyze each piece. Together, they turn a distributed system into something observable and debuggable.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.