Step 7 of 10 · 35 min

Reliability and Observability

At scale something is always broken. Reliable systems contain failures, degrade gracefully, and make problems visible.

✓ Set SLOs and error budgets✓ Apply timeouts, retries with jitter, circuit breakers and bulkheads✓ Design for zone failure and instrument with logs, metrics and traces
Say it in 30 seconds
  • 27 callsfrom 3 layers each retrying 3 times
  • 10,000failures allowed on 10M requests at 99.9%
  • 43.2 mindowntime per 30 days at 99.9%
Show what to say

“I assume something is always failing, so I contain it. Every call gets a timeout, retries happen only for idempotent calls, with jittered backoff at a single layer, and circuit breakers and bulkheads fail fast to a fallback like cached bestsellers. I set an SLO such as 99.9% and use the error budget to decide whether to ship or freeze.”

Trade-offs

  • Retries vs load: retries heal transient failures, but 3 layers of 3 attempts send 27 calls at a struggling service; retry in one layer.
  • Circuit breaker vs always calling: failing fast frees threads and gives the dependency room, but users get the fallback while it is open.
  • Four nines vs three: each extra nine cuts downtime 10x but costs far more and demands automated detection and failover.

Bring it up before they ask

Two app servers sharing one database, zone or deploy are not redundant, so check what fails together before counting nines.

New to the words? Read the one-minute primer

0 of 0 sections ticked
30-second answer

Something is always failing, so contain it: a timeout on every call, jittered for safe retries, and with fallbacks. Set and alert on what users feel, not CPU.

Why it matters

Chain 10 services at 99.9% each and only about 99% of requests succeed.

Read more · 66 words

At scale something is always broken. So contain failures, show something useful anyway, and notice before your users do.

Chains multiply their downtime; independent copies multiply their chance of all failing. That one idea explains most of this page: shorten the chain (fallbacks), and add copies that cannot fail together (redundancy).

Play Failure spike, then Half-open probe: the breaker trips, a fallback answers, then it heals.

One service
99.9%
43 min down per month
10 in a chain
99.0%
0.999 to the 10th power: 7.2 h
2 in parallel
99.9999%
1 minus 0.001 squared, if independent

Press Play to watch it run · 4 steps

The caller reaches a flaky dependency through a circuit breaker. While the breaker is closed calls pass through; while it is open they fail fast and a fallback cache answers.

Circuit breakerThe caller reaches a flaky dependency through a circuit breaker. While the breaker is closed calls pass through; while it is open they fail fast and a fallback cache answers.closedopenCallerCircuit breaker5 failures, 10 sDependencyrecommendationsFallback cachepopular items

Tap any box to see what it is and what it talks to.

  • Client
  • API gateway
  • External API
  • Cache
Open in playground
Text description
  1. A request for recommendations reaches the breaker. It is closed, so calls pass through.
  2. The call goes to the dependency with a 300 ms timeout.
  3. It answers in 80 ms. Each success resets the failure count to 0.
  4. The user gets personalised recommendations.

SLOs and error budgets

"Reliable enough" needs a number. Measure something users feel (an ), like requests answered in under 300 ms.

Read more · 385 words

Set a target (an SLO), like 99.9% over 30 days. The customer contract, the SLA, is looser.

An SLI is the share of good events out of valid events, and it must be something a user would complain about. Server CPU is not an SLI; a failed checkout is. Answer the questions in the decision tree to see which SLI fits a service.

Use availability plus for anything a person waits on. Count a request as good only if it is both successful and fast: a 12 s answer is an outage to the user even when it returns 200.

Pick the SLO from what users tolerate, not from what you hope to achieve, because every extra nine costs about ten times more.

AvailabilityDowntime per 30 days
99%7.2 h
99.9%43.2 min
99.99%4.32 min

The arithmetic: 0.1% of a 30-day month is 0.001 x 43,200 minutes = 43.2 minutes. One 45-minute outage uses a whole 99.9% month. At 99.99% you get about 4 minutes, too fast for a human, so must be automatic.

Start with 99.9% for a normal product. Go to 99.99% only for money paths, and say what it costs: automatic failover, multiple zones and a tested runbook.

The is the failure the SLO allows, and it turns reliability into a shared number. At 99.9% and 10M requests a month, 10,000 may fail. Budget left: ship faster. Budget spent: freeze risky changes. The speed of spending is the burn rate: 1 means exactly on budget for the whole window. At 2x the budget is gone on day 15, at 10x on day 3.

The SLO is the internal target; the SLA is the contract, with refunds or penalties, set looser. If the SLA promises 99.9%, aim your SLO at 99.95% so you notice trouble before you owe money.

Monitoring is a doctor listening to a heartbeat: the dashboard shows the pulse and the spike tells you something is wrong.
Which SLI fits this service?yesnoyesyesyesnononoAvailability and latencyAvailabilityIs slow as bad as failed(checkout, search)?FreshnessDurabilityCorrectnessDoes it hold data userscannot recreate?Does it move data in thebackground (a pipeline)?Does a user wait foreach response (an API ora page)?

Does a user wait for each response (an API or a page)?

Downtime per year by availability (log scale)
1101001K10Kminutes3.65 days99%8.77 h99.9%52.6 min99.99%5.26 min99.999%
Show data table
Downtime per year by availability
minutes
99%3.65 days
99.9%8.77 h
99.99%52.6 min
99.999%5.26 min
Error budget left over a 30-day window
  • Burn 1x
  • Burn 2x
  • Burn 10x
0255075100%09152430DayBurn 1xBurn 2xBurn 10x
Show data table
Error budget left over a 30-day window
DayBurn 1x (%)Burn 2x (%)Burn 10x (%)
0100100100
390800
680600
970400
1260200
155000
184000
213000
242000
271000
30000

How failures spread

A failure rarely stays where it starts.

Read more · 278 words

Something slow or dead makes the healthy parts do more work, and they fall over too. Four scenes cover most outages.

SceneWhat happensFix
Retries multiply (retry amplification)A client, a gateway and a service that each try 3 times send 3 x 3 x 3 = 27 calls to the database for one user requestRetry in one layer only, with a retry budget (next section)
A slow dependency eats your threadsA slow call holds its thread; a failure frees it at once. By Little's law, threads busy = requests per second x seconds per call: 500 per second at 80 ms is 40 threads; at 30 s it is 15,000, and a pool of 200 is empty in 0.4 sA timeout near the dependency's slow case, a breaker, and a bulkhead. Optional calls must never starve the main flow
Overload cascadesOne failure shifts its load onto the survivors and they fail too. Three servers at 70% busy carry 210 units; lose one and each of the other two needs 105%With N servers, losing one raises each survivor's load by N / (N - 1). Run below (N - 1) / N: 66% for 3, 75% for 4, 80% for 5. Shed the rest
Redundancy that fails together (correlated failure)Two app servers on one database, zone, deploy pipeline or config push are one failure waiting to happenFor each pair ask what they share. Separate the shared parts, or stagger the change

Play Retry at every layer, then Retry in one layer.

Match the symptom to the defence before you reach for a pattern. The tree shows the order to say it in an interview.

Press Play to watch it run · 4 steps

A client, a gateway and a service each retry a failing call 3 times. One user request becomes 27 calls at the database, which was already struggling.

Retries multiply across layersA client, a gateway and a service each retry a failing call 3 times. One user request becomes 27 calls at the database, which was already struggling.x3x3x3Client app3 triesGateway3 triesOrders service3 triesDatabaseslow

Tap any box to see what it is and what it talks to.

  • Client
  • API gateway
  • Service
  • Database
Open in playground
Text description
  1. The database has slowed to 5 s. A user request starts, and the client library allows 3 tries.
  2. Each call that reaches the gateway is retried 3 times on its way to the service: 3 x 3 = 9 calls.
  3. The service retries the database 3 times for each call: 9 x 3 = 27 calls for one user request.
  4. At 1,000 requests a second that is 27,000 calls a second at a database that could not serve 1,000. The retries are now the outage.
Which defence for which failure?yesnoyesyesyesnononoTimeout, breaker,fallbackTimeout, one retry,bulkheadCan the request stillsucceed without it?Shed load by priorityRedundancy and failoverRoll back firstIs a whole machine, zoneor region gone?Is your own serviceoverloaded (queuegrowing)?Is a dependency slow orfailing while you arehealthy?

Is a dependency slow or failing while you are healthy?

Quick checkThree layers each retry 3 times. Calls at the database?

Containing a bad dependency

A call that hangs holds a thread until the caller runs dry.

Read more · 727 words

So every call gets a timeout, set near the dependency's slow case (80 ms normally, so about 250 ms). A retry fixes a blip: one lost packet, and the second try works. It hurts when the service is overloaded: 1,000 requests a second with 3 tries each can become 3,000.

ToolHow it worksUse it forWatch out for
Timeout and deadlineA timeout limits one call; a deadline limits the whole request and travels with it. Each hop passes on only the time left, so work stops when the user has given up. Set a timeout from the dependency's p99 plus slack (p99 80 ms, so about 250 ms)A short timeout everywhere, with the sum of timeouts on a path below the user's deadlineSome HTTP clients have no timeout until you set one
RetryHelps when the failure was transient (dropped packet, restarting pod); hurts when it is loadIdempotent calls and retryable errors: a timeout, a connection reset, a 503. Keep tries to 2 or 3 in totalNever retry a 400 or a 404; the answer will not change. A card charge is not safe to retry without an idempotency key
Backoff and jitterExponential backoff doubles the wait after each failure; full jitter picks a random time within it: wait = random(0, min(cap, base x 2 to the power attempt)). With base 100 ms: 0 to 100, 0 to 200, 0 to 400 msEvery retry. Without jitter, 1,000 clients that failed together retry at the same instant and hit the recovering service as a wallRetry in one layer only
Retry budgetCaps retries at a share of normal traffic, typically 10%. Each first attempt earns a little credit and each retry spends ten times as much, so even if every call fails you add at most about 10% extra loadThe default for services that call other services (retry-budget.js below)
Idempotency keyThe client sends a unique key with the request and the server saves the result under it, so a retry returns the first result. The first try may have worked with only the reply lostEvery POST that creates or charges something. Keep keys at least as long as clients may retry (often 24 hours)

Switch Retry policy to Exponential + full and watch the peak after recovery drop.

A drowning service needs you to stop calling it. A is a fuse: it trips, calls fail fast, and a fallback answers. It has three states:

  • Closed: calls pass; failures are counted.
  • Open: after 5 failures, calls fail instantly for about 10 s.
  • Half-open: one trial call. Success closes it; failure reopens it.

Play both scenarios: a successful probe, and Probe fails. Use a breaker on every call to a dependency that has a fallback or can be skipped. Trip on an error rate, such as 50% of at least 20 calls in the last 10 s, rather than a raw count, so a quiet service does not trip on one error. The cost: while it is open, users get the fallback even if the dependency has just recovered, for up to one cooldown.

Raise Dependency error rate until the breaker trips, then lower it and watch the half-open probe close it. Failure threshold sets how many failures in a row trip it.

A gives each dependency its own pool, like a ship's watertight rooms: if recommendations hangs, checkout still has its slots. Size each pool as x seconds per call, plus headroom of about 2 to 3 times. Use it when one optional dependency shares a process with a critical path. The cost is idle capacity: a pool that is not busy cannot lend threads to one that is.

Backoff is a staircase where each retry waits about twice as long as the last, so the server gets room to recover.
Interactive

Retry storm

300 clients fail at once; the server is back at 10 s and answers 20 requests per 100 ms.

Retry policy
Peak after recovery
165 / 100 ms
full jitter: 12
Everyone served by
not by 30 s
280 failing · full jitter: 17.5 s
Requests sent
1,790
6.0 per client
Requests arriving at the server per 100 ms (line: recovery at 10 s)
  • Exponential
  • Full jitter
050100150200req07.51522.429.9Time (s)ExponentialFull jitter
Show data table
Requests arriving at the server per 100 ms (line: recovery at 10 s)
Time (s)Exponential (req)Full jitter (req)
0135143
0.1165184
0.2028
0.3029
0.4037
0.5033
0.6034
0.7048
0.8049
0.9044
113533
1.116528
1.2012
1.3017
1.4015
1.5018
1.6018
1.7018
1.8024
1.9015
2030
2.1025
2.2023
2.3019
2.4016
2.5017
2.6017
2.709
2.8013
2.9012
313512
3.11658
3.2022
3.303
3.4017
3.5010
3.604
3.709
3.8011
3.9012
406
4.1015
4.2010
4.3012
4.409
4.509
4.609
4.7015
4.8011
4.907
5014
5.109
5.2010
5.3011
5.406
5.505
5.6010
5.706
5.805
5.907
608
6.108
6.2012
6.308
6.404
6.505
6.604
6.708
6.806
6.904
71355
7.11652
7.2011
7.306
7.4011
7.504
7.609
7.706
7.808
7.9010
8013
8.105
8.207
8.3011
8.404
8.5010
8.6012
8.707
8.808
8.908
9010
9.109
9.203
9.304
9.407
9.505
9.609
9.707
9.8011
9.909
1004
10.106
10.207
10.3011
10.4012
10.509
10.607
10.708
10.806
10.906
11011
11.109
11.207
11.309
11.405
11.505
11.608
11.707
11.805
11.903
1208
12.105
12.203
12.305
12.406
12.505
12.606
12.705
12.803
12.907
1304
13.105
13.201
13.303
13.402
13.504
13.605
13.701
13.806
13.902
1402
14.103
14.202
14.302
14.404
14.503
14.604
14.703
14.802
14.903
151352
15.11658
15.203
15.300
15.404
15.503
15.603
15.700
15.800
15.901
1602
16.100
16.203
16.300
16.401
16.503
16.600
16.701
16.803
16.901
1702
17.100
17.200
17.300
17.400
17.501
17.600
17.700
17.800
17.900
1800
18.100
18.200
18.300
18.400
18.500
18.600
18.700
18.800
18.900
1900
19.100
19.200
19.300
19.400
19.500
19.600
19.700
19.800
19.900
2000
20.100
20.200
20.300
20.400
20.500
20.600
20.700
20.800
20.900
2100
21.100
21.200
21.300
21.400
21.500
21.600
21.700
21.800
21.900
2200
22.100
22.200
22.300
22.400
22.500
22.600
22.700
22.800
22.900
231300
23.11600
23.200
23.300
23.400
23.500
23.600
23.700
23.800
23.900
2400
24.100
24.200
24.300
24.400
24.500
24.600
24.700
24.800
24.900
2500
25.100
25.200
25.300
25.400
25.500
25.600
25.700
25.800
25.900
2600
26.100
26.200
26.300
26.400
26.500
26.600
26.700
26.800
26.900
2700
27.100
27.200
27.300
27.400
27.500
27.600
27.700
27.800
27.900
2800
28.100
28.200
28.300
28.400
28.500
28.600
28.700
28.800
28.900
2900
29.100
29.200
29.300
29.400
29.500
29.600
29.700
29.800
29.900

Exponential: the retries stay synchronised, so the recovering server's worst 100 ms saw 165 requests (it can answer 20) against 12 with full jitter. At 30 s, 280 clients were still failing; with jitter everyone was served by 17.5 s.

How the model works

Exponential delays are 1, 2, 4, 8 s, then 8 s (capped); full jitter waits a random time between 0 and that. Past its capacity the server wastes time on rejections, so it answers fewer. Each run uses a seeded random stream: reshuffle for another.

Calls reaching the database for one failing request
0102030calls27Every layer retries3One layer retries1.1One layer plus10% budget
Show data table
Calls reaching the database for one failing request
calls
Every layer retries27 calls
One layer retries3 calls
One layer plus 10% budget1.1

Press Play to watch it run · 3 steps

Closed lets calls through and counts failures. Five failures open the breaker; after the cooldown it goes half-open and sends one probe, which either closes it or opens it again.

Circuit breaker statesClosed lets calls through and counts failures. Five failures open the breaker; after the cooldown it goes half-open and sends one probe, which either closes it or opens it again.5 failurescooldown elapsedprobe okprobe failedClosedcalls passHalf-open1 probeOpenfail fast

Tap any box to see what it is and what it talks to.

  • API gateway
  • External API
  • Cache
Open in playground
Text description
  1. Closed: calls flow and failures are counted. The 5th consecutive failure trips the breaker to Open.
  2. Open: every call fails fast and the fallback answers. Once the 10 s cooldown has elapsed, the next call moves it to Half-open.
  3. Half-open: a single probe is let through. It succeeds, so the breaker closes and the failure count resets.
Interactive

Circuit breaker

Requests (5 per second) pass through a breaker. Raise the dependency's error rate to trip it open, then lower it and watch a trial request close it.

5 failuresin a rowcooldownovertrialfailstrial succeedsClosedfailures 0/5● currentOpenfail fastHalf-openone trial callRecent requests (newest →)

✓ success · ✕ real error (dependency called) · ↩ fast-fail, fallback served

✓ Successes
0
↩ Fast-failures
0
fallback served
✕ Real errors
0
dependency called
Calls spared
0%
never reached it

Closed: calls go to the dependency (0 of 5 failures in a row so far). 0 successes, 0 fast-failures served by the fallback, 0 real errors.

State changes

No state changes yet: raise the error rate until 5 requests in a row fail.

Surviving big failures

Overloaded, serving everyone means serving no one.

Read more · 991 words

Two ideas: refuse early, and decide the fallback before the outage. Then add copies that cannot fail together.

ToolHow it worksUse it forWatch out for
Load sheddingRefuse work you cannot finish, early and cheaply: before parsing or database work, reply 503 with Retry-After (429 for one client over its limit), check under a millisecond. Also drop abandoned work: a request queued past its deadline has no one waitingArrival rate above capacity for more than a few seconds, while autoscaling (minutes) cannot help yetPer-client limits are a different tool: rate limiter
Priority sheddingDrop the least important work first: keep checkout and login; drop prefetch, analytics and batch jobs. With one shared queue the login codes wait behind a million marketing messages; with separate lanes they do notProtecting the work that earns money. Give the critical class its own lane or reserved capacity (say 30% of workers nothing else may use)The reserved capacity sits idle in quiet times
Bounded queueAn unbounded queue turns overload into a delay nobody wants. Wait at the back = backlog / throughput: at 1,000 req/s a cap of 100 is at most 0.1 s; a backlog of 30,000 is 30 s, long after the user left. When full, reject with 503: that is backpressureEvery queue. Set the cap so the wait stays under the request's deadline
FallbackFor each dependency, write down what the user sees when it is down. Use a stale cache whenever slightly old beats nothingOptional parts hidden or replaced with cached data; only the core flow protectedA little staleness: label it ("prices may be out of date")
Kill switchA feature flag that turns an expensive extra off without a deploy: on-call flips recs.enabled and the page stops calling recommendations within secondsEvery optional feature that calls another service. Flip it first under load, ask questions later
Read-only modeWith the primary database down, a banner keeps browsing, search and order history alive while writes are refusedBeats an error page for most products
When this is downShow instead
RecommendationsCached bestsellers
ReviewsNothing; the page still sells
SearchCached popular queries
Database writesRead-only mode with a banner

A fallback that never runs quietly breaks. Exercise each one on a schedule: turn the dependency off in staging, then in production for 1% of traffic, and check that the page still renders.

Redundancy. Two of everything helps only if they cannot fail together.

  • N+1 across zones. One spare unit beyond what the load needs, so a failure or rolling deploy does not cut capacity. Across 3 zones, run each below about 66% busy so the two survivors carry everything. At 60%, 3 zones carry 180 units; 180 / 2 = 90% each. At 80% they carry 240; 240 / 2 = 120% each. The default: 3 zones, each below 66% busy. Play At 60% busy, then At 80% busy.
  • Zones versus regions. Zones are separate buildings in one region, about 1 to 2 ms apart, so a there can be synchronous. Regions are far apart (tens of ms), so is asynchronous and the copy lags. Use a synchronous replica in another zone when you cannot lose committed writes; an asynchronous replica in another region to survive a disaster, accepting the loss of the last seconds. is how much data you can lose. is how long you can be down.
  • Active-passive or active-active. Active-passive keeps a standby idle until the fails: simpler and cheaper, but slow to fail over. Active-active serves from several places at once: fast, but must handle writes in two places (Step 6). Start with active-passive. Go active-active only when RTO must be near zero, and then run each region below 50% busy so one can carry both.
  • When goes wrong. Failover is detected, not known: a monitor only sees silence. Three missed 5 s apart means 15 s before it acts. If the old primary is only cut off, both accept writes: . The fix is a : every promotion gets a higher number and storage rejects writes with an older one. Rehearse failover on a schedule; the first real one should not be the first test.

Choose by RPO and RTO, two numbers the business can say out loud. A payments ledger needs RPO 0. A blog can live with 24 h.

SetupRPORTO
Nightly backupsUp to 24 hHours
Async replica, other regionSeconds to minutesTens of minutes
Sync replica, other zone01 to 2 min
Active-active regionsNear 0Near 0

Try the calculator: pick the weakest link in the request path, make it a redundant pair, and watch the total move.

Interactive

Priority lanes

Login codes (OTP) must arrive in seconds; marketing can wait hours. Give them separate queues and workers, or watch one campaign bury every login.

OTP lanecritical · login codesempty2 workers idleMarketing lanelow priorityempty8 workers idle
  • OTP code (one every 0.5 s)
  • marketing (log-scale bar)
  • 1 worker = 20 messages/s
OTP sent now waits
50 ms ✓ on time
Oldest waiting OTP
none (0 delivered)
Marketing backlog
0 keeping up
Simulated time
0 ms (10× real time)

Separate lanes: login codes go out in 50 ms. Launch the campaign and watch them stay fast.

Speed and OTP wait chart
Speed

The OTP latency chart fills in as the simulation runs.

Wait for the last request in the queue, at 1,000 req/s (log scale)
110100seconds0.1 sCap of 1001 sBacklog 1,00030 sBacklog 30,000
Show data table
Wait for the last request in the queue, at 1,000 req/s
seconds
Cap of 1000.1 s
Backlog 1,0001 s
Backlog 30,00030 s

Press Play to watch it run · 3 steps

Three zones share the traffic. If each runs at 60% busy, losing one leaves two at 90%. At 80% busy the two survivors would need 120% and fail.

Losing a zoneThree zones share the traffic. If each runs at 60% busy, losing one leaves two at 90%. At 80% busy the two survivors would need 120% and fail.Load balancerZone A60% busyZone B60% busyZone C60% busy

Tap any box to see what it is and what it talks to.

  • Load balancer
  • Service
Open in playground
Text description
  1. Three zones at 60% carry 180 units of work (100 is one zone's full capacity).
  2. Zone A loses power. Health checks remove it within seconds.
  3. 180 units over 2 zones is 90% each. Everyone is served.
Which redundancy setup?yesnoyesyesnonoMulti-region withconsensusSync replica, other zoneMust you also survive awhole region going down?Async replica, otherregionBackups plus a restoredrillMust you stay up througha regional disaster?Is losing one committedwrite unacceptable(money)?

Is losing one committed write unacceptable (money)?

Interactive

Availability calculator

Pick a box to edit it. Serial dependencies multiply; a redundant pair is down only if both copies are: 1 − (1 − a)².

  1. User
Edit 1.
= 99.99%
Composite
99.84%
4 in series
Downtime / year
14.02 h
Downtime / month
1.17 h
Weakest link
Payment API
99.9%

0.9999 × (1 − 0.001²) × 0.9995 × 0.999 = 99.84%

The whole path is up 99.84% of the time: about 14.02 h of downtime a year (1.17 h a month). The weakest link is Payment API at 99.9%: making it a redundant pair would help most.

Chart: yearly downtime by nines
Downtime per year by availability (log scale)
1101001K10Kminutes3.65 days99%8.76 h99.9%4.38 h99.95%52.6 min99.99%5.26 min99.999%
Show data table
Downtime per year by availability
minutes
99%3.65 days
99.9%8.76 h
99.95%4.38 h
99.99%52.6 min
99.999%5.26 min

Shipping and watching safely

Most outages start with a change. Keep changes small, visible and reversible.

Read more · 765 words
OptionHow it worksUse it forWatch out for
CanarySends a small slice of traffic to the new version and compares it with the old. At 10,000 req/s, 1% is 100 a second: enough to spot a broken deploy in minutes while 99% of users never see it. Compare error rate and p99 against stable at the same time; step 1%, 10%, 50%, 100%; roll back automatically when the canary burns budget faster than v1The default for servicesSlower rollout; two versions live at once
Blue/greenRuns the new version beside the old and switches at the load balancer, so rollback is instantWhen you cannot run two versions on live traffic, or want a full-stack rehearsal before users arriveDouble capacity during the switch, and a shared database: use expand and contract (add the new column, deploy, backfill, switch reads, drop the old column in a later release)
Feature flagSeparates deploying code from releasing it: ship dark, enable for 1%, ramp, turn off without a deploy. Hash flag and user id so a user always gets the same answer and raising the percentage only adds usersRisky features and kill switchesFlag debt: delete a flag once the ramp is done

Play Good , then Bad canary.

At 3 a.m. you need three answers, fast:

SignalAnswersExample
MetricsIs something wrong?Error ratio, p99 latency
LogsWhat happened in this request?JSON with reqId, status
TracesWhere did the time go?API 320 ms, database 250 ms
  • Metrics are cheap numbers over time. For services watch rate, errors and duration (RED). Average hides the pain: take 100 requests, 98 at 100 ms and 2 at 10 s. The average is (98 x 100 + 2 x 10,000) / 100 = 298 ms, which looks fine. The is 10 s. Alert on p99 or on the share of requests over the threshold, never on the mean.
  • Logs explain one request. Write JSON with a request id, one line per request at the edge. Use structured fields, not sentences, and never log passwords, tokens or card numbers. Sample the boring successes if volume is huge, but keep every error.
  • Traces follow one request through every service as timed spans. A header (traceparent) carries one id across services, so ten services join into one waterfall. Sample them (1% to 10%) and keep all traces for errors and slow requests.

Toggle Slow DB and Timeout + fallback and watch which span eats the 200 ms budget.

Alert on symptoms and burn rate, not CPU. On a 99.9% SLO, 1.44% errors is a burn of 14.4, which spends 2% of the month's budget in an hour: page. A slow burn over three days: ticket.

Burn rateWindowBudget spentAction
14.4x1 hour2%Page
6x6 hours5%Page
1x3 days10%Ticket

Every alert should name a user-visible symptom and link to a runbook of first safe steps. CPU, memory and disk go on dashboards.

When the pager fires, stop the bleeding first:

  1. Detect

    A burn-rate alert pages on-call.

  2. Triage and communicate

    Name one incident commander and update the status page.

  3. Mitigate first

    Roll back, fail over, shed load or flip a flag. Root cause can wait.

  4. Resolve and learn

    Write a blameless postmortem: timeline, causes, action items with owners.

Most incidents are fixed by undoing the last change, not by understanding it. Here the deploy is bad at minute 0, the page fires at minute 4, and the rollback finishes at minute 18. The cause is studied afterwards, with users already fine. Ask first: what changed in the last hour? Then roll back or flip the flag, and look at traces only once the graphs recover.

A postmortem records what happened so it does not happen again, and it blames the system, not a person. Write the timeline, the causes (usually several), what went well, and action items with an owner and a date. An incident without action items will repeat.

Press Play to watch it run · 3 steps

A small slice of traffic goes to the new version while metrics are compared with the old one. A healthy canary is widened step by step; a bad one is rolled back automatically.

Canary releaseA small slice of traffic goes to the new version while metrics are compared with the old one. A healthy canary is widened step by step; a bad one is rolled back automatically.99%1%statsstatsLoad balancerv1 stable99% of trafficv2 canary1% of trafficMetricserrors, p99

Tap any box to see what it is and what it talks to.

  • Load balancer
  • Service
  • External API
Open in playground
Text description
  1. 1% of 10,000 requests a second is 100 a second to v2. In 5 minutes that is 30,000 requests.
  2. v2 shows 0.1% errors and the same p99 as v1. A real regression to 2% would show as 600 errors, not 30.
  3. Healthy, so the canary widens: 1%, then 10%, 50%, 100%, with the same check at each step.
{"ts":"2026-03-02T09:14:07Z","reqId":"r-81f3","route":"POST /order","status":503,"ms":312,"user":"u-42"}
Same 100 requests, three summaries (log scale)
1101001K10Kms100 msp50298 msAverage10 sp99
Show data table
Same 100 requests, three summaries
ms
p50100 ms
Average298 ms
p9910 s
Interactive

Trace waterfall

One request, every hop, against a 200 ms latency budget. Flip the toggles to see which hop eats the budget.

0 ms50 ms100 ms150 ms200 msDNSTCP + TLSCDN edgemiss└ API service└ Redishit└ Databasenot called (cache hit)└ Recs service└ Queue publishDNS: DNS lookup, 0–12 ms (12 ms)12 msTCP + TLS: TCP + TLS 1.3, 12–52 ms (40 ms)40 msRedis: GET product:42, 76–78 ms (2 ms)2 ms · hitRecs service: GET /recs, 76–96 ms (20 ms)20 msQueue publish: publish view event, 96–99 ms (3 ms), async: not on the critical path3 ms · asyncAPI service: GET /product/42, 71–101 ms (30 ms)30 msCDN edge: CDN miss → origin, 52–118 ms (66 ms)66 ms · miss200 ms budgettotal 118 ms

Total 118 ms of the 200 ms budget (59%): within budget. Biggest hop: TCP + TLS (TCP + TLS 1.3, 40 ms). Redis → DB and the recommendations call run in parallel, so the slower branch sets the pace; the queue publish is fire-and-forget.

Page, ticket or dashboard?yesyesnonoyesyesnonoPage nowTicket, next businessdayDashboard onlyBurn 1x or more (10% ofthe budget in 3 days)?Burn 14.4x or more (2%of the budget in anhour)?Ticket, next businessdayDashboard onlyWill a disk, certificateor quota run out withindays?Are users seeing errorsor slowness against theSLO?

Are users seeing errors or slowness against the SLO?

An example incident, minute by minute0 min6 min12 min18 min24 min30 minDetectBurn-rate alert fires: 0–4 minTriageCommander named, status page: 4–10 minMitigateRoll back the deployRoll back the deploy: 10–18 minLearnConfirm recovery, hand overConfirm recovery, hand over: 18–30 minBad deployPageUsers recover

A bad deploy at minute 0 is detected by a burn-rate alert at minute 4, mitigated by rollback at minute 18, and only then investigated. The postmortem comes later.

Text description
  • Detect: Burn-rate alert fires (0–4 min)
  • Triage: Commander named, status page (4–10 min)
  • Mitigate: Roll back the deploy (10–18 min)
  • Learn: Confirm recovery, hand over (18–30 min)

Code

The breaker lets exactly one half-open probe through.

Read more · 58 words

availability.js and slo.js reproduce this page's numbers.

// Full-jitter exponential backoff, plus a "retryable" predicate
export async function retry(fn, { tries = 4, baseMs = 100, retryable = (e) => e.retryable ?? true } = {}) {
  for (let i = 0; ; i++) {
    try {
      return await fn();
    } catch (e) {
      if (i >= tries - 1 || !retryable(e)) throw e;              // never retry validation errors or non-idempotent calls
      await new Promise((r) => setTimeout(r, Math.random() * baseMs * 2 ** i));   // full jitter: wait 0–100, 0–200, 0–400 ms
    }
  }
}
export const serial = (...a) => a.reduce((x, y) => x * y, 1);            // every dependency must be up
export const parallel = (...a) => 1 - a.reduce((x, y) => x * (1 - y), 1); // up if any copy is up (independent failures)

export const downtimeMinutes = (a, days) => (1 - a) * days * 24 * 60;
export const downtimeMinutesPerYear = (a) => downtimeMinutes(a, 365.25);  // average year, leap days included

// serial(0.999, 0.999)          → 0.998 (two 99.9% dependencies give ≈ 99.8%)
// serial(0.999, 0.999, 0.9995)  → 0.9975 (~22 h down per year)
// parallel(0.99, 0.99)          → 0.9999
// downtimeMinutes(0.999, 30)    → 43.2 minutes per 30 days
// downtimeMinutesPerYear(0.999) → 526 minutes ≈ 8.77 h

Reliability in the interview

Bring reliability up as soon as the design has more than one service, or when the interviewer asks what happens when something fails.

Read more · 617 words

Then walk through five decisions in order.

  1. Say the target as an SLO

    "99.9% of checkouts succeed in under 300 ms over 30 days." That is 43 minutes of a month and it tells everyone how much to spend on resilience.

  2. Find what fails together

    Walk the diagram and name the single points of failure: one database, one zone, one deploy. Give each a copy in a separate failure domain.

  3. Contain a bad dependency

    A timeout on every call, one retrying layer with and , a breaker with a fallback, and a on optional calls.

  4. Survive the big failures

    3 zones each below 66% busy, a synchronous with automatic , and by priority for overload.

  5. Ship and watch safely

    at 1% with automatic rollback, , and burn-rate alerts on what users feel. Name and if data matters.

If the interviewer saysYou raise
"What if the payment service is slow?"Timeout, one idempotent retry, breaker, queue the order
"What if a data center dies?"3 zones below 66% busy, sync replica, automatic failover
"How do you deploy safely?"Canary at 1%, automatic rollback, feature flags
"How do you know it is healthy?"RED metrics, p99, burn-rate alerts, traces
"We need five nines"About 26 s a month: only automatic failover works; say the cost

Quick checkWhat do you ask for before choosing resilience patterns?

Interview questions

Why add jitter to retries?

Clients would retry in lockstep and knock a recovering service back down.

SLO vs SLA?

The is your internal target, and its sets how fast you ship. The SLA is the looser contract with penalties.

How do you survive a zone failure?

3 zones with spare capacity, and synchronous database with automatic .

What do you alert on?

Burn rate on what users feel (errors, against the SLO). CPU and disk go on dashboards.

How can retries make an outage worse?

Retries at every layer multiply load (3 × 3 = 27 calls). Use jittered , breakers and one retrying layer.

How do you stop a slow dependency taking everything down?

A short timeout, a breaker with a fallback, and a so its pool is capped. Threads busy = rate x latency, so a hang drains a shared pool in under a second.

What is split brain and how do you prevent it?

Two nodes both think they are and accept writes. A makes storage reject the older one.

Check yourself

Question 1 of 5

Score 2 out of 3 or better to mark this page complete.

1. 99.9% monthly availability allows about…

Next: Put it all together on a real problem, the URL shortener.