Resilience & Failure
Design the system so that when a part breaks — and it will — the failure is
contained and the user still gets a useful (if degraded) answer instead of an
error page or a cascading outage. Getting this wrong is the difference between a
slow dependency and a total meltdown: the most common amplifier of an outage is
the system's own reaction to it (retry storms, health-check stampedes).
When to reach for this
Any design with a remote dependency, a shared resource, or an SLA. Reach here to
find single points of failure, decide what each call does when its dependency is
slow or down, protect a service from being overwhelmed (rate limiting), and plan
how a recovered service comes back without being crushed by the backlog.
When NOT to
Don't wrap a single in-process function or a best-effort batch job in circuit
breakers and bulkheads — that's machinery for cross-process/cross-network calls
(YAGNI). Don't add retries to a non-idempotent write without an idempotency key
first (→ api-design) — you'll duplicate side effects. The cheapest design that
meets the availability target wins; chasing an extra nine you don't need costs
real complexity (→ back-of-the-envelope for what a nine actually buys).
Clarify first
- Availability target — how many nines, and is it per-request or per-feature? (→
back-of-the-envelope.)
- Blast radius — if this dependency dies, must the whole request fail, or can the feature degrade or hide?
- Idempotency — is the operation safe to retry? If not, what makes it safe (key, dedup)? (→
api-design.)
- Latency budget — how long may a call wait before a timeout is better than waiting? (→
back-of-the-envelope.)
- Limit dimension & policy — rate-limit per user / IP / API key / tenant? Hard (reject) or soft (queue/shape)? Burst tolerated?
The options
Layered defenses; most real designs combine several.
- Timeout — bound every remote call. Use everywhere; an unbounded wait is
the root of most cascades.
- Retry with backoff + jitter — re-attempt transient failures with growing,
randomized delays. Use for idempotent calls against blips; never naked retries.
- Circuit breaker — stop calling a dependency that's failing; fail fast and
probe to recover. Use when a downstream is down or slow and retries would pile on.
- Bulkhead — isolate resources (thread pools, connection pools, queues) per
dependency. Use so one slow dependency can't exhaust capacity shared by others.
- Graceful degradation — fall back to a cached/stale value, partial result,
default, or hidden feature. Use when a usable-but-worse answer beats an error.
- Rate limiting / load shedding — cap inbound work; reject or shape excess.
Use to protect a service from overload, abuse, or a stampeding caller.
- Redundancy / failover — run N>1 of every component; promote a standby on
failure. Use to remove SPOFs. (Health checks/LB failover live in
load-balancing.)
Rate-limiting algorithms (token bucket, leaky bucket, fixed/sliding window) and
the circuit-breaker state machine are detailed in references/deep-dive.md.
Trade-offs
| Option |
What it solves |
What it worsens |
Change it when |
| Timeout |
Bounds blocked threads; stops one slow call hanging the caller |
Too tight → false failures; too loose → cascades |
Tune to the dependency's p99, not a guess |
| Retry + backoff + jitter |
Rides out transient blips |
Multiplies load; duplicates non-idempotent writes |
Add jitter + cap attempts + budget; require idempotency |
| Circuit breaker |
Fails fast, gives a sick dependency room to recover |
Adds state/tuning; can trip on a blip and over-shed |
Flapping → tune thresholds / half-open probe rate |
| Bulkhead |
Contains one failure to its own pool |
Lower peak utilization; more pools to size |
One noisy dependency starves others |
| Graceful degradation |
Keeps the user served when a dependency dies |
Serves stale/partial; more code paths to test |
Correctness must be exact → fail closed instead |
| Rate limiting |
Protects the service; bounds cost/abuse |
Rejects legitimate bursts; needs shared state at scale |
Limits too strict (valid drops) or too loose (overload) |
| Redundancy / failover |
Removes SPOFs; survives node/region loss |
Cost, replication lag, failover consistency risk |
Failover drops un-replicated writes → consistency-coordination |
Behavior under stress
This block exists to stop the system from amplifying its own outage.
- Retry storm: a dependency slows, every caller retries, retries pile on the
retries of callers upstream, and load multiplies geometrically. Mitigate:
exponential backoff with jitter, a per-request retry budget (cap total
attempts), and a circuit breaker so a dead dependency isn't retried at all.
- Thundering herd on recovery: a service comes back and every queued client
and expired cache entry hits it at once, knocking it over again. Mitigate:
half-open circuit breakers that admit a trickle, jittered client reconnect,
request coalescing, and slow-start ramp. (Cache-expiry stampede is
caching.)
- Health-check stampede / accidental DDoS: aggressive health checks or
load-balancer probes hammer a recovering instance. Mitigate: gentle probe
intervals, fail-fast readiness, and draining. (Probe mechanics →
load-balancing.)
- Timeout-less cascade: one slow dependency holds threads until the pool is
exhausted, and the caller now looks "down" to its callers. Mitigate:
timeouts + bulkheads everywhere.
- Rate-limiter as SPOF: a shared counter store (e.g. Redis) for limits goes
down. Mitigate: fail-open (allow on limiter error) for availability, or
fail-closed for protection — decide deliberately.
Monitor: error rate and p99 per dependency, retry counts, circuit-breaker
state transitions, pool saturation/queue depth, rate-limit rejection rate, and
"time to first success" after a recovery.
How to apply
- Clarify the inputs — pin the availability target, blast radius per
dependency, idempotency, latency budget, and the rate-limit dimension/policy
(the "Clarify first" list). No defense is chosen before these are answers.
- Pick the defenses — walk the trade-off table per dependency, not globally.
Every remote call gets a timeout; add retry+jitter only where idempotent; add
a circuit breaker where a sick downstream would pile on; bulkhead shared
pools; choose degrade vs fail closed by whether a stale answer is acceptable.
- Set the key knobs — timeout = the dependency's measured p99; retry cap
(often 2–3) plus a per-request budget and jitter; breaker open/half-open
thresholds; bulkhead pool sizes; limiter rate/burst and fail-open-vs-closed.
- Stress-test the design — replay each amplifier from "Behavior under stress"
(retry storm, recovery herd, health-check stampede, timeout-less cascade,
limiter-as-SPOF) and confirm a mitigation is in place for each.
- Size with numbers — compute composed availability along the request path
(series multiplies, parallel adds nines) and confirm the target is met without
over-provisioning. (→
back-of-the-envelope.)
- Pick a provider — default to the generic recipe; only read a provider file
if the user named a cloud (see "Choosing a provider").
Dos and don'ts
Do
- Bound every remote call with a timeout tuned to the dependency's p99.
- Add jitter and a retry budget so re-attempts can't multiply into a storm.
- Make a degraded response explicit (
stale: true) instead of a silent lie.
- Decide fail-open vs fail-closed deliberately for limiters and breakers.
- Stress-test against the amplifiers before calling the design resilient.
Don't
- Retry a non-idempotent write without an idempotency key (→
api-design).
- Wrap in-process calls in breakers/bulkheads — that's cross-network machinery.
- Chase an extra nine the SLA doesn't require; redundancy cost is non-linear.
- Let a shared limiter or counter store become an unguarded single point of failure.
- Hammer a recovering instance with aggressive health checks or full reconnects.
Numbers that matter
Tie timeouts to the dependency's measured p99, not a round guess. Cap retries
(often 2–3) and apply a budget so total attempts can't explode. Each extra
"nine" of availability costs disproportionately more redundancy — know what a
nine actually buys before targeting it. Composed availability matters: components
in series multiply (two 99.9% deps in a request path ≈ 99.8%), redundant
components in parallel add nines. For all of these — latency tables, the nines
table, series/parallel availability math — see back-of-the-envelope.
Interface sketch
Two contracts are load-bearing here.
- Degraded response: make "I'm degraded" explicit, not a silent lie. Return
the fallback plus a signal, e.g.
{ "data": [...], "stale": true, "source": "cache", "as_of": "2026-05-29T10:00Z" } so callers and clients can react.
- Rate-limit response: reject with HTTP
429 Too Many Requests and standard
headers — X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After
(seconds) so a well-behaved client backs off instead of retrying into the wall.
Choosing a provider
Default to the generic recipe above (resilience libraries, a token-bucket/leaky-
bucket limiter, health checks, N+1 redundancy). If the user names a cloud, read
references/providers/<provider>.md for the managed-service mapping, quotas/limits,
and provider-specific trade-offs. If no file exists for that provider, the generic
recipe is the answer.
Diagram
To visualize a fallback path (gateway → timeout on primary → dashed arrow to
cache/default) or a circuit-breaker state machine, use the in-plugin
architecture-diagram skill; draw the degraded path as a dashed arrow and the
failed dependency in the error color.
Related building blocks
messaging-streaming — pairs with this: a queue absorbs a write spike and a dead-letter queue contains poison messages; owned-concept lives in it for delivery guarantees and DLQ mechanics.
load-balancing — depends on it for health checks and LB-level failover routing; pair its probes with the redundancy here to remove SPOFs.
consistency-coordination — owned-concept lives in it: the consistency consequences of failover (un-replicated writes lost, quorum under partition) are decided there.
api-design — depends on its idempotency-key contract before any retry of a write is safe.
caching — pairs with graceful degradation as a fallback source; owned-concept lives in it for the cache-expiry stampede (vs. the recovery herd here).
system-design — feeds into the orchestrator; this block is its step-5 failure-mode check.
References
references/deep-dive.md — circuit-breaker state machine, backoff/jitter formulas, retry budgets, the five rate-limiting algorithms (token bucket, leaky bucket, fixed/sliding window) with distributed-counter and race-condition handling, bulkhead sizing, SPOF analysis and failover modes. Read when designing the resilience layer in detail.
references/providers/{generic,aws,azure,gcp,temporal}.md — service mappings, limits, and pitfalls per environment; temporal.md covers durable retries/timeouts and saga compensation as workflow primitives.
1---2name: resilience-failure3description: This skill should be used when the user asks about "fault tolerance", "resilience", a "circuit breaker", "graceful degradation", "retry storm" or "thundering herd on recovery", "exponential backoff with jitter", "timeout", "bulkhead", a "single point of failure" (SPOF), "failover", or "rate limiting" (token bucket / leaky bucket / sliding window). Use it whenever a design must keep working through node crashes, slow dependencies, traffic spikes, or partial outages — i.e. any time the answer to "what happens when this breaks?" is missing, even if the user doesn't say "resilience".4---56# Resilience & Failure78Design the system so that when a part breaks — and it will — the failure is9contained and the user still gets a useful (if degraded) answer instead of an10error page or a cascading outage. Getting this wrong is the difference between a11slow dependency and a total meltdown: the most common amplifier of an outage is12the system's own reaction to it (retry storms, health-check stampedes).1314## When to reach for this15Any design with a remote dependency, a shared resource, or an SLA. Reach here to16find single points of failure, decide what each call does when its dependency is17slow or down, protect a service from being overwhelmed (rate limiting), and plan18how a recovered service comes back without being crushed by the backlog.1920## When NOT to21Don't wrap a single in-process function or a best-effort batch job in circuit22breakers and bulkheads — that's machinery for cross-process/cross-network calls23(YAGNI). Don't add retries to a non-idempotent write without an idempotency key24first (→ `api-design`) — you'll duplicate side effects. The cheapest design that25meets the availability target wins; chasing an extra nine you don't need costs26real complexity (→ `back-of-the-envelope` for what a nine actually buys).2728## Clarify first29- **Availability target** — how many nines, and is it per-request or per-feature? (→ `back-of-the-envelope`.)30- **Blast radius** — if this dependency dies, must the whole request fail, or can the feature degrade or hide?31- **Idempotency** — is the operation safe to retry? If not, what makes it safe (key, dedup)? (→ `api-design`.)32- **Latency budget** — how long may a call wait before a timeout is better than waiting? (→ `back-of-the-envelope`.)33- **Limit dimension & policy** — rate-limit per user / IP / API key / tenant? Hard (reject) or soft (queue/shape)? Burst tolerated?3435## The options36Layered defenses; most real designs combine several.3738- **Timeout** — bound every remote call. Use *everywhere*; an unbounded wait is39 the root of most cascades.40- **Retry with backoff + jitter** — re-attempt transient failures with growing,41 randomized delays. Use for idempotent calls against blips; never naked retries.42- **Circuit breaker** — stop calling a dependency that's failing; fail fast and43 probe to recover. Use when a downstream is down or slow and retries would pile on.44- **Bulkhead** — isolate resources (thread pools, connection pools, queues) per45 dependency. Use so one slow dependency can't exhaust capacity shared by others.46- **Graceful degradation** — fall back to a cached/stale value, partial result,47 default, or hidden feature. Use when a usable-but-worse answer beats an error.48- **Rate limiting / load shedding** — cap inbound work; reject or shape excess.49 Use to protect a service from overload, abuse, or a stampeding caller.50- **Redundancy / failover** — run N>1 of every component; promote a standby on51 failure. Use to remove SPOFs. (Health checks/LB failover live in `load-balancing`.)5253Rate-limiting algorithms (token bucket, leaky bucket, fixed/sliding window) and54the circuit-breaker state machine are detailed in `references/deep-dive.md`.5556## Trade-offs5758| Option | What it solves | What it worsens | Change it when |59|---|---|---|---|60| Timeout | Bounds blocked threads; stops one slow call hanging the caller | Too tight → false failures; too loose → cascades | Tune to the dependency's p99, not a guess |61| Retry + backoff + jitter | Rides out transient blips | Multiplies load; duplicates non-idempotent writes | Add jitter + cap attempts + budget; require idempotency |62| Circuit breaker | Fails fast, gives a sick dependency room to recover | Adds state/tuning; can trip on a blip and over-shed | Flapping → tune thresholds / half-open probe rate |63| Bulkhead | Contains one failure to its own pool | Lower peak utilization; more pools to size | One noisy dependency starves others |64| Graceful degradation | Keeps the user served when a dependency dies | Serves stale/partial; more code paths to test | Correctness must be exact → fail closed instead |65| Rate limiting | Protects the service; bounds cost/abuse | Rejects legitimate bursts; needs shared state at scale | Limits too strict (valid drops) or too loose (overload) |66| Redundancy / failover | Removes SPOFs; survives node/region loss | Cost, replication lag, failover consistency risk | Failover drops un-replicated writes → `consistency-coordination` |6768## Behavior under stress69This block exists to stop the system from amplifying its own outage.7071- **Retry storm:** a dependency slows, every caller retries, retries pile on the72 retries of callers upstream, and load multiplies geometrically. *Mitigate:*73 exponential backoff with **jitter**, a per-request **retry budget** (cap total74 attempts), and a circuit breaker so a dead dependency isn't retried at all.75- **Thundering herd on recovery:** a service comes back and every queued client76 and expired cache entry hits it at once, knocking it over again. *Mitigate:*77 half-open circuit breakers that admit a trickle, jittered client reconnect,78 request coalescing, and slow-start ramp. (Cache-expiry stampede is `caching`.)79- **Health-check stampede / accidental DDoS:** aggressive health checks or80 load-balancer probes hammer a recovering instance. *Mitigate:* gentle probe81 intervals, fail-fast readiness, and draining. (Probe mechanics → `load-balancing`.)82- **Timeout-less cascade:** one slow dependency holds threads until the pool is83 exhausted, and the caller now looks "down" to *its* callers. *Mitigate:*84 timeouts + bulkheads everywhere.85- **Rate-limiter as SPOF:** a shared counter store (e.g. Redis) for limits goes86 down. *Mitigate:* fail-open (allow on limiter error) for availability, or87 fail-closed for protection — decide deliberately.8889**Monitor:** error rate and p99 per dependency, retry counts, circuit-breaker90state transitions, pool saturation/queue depth, rate-limit rejection rate, and91"time to first success" after a recovery.9293## How to apply941. **Clarify the inputs** — pin the availability target, blast radius per95 dependency, idempotency, latency budget, and the rate-limit dimension/policy96 (the "Clarify first" list). No defense is chosen before these are answers.972. **Pick the defenses** — walk the trade-off table per dependency, not globally.98 Every remote call gets a *timeout*; add retry+jitter only where idempotent; add99 a *circuit breaker* where a sick downstream would pile on; *bulkhead* shared100 pools; choose *degrade* vs *fail closed* by whether a stale answer is acceptable.1013. **Set the key knobs** — timeout = the dependency's measured p99; retry cap102 (often 2–3) plus a per-request budget and jitter; breaker open/half-open103 thresholds; bulkhead pool sizes; limiter rate/burst and fail-open-vs-closed.1044. **Stress-test the design** — replay each amplifier from "Behavior under stress"105 (retry storm, recovery herd, health-check stampede, timeout-less cascade,106 limiter-as-SPOF) and confirm a mitigation is in place for each.1075. **Size with numbers** — compute composed availability along the request path108 (series multiplies, parallel adds nines) and confirm the target is met without109 over-provisioning. (→ `back-of-the-envelope`.)1106. **Pick a provider** — default to the generic recipe; only read a provider file111 if the user named a cloud (see "Choosing a provider").112113## Dos and don'ts114**Do**115- Bound every remote call with a timeout tuned to the dependency's p99.116- Add jitter and a retry budget so re-attempts can't multiply into a storm.117- Make a degraded response explicit (`stale: true`) instead of a silent lie.118- Decide fail-open vs fail-closed deliberately for limiters and breakers.119- Stress-test against the amplifiers before calling the design resilient.120121**Don't**122- Retry a non-idempotent write without an idempotency key (→ `api-design`).123- Wrap in-process calls in breakers/bulkheads — that's cross-network machinery.124- Chase an extra nine the SLA doesn't require; redundancy cost is non-linear.125- Let a shared limiter or counter store become an unguarded single point of failure.126- Hammer a recovering instance with aggressive health checks or full reconnects.127128## Numbers that matter129Tie timeouts to the dependency's measured **p99**, not a round guess. Cap retries130(often 2–3) and apply a budget so total attempts can't explode. Each extra131"nine" of availability costs disproportionately more redundancy — know what a132nine actually buys before targeting it. Composed availability matters: components133in series multiply (two 99.9% deps in a request path ≈ 99.8%), redundant134components in parallel add nines. For all of these — latency tables, the nines135table, series/parallel availability math — see `back-of-the-envelope`.136137## Interface sketch138Two contracts are load-bearing here.139140- **Degraded response:** make "I'm degraded" explicit, not a silent lie. Return141 the fallback plus a signal, e.g. `{ "data": [...], "stale": true, "source":142 "cache", "as_of": "2026-05-29T10:00Z" }` so callers and clients can react.143- **Rate-limit response:** reject with HTTP `429 Too Many Requests` and standard144 headers — `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `Retry-After`145 (seconds) so a well-behaved client backs off instead of retrying into the wall.146147## Choosing a provider148Default to the generic recipe above (resilience libraries, a token-bucket/leaky-149bucket limiter, health checks, N+1 redundancy). If the user names a cloud, read150`references/providers/<provider>.md` for the managed-service mapping, quotas/limits,151and provider-specific trade-offs. If no file exists for that provider, the generic152recipe is the answer.153154## Diagram155To visualize a fallback path (gateway → timeout on primary → dashed arrow to156cache/default) or a circuit-breaker state machine, use the in-plugin157`architecture-diagram` skill; draw the degraded path as a dashed arrow and the158failed dependency in the error color.159160## Related building blocks161- `messaging-streaming` — *pairs with* this: a queue absorbs a write spike and a dead-letter queue contains poison messages; *owned-concept lives in* it for delivery guarantees and DLQ mechanics.162- `load-balancing` — *depends on* it for health checks and LB-level failover routing; pair its probes with the redundancy here to remove SPOFs.163- `consistency-coordination` — *owned-concept lives in* it: the consistency consequences of failover (un-replicated writes lost, quorum under partition) are decided there.164- `api-design` — *depends on* its idempotency-key contract before any retry of a write is safe.165- `caching` — *pairs with* graceful degradation as a fallback source; *owned-concept lives in* it for the cache-expiry stampede (vs. the recovery herd here).166- `system-design` — *feeds into* the orchestrator; this block is its step-5 failure-mode check.167168## References169- **`references/deep-dive.md`** — circuit-breaker state machine, backoff/jitter formulas, retry budgets, the five rate-limiting algorithms (token bucket, leaky bucket, fixed/sliding window) with distributed-counter and race-condition handling, bulkhead sizing, SPOF analysis and failover modes. Read when designing the resilience layer in detail.170- **`references/providers/{generic,aws,azure,gcp,temporal}.md`** — service mappings, limits, and pitfalls per environment; `temporal.md` covers durable retries/timeouts and saga compensation as workflow primitives.