Production Incident Diagnosis and Distributed Systems Troubleshooting Questions
Debugging distributed systems under fire: diagnosing latency and reliability regressions, root-causing across service boundaries, reading traces and metrics during an incident, and reasoning about complex production failures. Covers the investigative method for hard-to-reproduce, multi-service problems. The operational counterpart to resilient design.
A deployed model is making more calls to an upstream dependency than expected, and it's causing cascading failures in other microservices. With limited engineering resources, you must choose between an immediate rollback, throttling the model, or patching the serving code. Provide a prioritized decision framework, list the short-term mitigations under consideration, and estimate the likely outcome of each action under uncertainty.
Sample Answer
Direct answer. With limited resources and a live cascading failure, the decision framework should weigh speed of relief against risk of losing work, in that order of urgency, since a model that's already causing outages downstream needs the fastest safe reduction in its blast radius, with the fully correct long-term fix as a secondary concern for right now.
Structured elaboration.
- Prioritized decision framework. First: does the cascading failure pose an active, worsening risk to other services (in which case speed dominates every other consideration)? If yes, immediate rollback is usually the safest default, since it's typically the fastest way to guarantee the excess calls stop, and it's a well-understood, low-risk operation compared to a live code patch. Second, if rollback isn't immediately available or would itself be risky (say, the previous model version is known to have its own separate issues), throttling the model's call volume to the upstream dependency directly addresses the cascading mechanism without needing to fully revert the model. Third, patching the serving code (adding a cache, a rate limiter, or a circuit breaker around the specific upstream call) is the most targeted fix but takes the longest to build and test safely, making it usually the wrong choice for stopping an ACTIVE cascade, better suited as the follow-up once the immediate risk is contained.
- Short-term mitigations to list and weigh, given limited engineering resources specifically. Rollback: fast, low custom-engineering effort, but loses whatever improvement the new model version provided. Throttling: moderate effort (may require a config change or a simple gate, not a full redeploy), keeps the new model largely in place but caps its potential value while capped. Patching: highest effort, most precise, but slowest and riskiest to build correctly under time pressure with limited resources.
- Estimate probable outcomes under uncertainty. Rollback: high confidence the cascade stops (since it removes the triggering behavior directly), moderate cost (losing the new model's benefit until a proper fix ships). Throttling: moderate confidence the cascade eases (depends on whether the throttled volume is actually low enough to stop overloading the downstream), lower cost (keeps most of the model's value). Patching under time pressure: lower confidence it actually works correctly on the first attempt (a rushed fix has real risk of its own bugs), and attempting it while resources are limited and the incident is active adds risk of a SECOND incident from an untested change.
- Choose based on the specific uncertainty you're facing. If you're highly confident throttling to a specific, known-safe volume will stop the cascade (because you understand the mechanism precisely), it's often the best balance of speed and value preservation. If you're not confident in that mechanism, or the downstream impact is severe enough that any residual risk is unacceptable, rollback is the safer default despite losing the new model's value temporarily.
Worked example. Suppose the new model version calls an enrichment service roughly 3 times more per request than the previous version (due to a new feature requiring additional lookups), and that enrichment service's capacity was sized for the old model's call volume; if you can confidently throttle the new model's calls to that specific service back down to roughly the old rate (say, via a simple per-model rate limit at the enrichment service's gateway), that directly and precisely addresses the cascading mechanism without losing the new model's other improvements entirely. If, however, the exact multiplier or mechanism isn't clearly understood yet, and the enrichment service is critical to multiple OTHER dependent services beyond just this cascade, the safer call under that uncertainty is a full rollback: guaranteed to stop the excess calls, at the acknowledged cost of losing the new model's benefits until a proper, tested fix (like a smarter caching layer for the enrichment lookups) can ship without time pressure.
Trade-offs and pitfalls. The core judgment call is how confident you are in the PRECISE mechanism before choosing a mitigation more targeted than rollback; a wrong guess about the mechanism, acted on under limited resources and time pressure, risks not fixing the cascade at all while still consuming the scarce engineering time you have. It's worth being honest with stakeholders about this trade-off explicitly: 'we're choosing rollback because we're not yet confident enough in throttling to bet the ongoing cascade on it' is a defensible, transparent decision, more so than a confident-sounding guess that turns out wrong.
You observe high tail latency (p99) for a microservice under load, even though the median latency remains acceptable. Outline a step-by-step troubleshooting plan, including instrumentation, reproducing the load pattern, targeted mitigations, and how you'd validate the fixes in production without risking further user impact.
Sample Answer
Direct answer. When p99 is bad but the median is fine, the problem is affecting a MINORITY of requests in a specific way, so averaging or median-based dashboards will actively hide it; the investigation has to specifically pull out the slow tail and ask what's different about those requests.
Structured elaboration.
- Isolate the slow requests specifically, don't look at aggregate dashboards. Filter your tracing or logging to just the requests above, say, the 95th or 99th percentile latency threshold, and look for what they have in common: a specific endpoint, a specific customer or tenant, a specific payload size or shape, a specific instance or availability zone, or a specific time-of-day pattern.
- Check for resource contention that only bites intermittently. A thread pool or connection pool that's usually adequate can occasionally saturate under a burst, causing a small fraction of requests to queue while most sail through; this produces exactly a good-median-bad-tail signature. Garbage-collection pauses have the same shape: most requests are unaffected, a small fraction land during a pause and get delayed.
- Check for a specific slow dependency call that only some requests make. If a feature flag, a conditional code path, or a specific customer's data shape triggers an extra downstream call (a cache miss requiring a database lookup, for example) for only a subset of requests, the median (mostly fast, cache-hit requests) looks fine while the tail (the subset needing the extra call) is slow.
- Reproduce under load, targeting the specific pattern found in step 1. Once you have a hypothesis (say, large payloads are slow), a load test that specifically varies that dimension can confirm it without waiting for it to reoccur naturally in production.
- Validate the fix without risking more user impact. Roll a fix out to a small percentage of traffic first and specifically watch the SAME tail-latency metric you used to find the problem, not just the aggregate, since a fix could improve the median while barely touching the tail if you mis-diagnosed the cause.
Worked example. Say filtering to the slowest 1% of requests shows they're disproportionately concentrated on one specific API key, and further digging shows that customer's requests average 50 times larger payloads than typical traffic (large batch uploads versus the typical small requests). The service's payload-parsing step is O(n) in a way that's invisible at typical sizes but adds real milliseconds at 50x the size, and if that customer represents roughly 1 to 2% of total request volume, that lines up closely with a p99 (but not median) impact. The mitigation is either optimizing the parsing step's complexity or, as a faster stopgap, routing especially large payloads to a separate processing path so they don't compete for the same resources as typical small requests.
Trade-offs and pitfalls. The trap here is trusting a healthy-looking median or average as evidence nothing is wrong; those aggregates are exactly the metric that HIDES this class of problem by construction, since a small fraction of slow requests barely moves the median at all. It's also worth being skeptical of 'it's probably just noise' when the tail is consistently bad rather than randomly bad: consistent tail badness usually has a specific, findable cause, while genuinely random noise (network jitter, for example) tends to look different in the data.
An hourly ETL job failed mid-run, leaving partially written partitions for several recent hours, and downstream consumers expect stable reads. Describe a safe recovery sequence: how you'd identify the affected partitions, snapshot the current state, safely delete or mark the partial partitions, re-run ingestion, and validate the result. Mention the SQL patterns or transactions you'd use to preserve atomicity where the storage layer supports it.
Sample Answer
Direct answer. A mid-run ETL failure leaving partial partitions is a data-integrity problem before it's anything else, so the recovery sequence has to be careful about ORDER: identify the exact damage, make it safe (don't let readers see it, don't lose the ability to undo), then repair, in that order.
Structured elaboration.
- Identify the affected partitions precisely. Compare the job's expected output (which partitions it was supposed to write, based on its schedule and input range) against what actually exists and its row counts or checksums; a partition with a suspiciously low row count, or one entirely missing, is your candidate set. Check the job's own logs for the exact point of failure to corroborate which partitions were mid-write versus genuinely untouched.
- Snapshot current state before touching anything, so you have a rollback point if your recovery sequence itself goes wrong; this is cheap insurance relative to the cost of compounding a partial-write incident with a botched recovery.
- Make the partial partitions safe for downstream consumers immediately. If downstream reads happen from the same partitions the failed job wrote to, either mark those specific partitions as unavailable/invalid (so readers get a clear 'not ready' signal rather than silently reading incomplete data) or, if the storage layer supports it, use an atomic swap pattern (write to a staging location, then atomically publish only once complete) so partial writes are simply never visible to readers in the first place, which is the more robust long-term pattern.
- Safely delete or mark the partial partitions, informed by step 1's precise identification, not a broad guess at which partitions might be affected.
- Re-run ingestion for exactly the affected range, not the whole job, to avoid unnecessary reprocessing and unnecessary risk to partitions that were never touched.
- Validate before declaring done. Compare row counts or checksums for the re-run partitions against an independent source of truth (an authoritative count or checksum comparison against a trusted source, the same kind of evidence used in any cross-system consistency check) to confirm the recovery is actually complete, not just that the job exited without an error this time.
Worked example. Say the job failed while writing partition 2026-07-20T14, and comparing that partition's row count (12,000) against the typical count for that hour (roughly 85,000, based on the surrounding hours) confirms it's a partial write, not a genuinely low-volume hour. Where the storage layer supports transactional writes at the partition level, the safest sequence is: write the re-run's output to a new, separate location, run the count/checksum validation against that new output BEFORE swapping it in, and only then atomically replace the partial partition with the validated, complete one, which never exposes readers to an intermediate, still-partial state during the fix itself, illustrated here for a Postgres-style target table using a swap pattern:
-- illustrative safe-swap pattern for a partitioned table
BEGIN;
-- new, validated data already loaded into staging_2026_07_20_14
ALTER TABLE events DETACH PARTITION events_2026_07_20_14;
-- DETACH does not drop the old partition, it stays present as a standalone table under its
-- original name, so that name must be freed up before staging can take it
ALTER TABLE events_2026_07_20_14 RENAME TO events_2026_07_20_14_old;
ALTER TABLE staging_2026_07_20_14 RENAME TO events_2026_07_20_14;
ALTER TABLE events ATTACH PARTITION events_2026_07_20_14
FOR VALUES FROM ('2026-07-20 14:00') TO ('2026-07-20 15:00');
COMMIT;
-- once the swap is confirmed correct (row counts/checksums match), drop the retained old partial data:
-- DROP TABLE events_2026_07_20_14_old;
The whole swap happens inside one transaction, so readers see either the old (partial) partition or the new (complete) one, never a state where the partition is half-replaced; the detached old partition is renamed rather than dropped immediately, so it stays available as a rollback/audit artifact until the swap is independently confirmed correct.
Trade-offs and pitfalls. Deleting the partial partition BEFORE the re-run has successfully produced and validated its replacement is a common and risky shortcut: if the re-run also fails, you've now made things worse (no data at all, rather than partial data) instead of better; always have the new, complete data validated and ready before removing the old, partial version. It's also worth checking WHY the job failed mid-run in the first place (a transient issue that a retry alone fixes, versus a systematic problem that will cause the exact same partial-write failure again) before considering the incident closed.
A user updates their profile in Service A and immediately reads from Service B, and sees stale data. Enumerate the possible causes across caching layers, replication lag, eventual consistency, and API layering. Propose an immediate mitigation to reduce user impact and a long-term fix that provides read-after-write semantics for this use case.
Sample Answer
Direct answer. This is the classic read-your-own-writes problem: the write to Service A and the read from Service B are hitting different copies of the data, and the fix depends on WHY those copies disagree, which could be caching, replication lag, or a genuine architectural boundary between the two services.
Structured elaboration.
- Rule causes in or out with targeted checks. If Service B has its own cache in front of its data store, check whether the stale value is coming from that cache (a cache with a TTL that hasn't expired yet would explain it cleanly) versus from B's underlying data store itself. If B reads from a database replica, check replication lag between the primary (which A wrote to) and the replica B read from at that moment.
- Distinguish 'lag' from 'architectural boundary'. If A and B share the same underlying data store, this is a caching or replication-lag problem with a relatively contained fix. If A and B genuinely own separate data stores and B's copy is populated asynchronously (an event, a sync job) from A's, the staleness is a designed property of the system, not a bug, and the fix is either making that sync faster or explicitly changing the guarantee the API offers.
- Check the API layer itself, not just caching and the data store. If B's read goes through an API gateway, a BFF (backend-for-frontend), or any intermediate service that caches responses (an HTTP cache respecting a
Cache-Controlheader, a CDN sitting in front of B's API, or an aggregation layer that batches and caches reads), that layer can serve a stale response even when B's own data store is already fully up to date. This is worth ruling out early since it has a different fix (a cache-busting header, a shorter TTL, or an explicit invalidation call at the gateway) from either a data-layer cache or replication lag. - Immediate mitigation to reduce user impact. If it's cache-related, a short-TTL or explicit cache invalidation on write is a fast, contained fix. If it's replication lag, routing reads for RECENTLY-written data to the primary (or a replica known to be caught up) for a short window after a write is a common, working pattern.
- Long-term fix for read-after-write semantics specifically. Session-level read-your-writes (track that this specific user/session just wrote, and route their next few reads to the primary or a lag-free path) is more targeted than routing everyone to the primary always, which would defeat the purpose of having read replicas or a cache at all.
Worked example. Suppose checking replication lag at the exact timestamp of the read shows the replica B queried was about 800ms behind the primary at that moment, and the user's read happened roughly 400ms after their write. That 400ms gap is well inside the 800ms lag window, so the read genuinely landed before B's replica had caught up: a real, if unlucky, timing collision rather than a bug in either service's logic. If lag is USUALLY well under 400ms and this was an outlier (say, a spike caused by a large batch write elsewhere competing for replication bandwidth at that moment), the immediate fix is investigating and reducing what causes those lag spikes; if lag routinely runs in the 400-800ms range, this isn't a rare edge case, it's a frequent user-visible problem, and session-level read-your-writes routing is the more appropriate fix regardless of the spike's cause.
Trade-offs and pitfalls. Routing ALL reads to the primary to guarantee freshness is the simplest fix but defeats the purpose of having replicas (which exist to spread read load), so it's rarely the right default; targeted, session-scoped read-your-writes gets most of the user-experience benefit at a much smaller cost. It's also worth being honest with product and API consumers about which guarantee you're actually offering: 'eventually consistent, usually within X ms' is a real and often acceptable contract, but only if it's stated, not silently assumed.
A vendor integration suddenly changes its response contract without notice, breaking your clients. Propose an emergency incident response and a longer-term strategy to prevent future vendor-induced breakages, covering contract enforcement, integration testing against the vendor's real API, and the commercial terms you'd push for.
Sample Answer
Direct answer. An unannounced breaking change from a vendor needs an emergency response that treats the vendor as an unreliable dependency for the moment, followed by a longer-term relationship and architecture change that makes the NEXT surprise less damaging.
Structured elaboration.
- Emergency incident response. Confirm the scope of what's actually broken for your clients (which of your features depend on the changed response contract), and apply the fastest safe mitigation: if you can adapt to the new contract quickly (a small parsing or mapping change on your side), that's often faster than waiting for the vendor to revert. If you can't adapt quickly, consider whether you can temporarily fall back to a cached or last-known-good version of whatever data the vendor provides, or gracefully degrade the dependent feature rather than let it fail outright for your own users.
- Communicate with the vendor immediately, both to report the issue (they may not know it's breaking integrators) and to understand their intent (was this deliberate, will they revert, is there a timeline), since that materially changes whether your best move is adapting to their new contract or waiting them out.
- Longer-term: contract enforcement. Push for a formal API contract or SLA with the vendor that includes advance notice for breaking changes, ideally with a defined deprecation window; without this, you're structurally exposed to a repeat of the same surprise.
- Integration testing against the vendor's real API on a schedule, not just at initial integration time: a contract test that runs periodically against the vendor's actual API (not just a mock) would have caught this specific change close to when it happened, rather than only when it caused a live production failure for real users.
- Commercial terms. If this vendor is significant enough to your business, this incident is legitimate leverage to negotiate stronger contractual protections (advance notice requirements, an SLA with remedies for breaking changes without notice) as part of the relationship going forward, not just a technical fix.
Worked example. Suppose the vendor's response contract change turns out to be a field that used to always be present and is now sometimes omitted for a subset of records; if your integration can tolerate treating that field as optional with a sensible default (say, treating a missing status field as 'unknown' rather than crashing), that's a same-day mitigation that doesn't require waiting on the vendor at all. In parallel, adding a scheduled contract test that specifically asserts the shape of the vendor's response (including that this field is present) against their live API on a recurring basis would have caught this exact change within, at most, one test cycle after it shipped, rather than after it caused a customer-facing failure.
Trade-offs and pitfalls. Adapting quickly to a vendor's new contract fixes the immediate problem but can create an awkward situation if the vendor later reverts the change, since your adapted code might then need to handle BOTH the old and new shapes during the transition; keeping the adaptation defensive (tolerant of either shape) rather than assuming the new shape is permanent is a safer default until the vendor confirms their intent. It's also worth being realistic that not every vendor relationship has enough leverage to negotiate a formal advance-notice SLA; where that's not achievable, investing more heavily in your own scheduled contract testing is the fallback that doesn't depend on the vendor's cooperation.
Unlock Full Question Bank
Get access to all 49 Production Incident Diagnosis and Distributed Systems Troubleshooting interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.