Skip to content
Preptima
hardScenarioDesignSeniorStaffLead

A configuration change that passed review takes the entire fleet down in under a minute. What in your design should have limited the damage?

No retry policy or circuit breaker helps here, because the change was valid and the defect was already deployed. What bounds the damage is treating configuration as a rollout with stages and bake time, a kill switch whose own dependencies cannot fail with it, and a measured rollback time.

6 min readUpdated 2026-07-29Target archetype: Big Tech, Enterprise Captive, Product Startup
Practice answering out loud

What the interviewer is scoring

  • Whether configuration is treated as a deployable artefact with the staged rollout code already gets
  • Does the candidate separate limiting blast radius from preventing the defect, and pick the former
  • That a way to disable a path without a deploy is proposed, along with what that mechanism itself depends on
  • Whether rollback duration is a measured number rather than an assumption that it is quick
  • Can they explain why a percentage-of-traffic canary would have missed this particular failure shape

Answer

The two things that did not fail

Be precise about the causal chain, because the wrong diagnosis leads to a whole afternoon of irrelevant answers about retries. The code was deployed earlier and worked. The configuration was legitimate and within its schema. What happened is that the new configuration reached a path in the already-deployed code that had never been exercised, and that path was faulty. Two safe-looking events combined into an unsafe one.

This is why resilience work at scale concentrates on blast radius rather than on defect prevention. Nothing in a code review of the configuration would have found it, because the configuration was correct. Nothing in a test of the code would have found it, because the input that triggers it did not exist yet. Retry policies, timeouts, backoff and circuit breakers are all defences against a dependency behaving badly, and here every dependency behaved perfectly while the fleet ran faulty logic in unison.

The public case is Fastly in June 2021, where a single customer configuration change triggered a latent bug in a release that had already been deployed, and much of the CDN went down with it. It is the example to reach for when asked why blast radius gets the engineering attention: the change was valid, the bug was already shipped, and no amount of request shaping would have caught either.

Configuration is the deployment nobody staged

The structural reason this class of failure is so damaging is asymmetry in how the two artefacts are handled. Code goes through review, tests, a build, a canary, a staged rollout with bake time and an automatic rollback. Configuration goes through review and then a push, and the push is global and immediate, because that is what makes configuration attractive in the first place.

So the single most valuable change is to give configuration the pipeline that code already has. Version it, so there is a previous value to return to and an audit trail of who set what. Validate it against a schema at push time, and separately at load time, since the pushing service and the consuming binary can disagree about what is valid. Then roll it out in stages: a small number of instances, a bake period long enough for real traffic to exercise the new path, promotion gated on health signals from the instances that already have it, and an automatic halt when those signals degrade.

Bake time is the part people leave out and it is doing the real work. A change that propagates to forty thousand instances in twenty seconds cannot be caught by any human process, because detection, decision and reversal are all slower than propagation. Deliberately slowing propagation is not inefficiency, it is the mechanism.

flowchart LR
  A[Config change proposed] --> B[Schema validation at push]
  B --> C[One cell receives it]
  C --> D{Health signals hold through bake}
  D -- degraded --> E[Halt and revert that cell]
  D -- healthy --> F[Next cell, wider]
  F --> D
  E --> G[Fleet still on last known good]

The edge worth noticing is the loop between promotion and the health gate: the fleet is never in a state where every instance has the new value and no instance has the old one, which is what keeps a revert cheap for the entire duration of the rollout.

Why a traffic-percentage canary would not have caught this

This is the detail that distinguishes a senior answer, and it is specific to the failure shape. A canary that sends one per cent of general traffic to instances carrying the change is testing the change against average traffic. But the trigger here is one particular customer's configuration exercising one particular path, so unless that customer's requests happen to land on the canary, the canary is healthy and the change is promoted to everything.

Two adjustments address it. Stage by configuration scope rather than by traffic share: apply the new configuration to a subset of tenants across all instances, so the tenants whose settings changed are precisely the ones being observed. And when the change is inherently global, use a shadow or dry-run mode, where the new path is evaluated and its result recorded and compared without being acted upon, so a defect in it produces a logged discrepancy instead of a failed request.

A kill switch that shares dependencies is not a kill switch

You want the ability to disable a path in seconds without a build and a deploy, because the alternative — waiting for a compile, an image push and a rolling restart — is measured in tens of minutes with production down. That is what feature flags are for, and the discipline that makes them worth having is that any risky new path is wrapped in one from the outset rather than after the incident that proves it needed one.

Then ask the question that most designs fail. Where does the flag value come from? If it is fetched from the same configuration service whose change caused the failure, or from a datastore now saturated by the incident, then the mechanism for turning off the problem is downstream of the problem. It has to degrade to something local: a value baked into the image as a default, a value cached on local disk from the last successful fetch, and a rule that an unreadable flag resolves to the safe state rather than throwing.

The same reasoning applies to the configuration loader itself. An instance that cannot parse newly pushed configuration must keep running on the last value it successfully loaded, log loudly, and report itself as degraded. Instances that crash on invalid configuration turn a bad push into a fleet-wide restart loop, and one that empties its cache on startup then takes out whatever it was protecting on the way back up.

Rollback is a number you measure, not a property you assume

Ask what the recovery time actually is, decomposed. Time to detect, which is bounded by your alerting interval and by whether the symptom is even monitored. Time to decide, which is human and is the largest term at three in the morning unless the rollback is automatic. Time to propagate the reversal, which for configuration is usually fast and for a container image is a rolling restart across the fleet. And time for the fleet to become healthy again, which is where cold caches, connection pools re-establishing and warm-up periods live, and which routinely exceeds the reversal itself.

Two facts fall out of doing that arithmetic honestly. Automatic rollback triggered by health signals removes the human term entirely and is worth more than any amount of pre-release testing, because it applies to the failures nobody predicted. And rollback must be exercised, since a path used once a quarter under stress is a path that has silently broken — the schema moved on, the old value is no longer accepted, the previous image was garbage-collected.

Partitioning so that one input cannot reach everything

The last defence is architectural rather than procedural: stop the fleet being one thing. Split it into cells, each a complete independent copy serving a subset of tenants, with no shared datastore or control plane between them. A poisonous input then breaks one cell, and your worst case becomes a fraction of customers rather than all of them.

Shuffle sharding refines it. Assign each tenant a small random subset of the available capacity rather than one cell, so two tenants rarely share their whole subset, and a tenant capable of destroying its assigned instances takes down only the small overlap it shares with any other. The cost of cells is real and should be stated: more infrastructure, per-cell deployment and observability, cross-cell operations that are awkward, and a routing layer that is now a shared component you have to keep very simple. That cost is why cells are the answer for a platform with many tenants and rarely the answer for a single-tenant internal service.

Nothing in the retry-and-timeout toolkit defends against a valid change reaching a latent bug in every instance at once, so the design questions are how slowly the change propagates, what turns it off without a deploy, and how many customers a single copy of the fleet can take down.

Likely follow-ups

  • Your kill switch is fetched from the same configuration service that just took the fleet down. What do you do instead?
  • How would you stage a configuration change whose faulty path is only exercised by one customer's traffic?
  • What is your rollback time for a change that is not a container image, and who has measured it end to end?
  • How do cells or shuffle sharding change the arithmetic when one tenant's input can crash a shared process?

Related questions

blast-radiusconfiguration-managementcanary-releaseskill-switchcell-architecture