Skip to content

Instantly share code, notes, and snippets.

@yanisurbis
Created November 11, 2024 11:57
Show Gist options
  • Save yanisurbis/aad525c127f0d2051b1aee238c69d005 to your computer and use it in GitHub Desktop.
Save yanisurbis/aad525c127f0d2051b1aee238c69d005 to your computer and use it in GitHub Desktop.

Understanding Distributed Tracing: Following Requests Across Microservices

Imagine ordering a pizza online. Your request doesn't just go to one place - it travels through multiple systems: the ordering system, payment processor, kitchen management system, and delivery tracking. How do companies ensure they can follow your order's journey through all these different systems? This is where distributed tracing comes in.

What is Distributed Tracing?

Distributed tracing is like putting a unique tracking number on a package, but for software requests. When you click "order" on a website, that action creates a request that might need to hop through dozens of different services. Each service adds its own information while maintaining a connection to the original request.

The Need for Standardization

As microservices architectures grew more complex, different companies developed their own ways to track requests:

  • Twitter created Zipkin (with B3 headers)
  • Google developed Dapper
  • Many others created their own solutions

This led to a problem: services using different tracing systems couldn't easily share trace information. It was like having tracking numbers that only worked within one courier company.

Enter Trace Headers

Two main standards emerged to solve this problem:

1. B3 Headers

Named after "BigBrotherBird" (Zipkin's original name at Twitter), B3 headers look like this:

b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90

Each part tells a story:

  • The first section is the Trace ID (like an order number)
  • The second is the Span ID (like a step in the process)
  • The third indicates sampling decisions
  • The last is the Parent Span ID (linking to the previous step)

2. W3C Traceparent

The more recent, standardized approach looks like this:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

This format provides:

  • Version number (00)
  • Trace ID (unique identifier for the entire request)
  • Parent ID (identifier for the immediate parent operation)
  • Trace flags (sampling and other control information)

Core Concepts of Distributed Tracing

Traces

A trace represents the complete journey of a request through a distributed system. Think of it as the entire story of a request, from the moment a user clicks a button until they receive a response. Each trace has a unique Trace ID that remains constant throughout the journey.

Example Trace ID:

4bf92f3577b34da6a3ce929d0e0e4736

Spans

Spans are the building blocks of a trace. Each span represents a single operation within the trace:

  • A database query
  • An HTTP request
  • A function call
  • A microservice operation

Each span contains:

  • Start and end timestamps
  • Operation name
  • Parent span ID (except for the root span)
  • Tags and attributes for additional context

Example Span structure:

{
    "spanId": "00f067aa0ba902b7",
    "operation": "database_query",
    "startTime": "2024-11-11T10:00:00Z",
    "endTime": "2024-11-11T10:00:01Z",
    "parentSpanId": "8b9f3c2a1d0e4f5b"
}

Trace Flags

Trace flags control how the trace is processed. In the W3C format, it's a byte-length field:

  • 00: Default
  • 01: Sampled (this trace should be recorded)
  • 02: Debug mode
  • 04-FF: Reserved for future use

Example:

traceparent: 00-4bf92f3577b34da6-00f067aa0ba902b7-01
                                                    ^^ Sampled flag

Version Numbers

The version number (in W3C traceparent) indicates the format version being used:

  • 00: Current version
  • ff: Invalid version
  • Other values: Reserved for future versions

Format significance:

00-4bf92f3577b34da6-00f067aa0ba902b7-01
^^ Version number

Sampling

Sampling determines which traces to record fully:

  • Not all traces can be stored (performance/cost reasons)
  • Sampling decision is made early
  • Sampling rate might be adjusted based on:
    • Traffic volume
    • Error rates
    • Resource usage
    • Business importance

Context Propagation

How trace information is passed between services:

  • HTTP Headers (B3, traceparent)
  • Message Queue Headers
  • RPC Metadata
  • Shared contexts

Example of context flow:

Service A → [trace: abc, span: 123] → Service B → [trace: abc, span: 456] → Service C

Why It Matters

Distributed tracing with these headers enables:

  • Performance monitoring across entire systems
  • Quick problem identification in complex architectures
  • Understanding user experience end-to-end
  • Capacity planning and optimization

For developers and operations teams, this means being able to follow a request's entire journey through the system, making it easier to:

  • Debug issues
  • Optimize performance
  • Understand system dependencies
  • Monitor service health

The Future

As systems continue to grow more complex, distributed tracing becomes increasingly crucial. The W3C Trace Context standard is gaining wider adoption, though many systems maintain support for B3 headers for compatibility. This evolution towards standardization helps create a more connected, observable web of services.


The next time you see these headers in your requests, remember: they're the digital breadcrumbs that help teams track and understand the complex journeys of modern web requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment