Skip to main content
  1. Posts/

Request ID: The Case Number for Distributed Systems

sun.ao
Author
sun.ao
I’m sun.ao, a programmer passionate about technology, focusing on AI and digital transformation.
Table of Contents

The Problem
#

3 AM, alarm sounds: order creation failed. You open the log system and face:

[ServiceA] 10:23:45 User requested order
[ServiceB] 10:23:46 Inventory check succeeded
[ServiceC] 10:23:46 Payment call failed
[ServiceA] 10:23:47 Order failed
[ServiceB] 10:23:47 Another user's request...
[ServiceC] 10:23:47 Yet another user's request...

Thousands of logs interleaved together. Which ones belong to this user’s request? This is the core problem distributed tracing solves.

What is Request ID?
#

Request ID is a unique identifier assigned to each request, persisting throughout its entire lifecycle:

User Request ──▶ ServiceA ──▶ ServiceB ──▶ ServiceC ──▶ Database
      │            │           │           │
      └────────────┴───────────┴───────────┘
                  Same Request ID

Analogy: Like a tracking number for packages - no matter how many transfer stations it passes through, you can trace the complete path with the number.

Design Principles
#

1. Uniqueness
#

Ensure global uniqueness to avoid collisions:

// Recommended: UUID v4
requestID := uuid.New().String()  // "550e8400-e29b-41d4-a716-446655440000"

// Or: Snowflake algorithm (ordered + unique)
requestID := snowflake.Generate()  // "1234567890123456789"

// Not recommended: Timestamp (collisions under high concurrency)
requestID := time.Now().UnixNano()  // Dangerous!

2. Propagation
#

Request ID must be automatically passed between services:

┌─────────┐     ┌─────────┐     ┌─────────┐
│Service A│────▶│Service B│────▶│Service C│
│X-Req-ID │     │X-Req-ID │     │X-Req-ID │
│=abc123  │     │=abc123  │     │=abc123  │
└─────────┘     └─────────┘     └─────────┘

HTTP Approach:

// Service A: Generate and pass
func handler(w http.ResponseWriter, r *http.Request) {
    requestID := uuid.New().String()
    r.Header.Set("X-Request-ID", requestID)
    // Call downstream services...
}

// Service B: Receive and continue passing
func handler(w http.ResponseWriter, r *http.Request) {
    requestID := r.Header.Get("X-Request-ID")
    // Log...
    // Continue passing when calling downstream...
}

gRPC Approach:

// Use metadata for propagation
md := metadata.Pairs("x-request-id", requestID)
ctx := metadata.NewOutgoingContext(context.Background(), md)

3. Generation Timing
#

ScenarioGeneration Location
User requestGateway layer (API Gateway)
Scheduled taskAt task start
Message consumptionGenerated at production, propagated at consumption
Internal callsUse upstream Request ID

Implementation Approaches
#

Approach 1: Gateway Unified Generation
#

User ──▶ API Gateway ──▶ ServiceA ──▶ ServiceB
          Generate Request ID
              └──────────┴──────────▶ All services use the same ID
# Nginx configuration
location / {
    set $request_id $request_id;  # Use Nginx built-in variable
    proxy_set_header X-Request-ID $request_id;
    proxy_pass http://backend;
}

Approach 2: Middleware Auto-Handling
#

// Gin middleware example
func RequestIDMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Prefer upstream-provided ID
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }

        // Store in context for later use
        c.Set("requestID", requestID)

        // Return in response header for debugging
        c.Header("X-Request-ID", requestID)

        c.Next()
    }
}

Approach 3: Logger Integration
#

// Use structured logging
log := logger.WithField("request_id", requestID)

// All logs automatically carry request_id
log.Info("Query inventory")   // {"level":"info","request_id":"abc123","msg":"Query inventory"}
log.Error("Payment failed")   // {"level":"error","request_id":"abc123","msg":"Payment failed"}

Best Practices
#

1. Naming Convention
#

Commonly used header names:

X-Request-ID      # Most common in industry
X-Trace-ID        # Tracing systems
X-Correlation-ID  # Correlation ID

2. Log Format
#

Use JSON format uniformly for easy log system searching:

{
  "timestamp": "2026-03-19T10:23:45.123Z",
  "level": "ERROR",
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "service": "order-service",
  "msg": "Payment call failed",
  "error": "connection timeout"
}

3. Tracing Integration
#

Request ID can work with OpenTelemetry and other tracing systems:

Request ID (business layer) ──▶ User-readable, log search
Trace ID (tracing layer)    ──▶ System-generated, chain visualization
// Link Request ID and Trace ID
span := trace.SpanFromContext(ctx)
span.SetAttributes(
    attribute.String("request.id", requestID),
)

4. Async Scenario Handling
#

Message Queue:

// Producer
msg := kafka.Message{
    Headers: map[string]string{
        "X-Request-ID": requestID,
    },
    Value: orderData,
}

// Consumer
requestID := msg.Headers["X-Request-ID"]
ctx := context.WithValue(context.Background(), "requestID", requestID)

Async Tasks:

// Pass Request ID to goroutine
go func(requestID string) {
    log := logger.WithField("request_id", requestID)
    // Async processing...
}(requestID)

Real-World Example
#

Problem Location
#

With Request ID, troubleshooting becomes simple:

# ELK query
request_id:"550e8400-e29b-41d4-a716-446655440000"

# Result: Sorted by time, complete chain at a glance
[10:23:45] ServiceA Received order request
[10:23:45] ServiceA Called inventory service
[10:23:46] ServiceB Inventory check succeeded
[10:23:46] ServiceA Called payment service
[10:23:46] ServiceC Payment call failed: connection timeout
[10:23:47] ServiceA Order failed

Complete Example
#

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/google/uuid"
    "go.uber.org/zap"
)

var logger *zap.Logger

func main() {
    r := gin.New()

    // Request ID middleware
    r.Use(func(c *gin.Context) {
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }
        c.Set("requestID", requestID)
        c.Header("X-Request-ID", requestID)
        c.Next()
    })

    // Logging middleware
    r.Use(func(c *gin.Context) {
        requestID, _ := c.Get("requestID")
        logger.Info("Request started",
            zap.String("request_id", requestID.(string)),
            zap.String("path", c.Request.URL.Path),
        )
        c.Next()
    })

    r.POST("/order", createOrder)

    r.Run(":8080")
}

func createOrder(c *gin.Context) {
    requestID, _ := c.Get("requestID")

    // Pass Request ID when calling downstream services
    // req.Header.Set("X-Request-ID", requestID.(string))

    logger.Info("Creating order",
        zap.String("request_id", requestID.(string)),
    )

    c.JSON(200, gin.H{
        "request_id": requestID,
        "status":     "success",
    })
}

Summary
#

Key PointDescription
UniquenessUUID or Snowflake, avoid collisions
PropagationHTTP Header / gRPC Metadata auto-pass
Generation LocationGateway unified generation, downstream propagation
Logger IntegrationStructured logging, auto-carry Request ID
Async HandlingMessage header / Context propagation

Request ID is the cornerstone of distributed system observability. Small investment, big return - one thread ties the entire call chain together.

Further Reading
#

Related articles