Engineering Notes
Home/Advanced Engineering/Advanced/Debugging Distributed Systems
Advanced Engineering · Advanced

Debugging Distributed Systems

In distributed systems, symptoms appear far from causes and clocks disagree about what happened first. Learn a debugging method, which tool answers which question, the usual culprits, and why logical clocks are needed to reason about causality.

🚀 Advanced Engineering● Advanced⏱ 5 min read▶ 2 interactive🛠️ 1 exercise🎤 5 interview Q&As

🎯 What you'll learn

  • Why distributed debugging is different
  • A method: scope, timeline, hypothesis, verify
  • Tools: traces, logs, metrics, profiles and what each answers
  • Common culprits, bug patterns, and clocks vs causality

1Why It's Different

In a single process you can set a breakpoint, step through, and see the whole state. In a distributed system:

  • the "program" spans many processes, machines and teams;
  • failures are partial (some instances, some regions, some requests);
  • timing matters — bugs depend on interleavings, latency, retries, clock skew;
  • you usually can't reproduce production conditions locally;
  • the act of observing (adding logs, attaching debuggers) can change timing.

So debugging becomes investigation from telemetry, guided by hypotheses — closer to science than to stepping through code.

2A Method

text
1. define the symptom precisely   what fails? for whom? since when? how much? (error rate, latency, affected %)
2. scope it                        which endpoints/regions/versions/tenants/instances? which not?
3. look for what changed           deploys, config/flag changes, traffic shape, dependencies, infra events
4. form a hypothesis               "requests from app v4.2 to /checkout time out on payments since 14:05"
5. test it with data               dashboards, traces, logs filtered by the hypothesis; compare good vs bad
6. mitigate first                  rollback, disable flag, shift traffic, scale — restore service before root-causing
7. find the root cause             5 whys / causal analysis on the preserved evidence
8. prevent recurrence              fix + tests + alert/runbook improvements (blameless post-mortem)

Compare good with bad: the fastest way to localize a problem is to split traffic by attributes (version, region, instance, customer, endpoint, payload size) and find the dimension where errors concentrate. High-cardinality, structured telemetry (devops-cloud/observability/) is what makes that possible.

3The Tools and What They Answer

QuestionTool
is something wrong, how bad, since when?metrics / SLO dashboards, deploy markers
which requests? which hop is slow/failing?distributed traces (system-design/system-design/06-distributed_tracing)
what exactly happened in this request?logs filtered by trace ID
which code is hot / stuck?continuous profiling, thread dumps, async stack traces
what is the system doing right now?live queries: pg_stat_activity, queue depths, connection pools, kubectl get events
network-level issues?curl -w timings, dig, tcpdump/Wireshark, mtr (networking-web/fundamentals/11-latency_and_troubleshooting)
can I reproduce it?replaying recorded traffic in staging, fault injection, load tests

4Common Culprits (Check These Early)

  1. A recent change — deploy, config, feature flag, dependency upgrade, infra change. (A large share of incidents.)
  2. Timeouts and retries interacting (06-…, 07-…) — amplification, metastable failures.
  3. Resource exhaustion — connection pools, file descriptors, memory, thread pools, disk, rate-limit quotas.
  4. Data-dependent bugs — one tenant's huge payload, one malformed record (poison message), unicode/timezones.
  5. Clock and ordering — skew between hosts, events out of order, TTLs and token expiry.
  6. Caching — stale or poisoned entries, cache stampedes.
  7. Partial deploys / version skew — old and new versions talking with incompatible schemas.
  8. DNS and certificates — expired certs, stale DNS caches.

5Distributed-System Bug Patterns

  • Heisenbugs — disappear when observed (timing). Use always-on structured telemetry rather than ad-hoc debug logging.
  • Split brain — two nodes both think they're leader (check fencing tokens, quorum).
  • Lost updates — concurrent writers (02-race_conditions/), check versions/CAS.
  • Duplicate processing — at-least-once delivery without idempotency.
  • Thundering herds — synchronized retries, cache expiry, cron jobs at :00.
  • Cascading failures — one slow dependency saturating callers (07-resilience_and_reliability_patterns/).

6Making Systems Debuggable (Before You Need It)

  • Structured logs with trace IDs, service version, region, tenant (devops-cloud/observability/01-logging).
  • RED metrics per endpoint and per dependency; saturation metrics for every pool and queue.
  • Tracing with sampling that keeps errors and slow requests.
  • Deploy/flag events as annotations on dashboards.
  • Runbooks per alert; dashboards that start from the user symptom.
  • Debug endpoints (protected): build info, config (redacted), pool stats, feature flags.
  • The ability to turn up logging for one tenant/request at runtime without a deploy.
Under the HoodWhy Clocks Make Causality Hard▾

Timestamps from different machines aren't directly comparable: NTP keeps clocks within milliseconds normally, but skew of tens or hundreds of ms happens, and a VM pause or leap-second smear can move clocks unexpectedly. Sorting a merged log by wall-clock time can therefore show an effect before its cause. Distributed systems establish causal order differently: Lamport timestamps (a counter incremented on every event and set to max(local, received) + 1 on message receipt) give an order consistent with causality; vector clocks detect concurrency; and tracing's parent/child span links encode causality explicitly, which is why traces are more trustworthy than timestamp-sorted logs when reconstructing "what caused what". When comparing timings across hosts, prefer durations measured on one host (span durations) to differences between two hosts' timestamps.

Debugging loop: define and scope the symptom, check recent changes, form a hypothesis, test it by comparing good and bad requests across metrics, traces and logs, mitigate first, then find the root cause and prevent recurrence
Debugging loop: define and scope the symptom, check recent changes, form a hypothesis, test it by comparing good and bad requests across metrics, traces and logs, mitigate first, then find the root cause and prevent recurrence

⚠️ Common Pitfalls

  1. Root-causing before mitigating while users are still affected.
  2. Changing several things at once — you won't know what fixed it.
  3. Trusting timestamp order across hosts.
  4. Debugging with averages — the problem lives in a slice (one region, one version, one tenant).
  5. No preserved evidence — restarting everything destroys the state you needed (take dumps/snapshots first if safe).
  6. Blame-oriented post-mortems — people hide information; fix systems, not people.

📋 Quick Reference

text
symptom → scope (split by attributes) → what changed? → hypothesis → test with data → mitigate → root cause → prevent
metrics: is it broken? · traces: where? · logs (by trace id): what exactly? · profiles/dumps: why is it stuck/hot?
check early: recent changes · timeouts/retries · exhaustion · data-dependent · clocks · caches · version skew · DNS/certs

🧠 Key takeaways

  • Distributed bugs: partial failure, concurrency, network delays and clock skew; symptoms appear far from causes.
  • Method: scope the impact → build a timeline (what changed?) → hypothesize → verify with data.
  • Traces answer where, logs what exactly, metrics how much/when; check deploys, config, certificates, DNS, limits first.
  • Clocks disagree: use trace context and logical clocks (Lamport/vector) for causal order.

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1How do you debug an issue that spans multiple services?core▾

Start from the symptom (alert, error, slow endpoint), use distributed tracing to find which service/span is failing or slow, pivot to that service's logs via the trace/correlation ID, check its metrics and recent deploys/config changes, then form and test hypotheses.

Follow-ups interviewers ask
  • What should every log line include? Timestamp, service, level, trace/correlation ID and structured context.
Q2Why are distributed systems hard to debug?core▾

Partial failures, nondeterministic timing, clock differences between machines, asynchronous flows through queues, retries hiding errors, and issues that only appear under production load or data.

Follow-ups interviewers ask
  • Why can't you trust timestamps across hosts? Clock skew; use trace causality or logical clocks for ordering.
Q3What's the first thing you check during an incident?core▾

What changed: recent deploys, config or feature-flag changes, traffic patterns, dependency status, infrastructure events. Most incidents are caused by changes; rolling back is often the fastest mitigation.

Follow-ups interviewers ask
  • Then? Scope the impact (which users, endpoints, regions) using dashboards.
Q4How do you debug intermittent failures?senior▾

Collect more data around occurrences (sampling errors at 100%), correlate with time, host, version, region or input data, look for resource limits (pools, file descriptors), retries and timeouts interacting, and try to reproduce with load or fault injection.

Follow-ups interviewers ask
  • Common culprits? Timeouts set shorter than tail latency, connection pool exhaustion, DNS caching, race conditions.
Q5What tooling makes distributed debugging easier?core▾

Structured logging with central search (ELK, Loki), distributed tracing (OpenTelemetry + Jaeger/Tempo), metrics dashboards (Prometheus/Grafana), error tracking (Sentry), and runbooks linking these together.

Follow-ups interviewers ask
  • What is exemplars? Links from metric data points to example traces.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.

✅ Check yourself

Answer the questions — feedback is instant.

1. Two services' logs show an effect timestamped before its cause. Likely reason?
Wall clocks aren't synchronized perfectly.
2. Lamport clock rule on receiving a message:
Preserves happened-before.
3. First question when an incident starts?
Most incidents follow changes.
4. Which signal best shows which hop in a request was slow?
The waterfall.