SwadhyAI Notes

All System Design notes

System Design · Observability

Debugging Tools for Distributed Systems

A payment fails in one region. Twelve services touch the request. SSH into a random pod and reading stdout will not find the race. You need a disciplined path from golden metrics to one correlated request.

Start with , jump via an into , read keyed by a , then profile or explain the slow leaf. Keep a ready so you do not turn every pod into a firehose.

Debug journey

1

Who is sick: route, region, version.

2

Which edge reddened first.

3

Exemplar or tail-sampled error.

4

Fix leaf + verify

Same histogram and error rate.

What actually happens

Monoliths let you set a breakpoint. Distributed systems replace that with propagation: every hop must forward trace context, stamp the same correlation id on logs, and export compatible spans. If the queue consumer starts a new root without a link, you lose the story between “API accepted” and “worker failed.”

Tooling is layered. Fleet and a live answer blast radius. Traces answer causality. Logs answer payload-level detail (with redaction). Profilers and DB explains answer “why is this span wide.” and are rare, high-cost tools for when the stack is lying or the process is dead.

and partial sampling are the silent enemies: two hosts disagree on order, or the only bad request was dropped by head sampling. Design for tail sampling of errors and for NTP-disciplined clocks.

How to choose a debugging tool

1. Is the fleet sick? Metrics and maps first—do not open a random trace.

2. Do you need causality? Tracing with an exemplar or forced debug header.

3. Is the leaf opaque? Profile, EXPLAIN, queue depth—or a scoped canary with richer telemetry—not global debug logging.

Simple rule of thumb

One golden signal → one correlated request → one leaf fix → re-check the same signal. If you cannot navigate that chain, invest in propagation and exemplars before buying another dashboard.

The debug path

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart TD
    Alert["Alert / user report"] --> Golden["Golden metrics: latency, errors, saturation"]
    Golden --> Scope["Scope: region, version, route, tenant"]
    Scope --> Map["Service map / dependency edges"]
    Map --> Trace["Exemplar or sampled trace"]
    Trace --> Logs["Structured logs with correlation ID"]
    Logs --> Local["Profile / DB explain / queue depth"]
    Local --> Hypothesis["Hypothesis + fix or mitigate"]
    Hypothesis --> Verify["Verify on same golden signal"]
plaintext
Incident card (fill in order):
  Symptom:     p99 /checkout > 2s in us-east, error rate flat
  Scope:       version 1.14.2, not 1.14.1; only VIP tenants
  Map edge:    API → Inventory latency up
  Exemplar:    trace 4bf9… span inventory.GetStock 1.8s
  Logs:        correlation_id=4bf9… lock_wait_ms=1600
  Leaf:        contention on stock row / missing index
  Mitigate:    cache + timeout + degrade recommendations
  Fix:         index + shorter transaction
  Verify:      same histogram and inventory span width

Context propagation

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart LR
    Edge["Edge / gateway"] -->|"traceparent"| API["API"]
    API -->|"traceparent"| Pay["Payments"]
    API -->|"traceparent"| Inv["Inventory"]
    API -->|"baggage? careful"| Rec["Recommendations"]
    Pay --> DB1["DB"]
    Inv --> Cache["Cache"]
    Rec --> MQ["Queue"]
    MQ -.->|"links / new root?"| Worker["Async worker"]

Use W3C traceparent (and optionally tracestate). Treat inbound baggage as untrusted input—do not copy it wholesale into metric labels. For async workers, either continue the parent context or create a child with an explicit link back to the producer span so UIs can still join the story.

Gateways must forward client debug flags only when authorized (employee device, signed header). Otherwise attackers force 100% sampling and blow your observability budget—a real production outage mode.

Tool kit by layer

Metrics & service maps

RED/USE signals, SLO burn, and dependency edges. Start here to avoid tunnel vision. Exemplars attach a trace id to a histogram bucket so you do not grep blindly. Watch for cardinality explosions when someone labels by raw URL.

Distributed tracing

OpenTelemetry SDKs + collector; Jaeger/Zipkin/Tempo-style backends. Prefer tail sampling that keeps errors and slow traces. Span attributes should follow semantic conventions. Compare sibling spans to spot fan-out amplification.

Structured logs

Always include trace_id / correlation_id, service, version, route, and error class. Sample high-volume success paths. Never log secrets or full card numbers—tokenise. Logs explain “what payload,” not “who called whom.”

Leaf tools: profile, EXPLAIN, queues

Once a span names the slow service, attach a CPU/alloc/lock profile, run EXPLAIN ANALYZE on the query, check pool wait times and consumer lag. This is where distributed debugging meets single-process performance work.

Heavy tools: canary, mirror, chaos, pcap, core

raise verbosity safely. compares candidates under real traffic. validates detection. Pcap/core dumps are last-mile for kernel or native mysteries—minimize retention and access.

Worked example: “random” timeouts

Clients see intermittent 504s. Each service dashboard looks “mostly green.” Averages hide a dependency that only fails under fan-out.

plaintext
Walkthrough:
  1. Metrics: gateway 504 rate up; API error budget burning
  2. Slice by route: /home fans out to 20 backends
  3. Service map: recommendations edge yellow (p99)
  4. Exemplar trace: 19 spans fine; recommendations 2.5s then cancel
  5. Logs: recommendations — "redis: connection pool exhausted"
  6. Leaf: pool size 10, 200 concurrent handlers; queueing
  7. Mitigate: timeout 200ms + stale cache + lower fan-out
  8. Fix: enlarge pool, pipeline, or dedicated replica
  9. Verify: gateway 504 and recommendations p99 recover

Why it felt random:
  Only pages that included the recommendations card waited.
  Head sampling dropped many failures until tail policy kept errors.

Failure modes of the tools themselves

FailureSymptomMitigation
Broken propagationOrphan spans / many rootsMesh or SDK checks; contract tests
Head-sample biasErrors missing from UITail sample errors/slow; unsampled metrics
Time skewNegative durations; weird orderNTP; causal IDs over wall clocks
Log cardinalityIndexer melt / huge billAllowlist fields; sample successes
Debug header abuse100% trace ingestAuthz on force-sample flags

Trade-offs

ChoiceGainCost
Always-on traces @ 1%Cheap baselineMiss rare bugs without tail policy
Tail samplingKeep errors/slow pathsCollector memory & complexity
Verbose debug canaryRich evidence, small blast radiusSkewed mix if canary traffic differs
Request mirroringReal traffic comparisonSide effects & double load
Packet captureGround truth on the wirePrivacy, volume, skill cost
SSH + printfFamiliarDoes not scale; loses causality

Choosing and explaining debugging distributed systems

A complete explanation connects the mechanism to a requirement. Describe what improves when you introduce it, which new failure cases become possible, and how you would know that the trade-off is worthwhile.

Explain the core idea

Navigate metrics → map → exemplar trace → correlated logs → leaf profile/EXPLAIN.

Propagation and sampling policy are part of the design, not an afterthought.

Mitigate user pain first, then prove the root cause with the same golden signal.

When it belongs

Designing observability for a multi-service system.

Explaining how you would handle a regional latency incident.

Choosing sampling, canaries, or mirroring for risky deploys.

How it fails

Broken trace context across queues or mesh hops.

Head sampling dropping the only failing requests.

Debug flags usable by the public internet.

Time skew scrambling span order.

Trade-offs to weigh

Telemetry fidelity versus cost and privacy.

Tail sampling power versus collector complexity.

Deep canary verbosity versus representative traffic.

Common misunderstandings

Opening a random trace before scoping with metrics.

Trusting averages when the map shows one red edge.

Logging unbounded fields as indexed labels.

Calling chaos “debugging” without a hypothesis and abort criteria.

Wrap-up

Distributed debugging is a designed path through signals, not heroics on a single VM. Build the path before the incident: context propagation, exemplars, structured logs, and a canary switch.

Exercises

  1. Missing chapter. API traces end before the email worker. How do you link producer and consumer spans, and what breaks if you do not?
  2. Tool pick. Error rate flat, p99 up, CPU low. Ordered list of tools for the first fifteen minutes.
  3. Abuse case. Design authz for a force-sample header so employees can debug without letting bots melt Tempo.

Key takeaway

Debug distributed systems by following one golden signal to one correlated request to one leaf cause. Invest in propagation, exemplars, and guarded debug canaries so that path exists when you need it.