Interactive course · about 9 hours

Fault Tolerance

Every part of a real system breaks. Machines stop answering, messages go missing, one program somewhere goes slow and takes ten others down with it. None of that can be prevented, and nobody has ever built a system out of parts that do not fail. What can be done is to arrange the parts so that a broken one costs you a little instead of everything, and a small set of ideas does that job every time. Here they are, one at a time, with something to break at the end of each.

Four words this course starts from

A request is one thing somebody asks a program to do: fetch a page, take a payment, look up a name. The program doing the asking is the client and the program that answers is the server. A service is a server and the machines it runs on, treated as one thing you can ask. Waiting is counted in milliseconds, and a millisecond is one thousandth of a second. Every other term arrives where it is needed.

Nothing here is quoted from a table

Every number you will read comes out of a run that happened when you pressed a button. Some labs roll dice for four thousand imaginary days and count the bad ones. Most of the rest run a small service second by second: requests arrive, workers pick them up, timeouts fire, retries are scheduled. When a chart shows a collapse, the collapse happened.

What you need before you start

From Build-a-Unix, the idea that a program is one of many on a machine and has to ask for anything outside itself. No other background. Four of the twenty-two labs ask you to write a few lines of code, and each of those has a note above it that explains the code from the beginning. The arithmetic never goes past percentages and multiplying two of them together.

The steps

Step 1

Everything fails, and the arithmetic of how often

Pick any single piece of a working system: one machine, one disk, one cable, one program. Ask how often it is working. The answer is never all of the time. A decent machine might be working 99 days out of every 100. A very good one, 999 days out of 1000. The number is always short of all of them, and the useful question is what happens when you build something out of many such pieces.

Call a part up when it is working and down when it is not. A chain is a set of parts where you need every single one. Your request goes through a cable, to a server, to a disk, and back, and any one of those being down means no answer. Real systems are full of chains, mostly built by accident rather than on purpose.

The arithmetic of a chain is multiplying, not adding. Two parts that are each up 99 days in 100 make a chain that is up about 98 days in 100. Ten independent 99-percent components in series give about 0.9910, or 90.4 percent. The first lab counts the same result day by day.

Why do I multiply the two numbers instead of adding them?

Because the chain is up on a day only when both parts are up on that same day. Take 100 days. The first part is up on 99 of them. Now look only at those 99 days and ask about the second part: it is up on 99 out of every 100 days, so it is up on about 98 of the 99. That is 99 hundredths of 99 hundredths, which is 98.01 days in 100.

Adding would say something quite different and quite wrong: that two parts each up 99 in 100 give you 198 days in 100, which is more days than there are. What does add up is the failures. One part is down 1 day in 100, so two parts are down on about 2 days in 100 between them. That agrees with the first answer, because being up 98 days in 100 and being down 2 days in 100 are the same sentence.

What does "up 99 days in 100" actually promise?

Less than it sounds like. It is a rate over a long stretch of time, not a schedule. It does not say which day is the bad one, and it does not promise that the bad days are spread out politely. Two down days can land next to each other, and over a long enough run they will.

This is why Lab 2 reports the longest run of bad days back to back alongside the total. A part that is up 99 days in 100 will, sooner or later, be down for two days in a row, and any plan that quietly assumed otherwise finds out at the worst moment.

Where do the numbers in the chart come from?

From rolling dice. For each of 4000 imaginary days, the lab rolls once for every part in the chain and asks whether that part is down today. If any part is down, that day is counted as bad. The red bars are those counts. The line running across them is the multiplying worked out in advance, which is why the bars land on it.

The dice are seeded, which means the sequence of rolls is fixed rather than fresh each time. If something surprises you, press the same button again and the same run comes back, so you can look at it properly instead of chasing it.

Lab 1 · A chain of parts
Try this firstDrag parts in the chain from 6 up to 14. The 4000 days are re-run at every notch. Watch the red bars climb, and watch how closely they sit on the line, which is what the multiplying predicted before a single die was rolled.
Now try the three buttons at the top. A part that is up 99 days in 100 sounds nearly perfect, and a chain of fourteen of them is down about one day in seven. Buying parts that are ten times better buys back roughly ten times fewer bad days, and no part you can buy switches the multiplying off. The two things on the chart are worth comparing on purpose. One of them rolled dice 4000 times and the other multiplied a number by itself. On the first button they agree, because they are the same fact arrived at from two directions. On the third button they stop agreeing, and that is the other thing this lab has to teach: a part that is down one day in ten thousand gives you a handful of bad days in four thousand, and you cannot measure a rate from a handful. The multiplying needs no days at all. Rare failures are the ones you have to work out, because they are the ones nobody has watched for long enough to count.
Lab 2 · Guess first, then count
Try this firstRead question 1, decide on one of the five answers, then press it. The options lock, the right one is marked, and the panel underneath rolls the dice for a thousand days and reports what actually happened. Then press Next question →.
Being wrong here is the point. Most people put the first answer two or three notches too low. Thirty parts each failing one day in a thousand give you thirty chances to fail every day, and thirty chances at one in a thousand is about one bad day in thirty-three. The running score sits at the end of the panel, and the three questions come round again if you want another go at them.
A service needs two parts to answer: a database that is up 99 days in 100, and a store of pictures that is also up 99 days in 100. Roughly how many days in 100 can the service answer?
About 98. 99 hundredths of 99 hundredths is 98.01, so the pair loses about two days in every hundred. The tempting answer is 99, because we read a list of parts and remember the best number in it. The third answer describes copies of the same job, which is Step 2, and these two parts do different jobs: the picture store cannot answer a database question.
Step 2

Redundancy, and the failure that takes out both copies

If needing every part is what hurts, then the fix is a part you do not need. Run the same job on two machines, and either one of them answering is enough. Keeping spare parts that do the same work is called redundancy, and it is the most used idea in the whole subject.

The arithmetic turns around. A chain multiplied the chances of being up, which made things worse. Copies multiply the chances of being down, which makes things better, and quickly. A machine that is down 5 days in 100 gives a pair that is down about 25 days in 10000, which is roughly one day in 400.

That is the promise, and it holds only while the two copies fail for their own separate reasons. Quite often they do not.

Why is multiplying the down chances the right sum this time?

Because the pair is down only when both copies are down at once. Each is down 5 days in 100, which is 5 hundredths. Of the 5 days in 100 that the first copy is down, the second copy is also down on about 5 in every 100 of them, and 5 hundredths of 5 hundredths is 25 ten-thousandths. Add a third copy and you multiply by 5 hundredths again, which is about one day in eight thousand.

Notice that this is the same multiplication as Step 1, pointed at a different question. In a chain you multiply the chances of working, and more parts make the answer smaller, which is bad. With copies you multiply the chances of failing, and more copies make the answer smaller, which is good. The arithmetic does not care which you wanted.

What is a rack, and what is a room?

A rack is a metal frame about the height of a door, holding a stack of machines. The machines in it share one power feed and usually one network switch. A room is a hall full of racks, sharing the building's power and its cooling, because a room full of machines gets hot enough to stop working within minutes if the cooling does.

That sharing is the point. Lose the power feed and every machine in that rack goes down in the same instant, however healthy each one was. Lose the cooling and eventually the room does. So two copies in one rack are two machines, one power feed and one switch, and the second copy protects you from precisely one of those three things.

I have two copies and they both went down together. How?

Something they share failed. It does not have to be power. Two copies of a program reading one database share the database. Two machines on one switch share the switch. Two copies given the same broken settings file at nine in the morning share the settings file, and that one takes both down inside a second.

A failure like that is called a shared cause: one event, both copies. The test to apply to any design is not "how many copies do I have" but "name an event that takes them all". If you can name one easily, that event is your real availability, and adding a third copy does not move it at all.

Lab 3 · Copies, and the failure they share
Try this firstLeave the second slider at 0 and read the bars. They fall off a cliff: one copy has hundreds of bad days out of 8000 and five copies has almost none. Now drag the second slider, failures that hit every copy at once, up to 5. The bars stop falling and the cliff becomes a step.
The dashed line is a floor, and no number of copies goes below it. Once a shared cause is in the picture, the bad days split into two kinds and the panel counts them separately. There are days where every copy failed on its own, which copies fix. And there are days where one event took them all, which copies cannot touch. Five copies plugged into the same failure are five things plugged into the same failure.
Lab 4 · Where the copies live
Try this firstPress two racks, one room, then two rooms. The two boxes above the chart say which room and rack each copy sits in, and 20000 days are re-run on every press. The bars compare all three plans side by side, so the one you have chosen is always visible next to the other two.
Read the breakdown of causes, not just the total. Machines break on their own about 2 days in 100, a rack loses power about 4 days in 1000, and a room loses cooling about 8 days in 10000. Moving the second copy out of the rack removes almost every rack failure and leaves the room as the thing both copies share. Moving it out of the room leaves almost nothing shared, and what remains is the two machines happening to break on the same day, which is the failure that copies were invented for.
You run two copies of a service in one rack. Which change removes more bad days: adding two more copies to that same rack, or moving the second copy into a different room?
Move it. Copies in one rack only help with machines failing on their own, and by the second copy most of that is already dealt with. The rack failure takes every copy in the rack at the same instant, so copies three and four are down on exactly the same days as copies one and two. More copies is tempting because it is the easy thing to buy, and here it buys more of the protection you already have.
Step 3

Retries, and the stampede

From here on parts are not up or down for a whole day. They are busy. A server handles requests with workers: each worker takes one request, works on it for a while, then takes the next one. Eight workers, each needing about 60 milliseconds a request, can finish somewhere near 130 requests a second between them and not one more. That number is the server's capacity, and it comes out of arithmetic rather than out of hope.

When a request gets no answer, the obvious move is to send it again. That is a retry, and each sending is an attempt. One person asking once, with three retries allowed, can become four attempts. Retrying is the cheapest thing in this course to switch on and the easiest one to regret.

The trouble is when the retry arrives. It arrives while the server is in difficulty, because being in difficulty is what caused the retry in the first place.

Where does the number 130 come from?

One worker taking 60 milliseconds a request finishes one request every 60 thousandths of a second. There are 1000 thousandths in a second, so that is 1000 divided by 60, which is about 16 and a half requests a second. Eight workers doing that at the same time is eight times as many, which is about 133 a second.

Two things follow, and both matter for the rest of the course. Nothing in the design can beat that number except more workers or faster work: not a bigger queue, not a cleverer retry, not asking more politely. And the number is easy to work out for any service. So you can always tell whether you are looking at a broken system, or at a system being asked for more than it has.

What is a chart column when it says "each second"?

Each column is one second of the run. The lab does not wait around for forty real seconds. It keeps a list of things that are going to happen, each stamped with the time it happens, always takes the earliest one next, and moves its clock straight to that moment. The time between two events costs nothing, so forty seconds of a busy server is a few hundred thousand small steps and finishes while your finger is still on the button.

Because the clock is inside the run, everything can be counted exactly: how many requests reached the server in second 11, how many were answered, how many were given up on. The bars and the lines are those counts.

So should I never retry?

Retrying is the right answer to a message that got lost, and to one machine out of many being briefly unwell. Those are common, and a single retry fixes them so cheaply that leaving it out would be strange.

It is the wrong answer to a server that is already at capacity, because then the retry is more work arriving at something that has no room for it. The difficulty is that the client cannot tell those two situations apart: no answer looks the same either way. The next four steps are all about making a retry cheap enough, rare enough and safe enough that switching it on is not a gamble.

Lab 5 · A four-second rush
Try this firstPress Run the 40 seconds with no retries still chosen. Traffic jumps to more than five times its usual level between second 10 and second 14. The red bars appear there and stop there. Now press retry 3 times, half a second apart and run it again, and look at where the red bars end this time.
Compare "people who asked" with "attempts the server saw". The rush is four seconds long in every one of these runs, because the rush is a setting and you are not changing it. What changes is how long the damage lasts. The extra attempts arrive after the rush is over, so the server is still drowning at a moment when nobody new is asking it for anything. The third button spaces the retries out further, and the two numbers it moves go in opposite directions: fewer people are given up on in total, and the losses go on for longer, because the later attempts land later. Spacing retries out is not the same as making fewer of them.
Lab 6 · Turn the load up and watch what comes out
Try this firstPress Run all ten loads. Ten separate twenty-second runs, at ten levels of demand from 20 people a second up to 280, one point on the chart per run. Two of the three lines flatten out. Find the one that does not.
The flat part is the ceiling being the ceiling. Both answered lines climb until they reach what eight workers can do and then stop, whatever you ask of them. The dashed line keeps climbing, because that one counts attempts rather than answers. At the right-hand end the server is being asked several hundred times a second and still answering about 130, and every attempt above that is work that has to be looked at and turned away. Retrying moved the work around. It did not create a worker.
A server can finish 130 requests a second. 200 people a second are asking for something, and every client that gets nothing retries three times. About how many requests a second does the server end up answering?
About 130, and no arrangement of retries changes it. The workers can finish 130 answers a second whether they are looking at 200 requests or 800. What the retries do change is the pile: the server now has to receive and refuse several hundred extra attempts a second, and the people behind them wait longer to be disappointed. The first answer counts attempts and calls them answers, which is the mistake this step exists to remove.
Step 4

Backoff, and the random part that does the work

Two hundred clients lose a server at the same instant. They all notice at the same instant, because they were all waiting on the same thing. If every one of them waits exactly one second and tries again, then two hundred requests arrive inside the same tenth of a second. The server takes the twenty it has room for, and the other hundred and eighty wait exactly one second and do it again. The crowd stays a crowd.

Making the wait longer after every failure is called backoff. Wait one second, then two, then four, then eight. That spreads the waves further apart in time, which helps a server that needs a quiet moment to recover.

It does nothing whatever about the size of a wave. Two hundred clients doubling the same number all get the same number. The cure for that is jitter: having worked out how long to wait, pick a random moment inside that stretch instead of waiting the whole of it.

What is a random number here, and what is roll?

A random number is one you cannot predict, drawn fresh each time, and the ones used here are evenly spread between 0 and 1. Any value is as likely as any other. In the code lab that value arrives as roll: 0.10 means a tenth of the way along, 0.55 means just past halfway. Every client gets its own, so no two clients agree on where inside the stretch to come back.

Evenly spread is what makes jitter work. If the numbers clustered near the start, the crowd would arrive early and together, which is the problem you were trying to remove. And as everywhere in this course, the dice are seeded, so the same button gives you the same run and you can look at a surprise twice.

If the random part does the work, why keep the doubling?

Because the two do different jobs. Jitter fixes the shape of the crowd: it turns one tall wave into a flat spread. Doubling fixes the rate: it means that if the trouble lasts a long time, each client asks less and less often instead of hammering away at the same pace for ever.

With jitter alone and no growth, two hundred clients keep offering the same number of attempts a second until they run out of patience. That is fine for a two-second outage and useless for a two-minute one. With doubling alone you get the waves. The lab has a button for each, so you can watch which half does what.

I have never read code like the box in Lab 8

A function is a piece of code with a name, which is handed some values and gives one back. This is the whole shape of it:

function nextDelay(attempt, roll) {
  return 1000;
}

The name is nextDelay. The two names in brackets are its arguments, which are the values handed in when somebody uses it. return is how it gives an answer back, and nothing after a return runs. So print(nextDelay(1, 0.10)) means work out nextDelay with those two values and show what came back. This version gives 1000 whatever it is asked, which is the flat one-second wait that failed in Lab 7.

What are let and while for?

let ceiling = 1000; makes a name for a value you intend to change later, and ceiling = ceiling * 2; changes it, taking whatever is in there, doubling it, and putting it back. Reading that line as a statement of fact does not work. It is an instruction carried out at a moment in time.

while (i < attempt) { ... } repeats everything inside the braces for as long as the test in the brackets holds. Something inside has to move towards making the test false, which is what i = i + 1; is for, otherwise it would go round for ever. This is how you double a number attempt times without writing the doubling out several times.

Lab 7 · Two hundred clients and one alarm clock
Try this firstPress Wake them all up with everyone waits 1 second chosen. Each bar is the attempts arriving in one tenth of a second, and the dashed line is what the server can take in that time. The first bar goes far above the line. Then press the other two buttons in turn and run each of them.
Watch two numbers: the busiest tenth of a second, and how many never got in. Doubling the wait moves the waves apart and leaves them the same height, because two hundred clients doubling one second all reach two seconds together. The third button doubles and then picks a random point inside, and the tall bars vanish. The randomness did that, not the doubling, and the two hundred clients, the server and its capacity were identical in all three runs.
Lab 8 · Write the waiting rule
Try this firstPress Run. It prints what three different clients would wait before their first retry, and all three print 1000, which is the whole problem in three lines. Make the answer depend on the two values the function is given, then press Test it on two hundred clients.
What the checker does. It asks your nextDelay for attempts 1 to 6, with sixty different rolls each. It refuses a negative wait, or one longer than a minute. Then it hands your rule to the same two hundred clients from Lab 7 and reports whether every one got served. Two properties have to hold: the wait grows as attempt grows, and two clients with different rolls get different answers. If it fails, read the message, because it says which of the two is missing. Start over puts the original code back, and Show me one that works writes out one answer if you would rather read one than find one.
Two hundred clients lose a server at the same moment. Each doubles its wait after every failure, and nobody adds any randomness. What arrives at the server when the first wait ends?
All two hundred at once. Backoff changes when a wave arrives and not how tall it is. Every client started from the same wait and applied the same rule to it, so they agree on every wait that follows. Expecting a spread is the assumption that quietly sinks a lot of real retry code, because it confuses spacing the waves out with breaking them up. Only a per-client random number does the second thing.
Step 5

Timeouts: too short duplicates work, too long hangs

A client that asks and gets nothing back cannot wait for ever, so it sets a timeout: a stretch of time after which it stops waiting and treats the request as failed. Both ends of that choice hurt, and they hurt in opposite directions, which is why there is no safe default.

Too short, and you give up on answers that were about to arrive. The server finishes the work anyway, into an empty room, and your client sends the request again. You have paid for the work twice and received it never.

Too long, and you hold on to a request that is not going to be answered. That costs more than it looks like, because your client is a machine too. It can only hold so many requests open at once and a request waiting on something dead is holding one of those places.

Why are some answers slow when nothing is broken?

Because a request shares the machine with everything else on it. A worker had just started something else. The disk was busy elsewhere. The network took a longer path this time. None of that is a fault, and none of it can be removed, so the time an answer takes is never one number. It is a spread, with a lot of quick answers and a few slow ones.

Lab 9 measures a whole minute in which nothing failed, nothing timed out and there were spare workers throughout. Even there, the slowest answers take many times longer than the typical one. That long thin tail on the right of the chart is normal, and it is exactly what makes choosing a moment to give up hard.

What does "the slowest 1 in 100" mean?

Sort every answer in the run from quickest to slowest, then walk along the sorted list. The value one hundredth from the end is the one the panel calls the slowest 1 in 100: only one answer in a hundred took longer than that."Half were under" is the same trick stopped in the middle of the list.

Averages are avoided here on purpose. A run where nearly everything takes 60 milliseconds and one answer takes 30 seconds has a comfortable average and a customer who has given up and gone away. What you want to know is how bad the bad end is, and walking a sorted list is how you find out.

What is a slot, and how does a caller run out of them?

While a client waits for an answer it is holding things: some memory, a network connection, and a place in its own list of work in progress. Call one of those places a slot. A client with sixteen slots can have sixteen requests open at once and no more, which is not meanness, it is what its own machine has room for.

Now give that client a ten-second timeout and a service that has stopped answering. Sixteen requests go out, none come back, and every slot is held by somebody who is already doomed. A request for a part of the system that is working perfectly cannot even be sent. The panel in Lab 10 counts those as "never even sent". That number is how a slow service takes down a caller that had nothing wrong with it.

Lab 9 · Where the slow answers are
Try this firstPress Run a minute and measure every answer. Each bar counts the answers that took that long, in steps of 25 milliseconds. Nothing fails anywhere in this run. Now drag give up after down from 300 ms towards 25. The dotted line moves with it and the count of answers your line would have thrown away climbs.
Look at the right-hand end of the chart, where the bars are one high. Those are real answers that took ten times longer than the typical one, in a run where the server had spare workers the whole time. Every answer to the right of your dotted line is one you would have abandoned while it was on its way, and the work would have been finished anyway with nobody left to receive it. That is what a short timeout buys you, and the panel puts a number on it.
Lab 10 · Try every timeout
Try this firstPress Run all eight timeouts. Eight separate forty-second runs, one per timeout, all containing the same trouble: one kind of request jams from second 12 to second 28 and starts taking about thirty seconds instead of seventy milliseconds. Then drag your timeout across all eight positions and read the numbers under the chart at each one.
Both ends are bad, and for opposite reasons. At the short end,"gave up too early" and "work thrown away" are large: you are abandoning answers that were coming and paying for them anyway. At the long end those two fall to nothing and "never even sent" takes over, because every slot is held by a request waiting on the jam. The panel names the best of the eight, and it is worth seeing where that lands. It is not in the middle of the row. It sits near the short end, at a little over twice as long as a typical answer takes, because almost everything here is quick and only the jammed requests are slow. The two sides are not evenly matched either: one notch shorter than the best costs you several answers in every hundred, and going all the way to the long end costs you about a third of them. When you have to guess, guess long rather than short of the typical answer, and not by much.
Half the answers from a service come back within 70 ms, and one in a hundred takes over 900 ms. You set your timeout to 100 ms. What happens to the work the server was doing for the requests you gave up on?
It finishes into an empty room. Giving up is something the client does on its own side. The server heard the request, handed it to a worker, and that worker keeps going. So you have spent a worker's time on an answer nobody will read, and your retry is now asking for a second one. The panel calls that "work thrown away", and at a short timeout under load it is where a large part of the capacity goes.
Step 6

Idempotence, so a retry is safe

Every retry so far has been harmless, because nothing in those labs remembered anything. Real requests change things. A payment moves money, and moving the same money twice is not a small problem.

The hard part is that a client cannot tell the difference between a request that never arrived and a reply that got lost on the way back. Both look identical from where it sits: it asked, and nothing came. In the first case retrying is correct. In the second it charges somebody twice.

The answer is not to make the network reliable, because you cannot. It is to make the second arrival harmless. An operation is idempotent when doing it twice leaves things exactly as doing it once did. The usual way to get there is to put a key on the request, and to have the receiver remember which keys it has already acted on.

What is a key, and who makes it up?

The client does, once, at the moment it decides what it wants. A key is a short label attached to that intention, like pay-1729, and every retry of that same intention carries the same label. A new intention, even for the same amount to the same person, gets a new label.

The receiver keeps the labels it has acted on. When a request arrives with a label it has seen, it does nothing and replies as though it had just succeeded, which is true, because it did succeed, a moment earlier. Notice what this does not require: no change to the network, no promise that replies arrive, no cleverness in the client beyond reusing its own label.

What is a list, and what is account.done?

A list is several values kept in order, written [3, 9, 4]. You reach one of them by its position, counting from 0, so list[0] is 3 and list[2] is 4. That position is called an index. list.length is how many values it holds, and list.push(7) adds one to the end.

An account in Lab 12 is a thing with two named parts inside it. account.balance is the money left, and account.done is a list of the keys this account has already acted on. The dot means "the part of it called", so the whole job is to look through that list before touching that balance.

Does the receiver have to remember every key for ever?

No, only for as long as a retry might still turn up. Clients give up after seconds or minutes, so real systems keep keys for hours or days and then forget the old ones, which keeps the list from growing without limit. The cost of forgetting too early is a duplicate charge, so the window is chosen to be comfortably longer than the longest retry anybody is allowed.

This is safe to skip and never needed later in the course. It is here because it is the first question a careful reader asks, and the answer is more ordinary than the question suggests.

Lab 11 · Paying twice
Try this firstPress Send 40 payments with just send it again chosen. Compare the two balances in the numbers: what the balance should be, and what it actually is. The panel underneath names the first few payments that were taken more than once. Then press send a key with it, and remember the keys and send the forty again.
Nothing about the network improved between those two runs. The slider says how many replies in every hundred go missing, and it is set the same either way. All that changed is whether the receiver could recognise a request it had already dealt with. Drag the slider up to 60 with keys switched on and the balance stays exactly right, which is what makes a retry something you can switch on without asking what it might cost.
Lab 12 · Make the retry safe
Try this firstPress Run. Two identical charges print two different balances, 90 and then 80. The two lines are the same request sent twice, so the two numbers should be the same. Change charge so that they are, then press Send every request twice and check the money.
The checker tries six situations, and two of them are the interesting ones. Two different keys asking for the same amount both have to go through, because two people really can be charged 20 on the same day, so matching on the amount is not enough. And one hundred copies of one request have to move the money once. It also checks that charging once still works, which is the rule an over-careful fix usually breaks.
A payment request arrives, the money moves, and the reply is lost on the way back. The client sends the same request again. What does the receiver need in order to do the right thing?
A key, and a memory of keys. Matching on the amount looks reasonable and fails on a real day. Two customers can be charged the same amount within the same minute, and one of them would silently not be charged at all. A faster network is a wish. Networks lose replies at any speed, and the whole point of making the second arrival harmless is that you stop needing the network to behave.
Step 7

The circuit breaker

Sometimes the thing you are calling is not slow or unlucky. It is down, and it will still be down in twenty seconds. Every request you send it is going to fail. Each one will wait out your whole timeout first, and hold a slot while it waits.

A circuit breaker watches the failures and, after enough of them in a row, stops sending altogether. Calls are refused on your own side, immediately, without going anywhere. Every few seconds it lets exactly one through to find out whether the answer has changed.

It repairs nothing. What it buys is that the healthy part of your service stops queueing behind the dead part, and that is usually the difference between a broken feature and a broken product.

Why is it called a circuit breaker?

Because of the switch in the box under the stairs. When too much current flows through a wire, the breaker cuts that wire off from the supply. It does not repair the fault, and it does not care what caused it. It notices a measurable sign of trouble and disconnects, so that the fault stays in one place instead of heating a wire inside a wall.

The software version copies the shape exactly: a measurable sign of trouble, an automatic disconnection, and a human problem that still has to be dealt with afterwards. Nobody thinks a tripped breaker means the wiring is fine now, and nobody should think an open breaker means the service is fine.

Open and shut sound the wrong way round to me

They come from the electrical breaker, where the words describe the circuit rather than the door. An open circuit has a gap in it, so nothing flows. A shut circuit is a complete loop, so current runs round it.

So an open breaker means calls are blocked, and a shut breaker means calls are flowing normally. A healthy system has all its breakers shut. It reads backwards the first three times and then stops bothering you. It is worth learning as it is, because every real library and dashboard uses these two words in this direction.

Why let one call through every few seconds?

Because nothing else would ever tell you the service came back. A breaker that opens and stays open needs a person to close it, and that person is asleep. So after a waiting period, called the cooldown, the breaker allows exactly one request out as a test. That single test request is called a probe.

If the probe succeeds the breaker shuts and normal traffic resumes at once. If it fails, the cooldown starts again and everything else is still being refused instantly. One request every three seconds is a cost you cannot measure, and it is the difference between recovering by itself and recovering when somebody notices.

What is true, and what is a history?

true and false are the only two values of their kind, and they are what questions answer. recent[i] being true means that call worked; false means it failed. A function that gives one of those back can be used directly to decide something, which is why Lab 14 asks for exactly true or false and not for the word "open".

A history is a list of those values, oldest first, so the newest call is at the end. Since positions count from 0, the last one is recent[recent.length - 1]. Getting the direction the wrong way round is the most common way to write a breaker that reacts to a failure from five minutes ago and ignores the one that just happened.

Lab 13 · Pull the breaker
Try this firstPress Run the minute with keep asking, it might come back chosen. Reviews are dead from second 15 to second 40 while search is healthy throughout. Read the top two numbers: search, which never broke at all, answered only a fraction of what it was asked. Now press stop asking after 5 failures in a row and run it again.
Watch "never even sent" collapse, and watch the average wait with it. Between second 15 and second 40 reviews answer nothing in either run, because reviews really are dead and no trick on the calling side revives them. The "reviews answered" figure stays near 60% all the same, because it covers the whole minute and reviews are healthy for the thirty-five seconds either side of the outage. Search is the number that moves. What the breaker changes is where the damage stops: refusing a doomed request in no time at all leaves the slots free for the work that can be done, and the reader of a page loses one panel instead of the page.
Lab 14 · Write the tripping rule
Try this firstPress Run. Three histories print, and the three answers should be false, false, true. The middle one is wrong: this rule opens on a single failure. Rewrite it so that it opens only when the last five calls in the list all failed, then press Test it on ten histories.
The ten histories are chosen to catch the four usual mistakes. An empty list, where nothing has happened yet. Four failures in a row, which is one short. Five failures with a success on the end, where a rule that looks at the start of the list gets it backwards. And calls that alternate between working and failing, where cutting the service off would throw away the half that still answer. A breaker that trips on noise is a breaker somebody switches off.
A service you call has answered nothing but errors for twenty seconds. Your breaker is shut, so you keep calling it and each call waits out your two-second timeout. Which of your own users are hurt by that?
All of them. This is the point of Step 5's slots arriving here. A call that is going to fail still occupies your client for the whole timeout, and your client only has so many places to hold requests. Fill them with doomed calls and a request for the healthy part cannot get out of the door, so one dead service turns into a dead product. Lab 13 counts exactly that, twice.
Step 8

Bulkheads

A ship is not one open space inside. It is divided into sealed compartments by walls called bulkheads, so that a hole lets water into one compartment rather than into the ship. The idea carries across without much translation.

A server with one pool of eight workers has one compartment. Any kind of work can take any worker. When one kind of work goes slow, it takes workers and holds them, and every other kind queues up behind it. One request in ten can end up holding all eight workers.

Splitting the eight into two separate pools, one for each kind of work, puts a wall between them. The slow kind still fails. It stops taking the fast kind down with it, and that is a choice you make in advance rather than a thing you discover.

What exactly is a pool of workers?

A fixed set of workers, and a rule about who may use them. A request that arrives while all of them are busy either waits in a queue or is turned away. Nothing about the machine changes when you split one pool into two: the same eight workers exist and the only new thing is a rule saying which requests each group of them will accept.

That rule is the whole mechanism. There is no clever scheduling, no priority, no measuring. Reports may use these five workers and searches may use those three, and neither can borrow from the other, ever, including when the other side is completely idle.

Why does slow work block quick work at all?

Because a queue is served in order. A search that would take 40 milliseconds, standing behind eight reports that will each take eight seconds, waits for a worker to come free like everybody else. Being quick does not move you up the line. This is called head-of-line blocking, after the one item at the front of a queue that holds up everything behind it.

It is worth being precise about what has gone wrong, because it is not what the numbers first suggest. Search has not slowed down, and search is not failing. Search is waiting, and the waiting is long enough that the clients give up and the answers become useless. Lab 15 shows the busy-workers line pinned at eight while the answered line falls to nothing.

Is holding workers aside not wasteful?

Yes, on an ordinary day. Reserved workers sit idle while the other side of the wall has a queue, and the shared pool would have used them. You are paying for a wall in exactly the same way a ship pays for bulkheads: in space you cannot otherwise use.

What you buy is that the failure has a shape you chose. Without the wall the shape is chosen for you by whichever kind of work happens to go slow, and the answer is usually all of it. Lab 16 lets you pick the split and reports what each one costs, so the trade is a number rather than an opinion.

Lab 15 · One slow thing, eight workers
Try this firstPress Run the minute. Searches and reports share one pool of eight workers. From second 15 the store the reports depend on goes slow and a report starts taking seven or eight seconds. Watch the dashed line of busy workers stick at eight while the answered line drops into the floor.
Reports are one request in ten, and they finish off nearly half the searches. Read the slowest-1-in-100 figure for search: nothing has been done to search, and yet a search now takes seconds. Nothing here is a bug in the search code, and no amount of making search faster would help, because search is not running. It is standing behind eight reports.
Lab 16 · Draw the wall
Try this firstPress Run every split. Seven runs, one for each way of dividing eight workers between the two kinds of work, plus the shared pool from Lab 15 as the dashed line to compare against. Then drag workers kept for search across all seven and read what each split costs.
No split saves the reports, and one of the splits does not save search either. Start at the far left, where search gets one worker of the eight. Search lands below the dashed line, which is to say it does worse than having no wall at all. A wall that tight starves the thing it was built to protect, because search is nine requests in ten and one worker cannot get through them. Move to two workers and search clears most of what it is asked; by three it is answering nearly everything, and the four splits above that add almost nothing. So the wall has a wrong setting as well as a right one, and "give the important work a bit of its own" is the shape of the answer rather than "give it as little as you can get away with". Reports, meanwhile, were going to fail whatever you choose, because the thing they depend on is broken. That is the trade: the wall does not save the slow work, it decides whether the slow work takes the quick work with it.
Reports are one request in ten, and they have gone slow. Searches are the other nine, and they are as quick as ever. All of them share eight workers. Why do the searches start failing?
Eight slow reports can hold all eight workers. It only takes as many slow requests as there are workers, so a tenth of your traffic is plenty. The load has not changed and search has not changed. What changed is how long each report holds a worker, and eight held workers is the same as no workers. The wall in Lab 16 fixes it by making some workers unavailable to reports at any price.
Step 9

Queues, and what a full one should do

A queue is a place to put a request while every worker is busy, so that a short burst does not turn into a refusal. Requests wait in the order they arrived and workers take them from the front. Nearly every server has one, and how deep it is allowed to get is a setting somebody chose, often without meaning to.

A queue can cover a burst. It cannot add capacity. If people are asking 150 times a second and the workers can finish 130, then 20 requests a second are piling up somewhere, and a deeper queue is a longer pile rather than a solution.

So a deep queue does not answer more work. It makes each answer take longer, and past a certain depth every request in it has already given up by the time a worker gets there.

How long does waiting in a queue actually add?

Work it out from the front of the queue. Eight workers each finishing a request every 60 milliseconds take one request off the front about every 7 or 8 milliseconds between them. So a request that joins a queue of 20 waits roughly 150 milliseconds before a worker even looks at it, and then takes its own 60 to be answered.

Now put the same arithmetic on a queue of 2000, which is a number real systems reach by leaving the default alone. That is around fifteen seconds of waiting. Nobody is still there. The queue is not holding work any more, it is holding a record of people who left.

Turning people away seems like the wrong thing to do

It feels wrong and it measures better, which is worth sitting with for a moment. Deliberately refusing work when there is no room for it is called load shedding. The refused request was not going to be answered in either design; the only question is whether the person finds out now or in four seconds.

A refusal in one millisecond is information. The client can ask a different copy, show the answer it kept from the last time it asked, or tell somebody honestly that this is not available right now. A refusal after four seconds of waiting is the same refusal with four seconds of the reader's life attached, and it also cost you a worker along the way.

Throwing out the longest waiter sounds unfair

It sounds unfair and it wastes the least. Whoever has been in the queue longest is the one most likely to have given up already, so their place is the one worth least. Serving them means spending a worker on an answer nobody will read, while somebody who arrived a moment ago and is still waiting gets refused.

This is the odd policy that wins in Lab 18, and it is worth knowing that the fair-looking rule and the useful rule come apart here. First in, first out is the fair one. When the queue is full and everybody has a deadline, the front of the queue is where the hopeless requests are.

Lab 17 · How deep should the queue be
Try this firstPress Run all eight queue sizes. Eight runs, all of them at 150 requests a second against a server that can finish about 130. The bars are answers per second and the line is how long a typical answer took. Then drag how many may wait from none, serve or turn away all the way up to 2000.
The bars stop rising almost immediately. The line goes on rising long after them. Somewhere around a queue of twenty the answers per second have got everything a queue can give, and every deeper setting after that buys waiting and nothing else. Read the two numbers together at the far right: the same answers per second as a queue of twenty, and a typical wait many times longer. A queue is a place to put work while a worker frees up. Longer than that, it is a place to make people wait before you disappoint them. The last two settings report the same typical wait, and the reason is worth having: at 150 requests a second against 130 finished, the pile grows by about 20 a second, so forty seconds never reaches two thousand waiting. The deepest setting on the slider is not a limit the run ever meets. It is the absence of one, which is exactly how real systems end up with it.
Lab 18 · What a full queue should do
Try this firstPress Run all three. Everybody in these runs gives up after 600 milliseconds. Three policies for a full queue stand side by side, and the red bars are work the server finished that nobody was waiting for any more. Then press each of the three buttons to read that one's numbers in full.
Compare the red bars first, then the answers per second. Letting the queue grow produces a mountain of finished work with nobody left to receive it, which is capacity spent on nothing. Turning the newest request away at the door answers more and tells the refused person immediately. Dropping the longest waiter answers the most of the three, for the reason in the note above, and it is the policy that looks least fair and behaves best.
Everybody gives up after 600 milliseconds. Your queue is deep enough that a request waits 4 seconds before a worker reaches it. What is that worker doing?
Work nobody wants. Giving up happens on the client's side, and the server does not hear about it, so a request sitting in a queue past its client's deadline still looks like work to be done. The worker finishes it into an empty room. The third answer is what you might reasonably hope for, and few systems do it. Assuming it happens is how a deep queue turns into a service that answers nobody.
Step 10

Graceful degradation

A page you look at is rarely one thing. It is a story from one service, your name from another, a count of comments from a third, a strip of recommendations from a fourth. Each of those has its own chance of not answering, and those chances are not small.

If the page refuses to appear unless all of them answered, you are back in Step 1 with a chain, and the weakest link in it is the piece nobody has ever needed. A recommendations strip that fails 14 times in 100 can blank a story that loaded perfectly.

Graceful degradation means deciding in advance which pieces are required and which are optional, then showing whatever arrived. An optional piece that did not answer either shows an older copy from a cache, which is a kept copy of the last good answer, or is quietly left out.

What is a cache, and why is an out-of-date answer allowed?

A cache is a copy of an answer you fetched earlier, kept nearby so you can use it again. Because it was fetched earlier it may be out of date, and for most pieces of a page that matters far less than it sounds."4 related stories, from an hour ago" is a real answer. A hole where the related stories should be is not.

The judgement is about which pieces can bear it. A stale comment count is fine. A stale bank balance is not, which is why the pieces that must be fresh are the ones you mark as required, and a service that cannot answer them fresh gets to stop the page.

How do you decide which pieces are required?

Ask what the reader came for. On a news page that is the story, and the answer is one piece, possibly two. Everything else on the page is something you are offering and an offer that cannot be made this second can simply not be made.

The habit that goes wrong is not deciding at all. Nobody sets out to make a recommendations strip compulsory. It happens because the page is built by fetching five things and rendering when all five arrive, and that code has quietly declared all five required. Lab 19 lets you break each piece in turn and see which of the two designs you are actually running.

What does null mean in Lab 20?

null is a value that stands for nothing here. In that lab, a piece whose service did not answer arrives with its value set to null, so parts[i].value === null is how the code asks "did this one answer?". Three equals signs is the ordinary way to test whether two values are the same.

It matters because null is not the same as empty. Put it on the page and the page prints the word null where the reader expected a comment count, which is worse than leaving the line out. That is exactly what the starting code in that lab does. A missing piece has to be recognised, not passed along.

Lab 19 · A page made of five pieces
Try this firstPress break the recommendations strip and look at the little page just under the row of break buttons. With show whatever answered chosen, the strip is simply left out and the story is still there. Now press all of it or none of it and watch the same broken strip replace the whole page with an apology.
Then press Load the page 600 times under each of the two designs. Each of the five services has its own chance of not answering, from 5 times in 1000 for the story to 14 times in 100 for the strip. The panel counts how many of the 600 loads were complete, how many were missing a piece, and how many showed nothing at all. Break the story itself and both designs give an apology, which is correct: that is the piece worth refusing to render. Fix everything puts all five back.
Lab 20 · Write the page builder
Try this firstPress Run. Three pieces go in and one line comes out, holding the three values this builder would put on the page: the story, and then null twice, because it passes along whatever it was handed. Fix page so a piece that did not answer uses its backup or is left out, then press Test it on seven pages.
Four rules, and the seven test pages check each of them. A piece that answered shows its value. A piece that did not, but has a backup, shows the backup. A piece with neither is left out entirely. And if any required piece is missing, the whole answer is the single apology line, however well everything else did. The empty page is in there too, because a page with no pieces is an empty page rather than an apology or a crash.
A page is built from five services and will not render unless all five answered. One of them supplies a strip of recommendations and fails 14 times in 100. How often does a reader get nothing at all?
At least 14 times in 100. Requiring all five makes a chain, and in a chain any one part being down is enough. So the worst piece sets the floor, and the other four failures add to it. Multiplying the five chances together is the sum for copies of one job from Step 2. Using it here is the most expensive arithmetic mistake in the course, because it turns "the page needs all five" into a number that looks reassuring.
Step 11

Keep a small service alive while a chaos panel breaks it

One service, two kinds of work, ninety seconds, and three separate pieces of trouble inside them. The panel above the run holds seven controls, and every one of them is an idea from the ten steps you have just done. Nothing new is introduced here.

The bar is 95 out of every 100 searches answered, and 90 out of every 100 requests of any kind served. The panel as you first find it does not clear it, which is the point: those are the settings a service ends up with when nobody has thought about failure.

Work out which stretch of the ninety seconds is losing people before changing anything. Every control covers one kind of trouble and does nothing at all about the other kinds.

What are the three pieces of trouble, and which step is each?

The list at the top of the lab says when each one starts. From second 20 to 28 there is a rush of about five times the traffic, which is Step 3 and Step 9: the queue and how much you retry. From second 40 to 58 the reviews store goes thirty times slower without failing, which is Step 5 and Step 8: the timeout, and the wall between the pools.

From second 66 to 84 the reviews store answers nothing but errors, slowly, which is Step 7 and Step 10: the breaker, and having something cached to serve instead. Then it recovers, and the last six seconds are there to show whether your design notices that it has.

Can a control that helped earlier make things worse here?

Yes, and that is most of the exercise. Extra attempts with no random wait are the stampede from Step 3 aimed at the rush in second 20. A long timeout during the slow stretch fills the caller's slots with requests that are going nowhere, which is Step 5 from the wrong end. A deep queue during the rush turns a refusal into a long wait and then a refusal.

So switching everything on is not the answer and never has been. Each control has a cost, and the run measures both sides of it, which is why the verdict line reports two numbers rather than a score.

Why are "answered" and "served" two different numbers?

Answered means a real, fresh answer from the service that owns the work. Served counts those plus the requests that got a cached reviews answer instead, once you switch that on. They are separated because they are worth different amounts to a reader, and rolling them together would let a design that serves nothing but stale copies look healthy.

Search has no cached fallback here, so its number is the strict one, and the bar asks more of it: 95 out of 100. The everything-served bar of 90 is where the cache is allowed to help. A design that clears both has decided in advance which of its promises it is willing to soften.

Lab 21 · Keep it alive
Try this firstPress Run the 90 seconds before touching any control. The list at the top says what is going to happen to you and when. The verdict under the chart says the run missed the bar and by how much, and the last line names the single worst second and which controls cover that kind of trouble.
One control at a time, and predict before you run. Move the timeout down and the extra attempts up, choose a waiting rule for those attempts, then try the three switches. When you want to see a design that clears the bar, press Show me one that holds. It moves every control to such a setting and runs it, so the answer is readable off the panel rather than out of a paragraph. Then switch one of those controls back off and run it again. Two of them lose you the bar on their own: take the separate pools away and search collapses, take the cached reviews away and the everything-served figure drops under 90 while search stays fine. The breaker is the surprise. With a wall and a cache already in place it turns out to be worth almost nothing here, because the wall is keeping search out of the way and the cache is already answering the requests the breaker would have refused. A control that is indispensable in one design is redundant in another, which is why this panel measures instead of awarding points.
Lab 22 · The bench
Try this firstPress Just under capacity. It moves the six sliders for you and runs a minute: 120 people a second against 8 workers taking 60 milliseconds each, which is about as much as they can do. Then press Well over capacity and compare the same numbers.
Nothing here is marked, and there is no target. Compare "answered per second" with "the most it could do", which is the workers divided by how long a request takes, and you can predict that second number before every run. Then try to break it in a way you can describe out loud first: too few workers, a timeout below the time the work takes, a queue so deep that everyone in it has left. The most useful runs are the ones where the result is not what you said it would be.
In your run, the red bars are worst between second 66 and second 84, while the reviews store is answering nothing but errors. Which two controls are the ones to reach for?
The breaker and the cache. Requests to a dead service have to be refused quickly, or they hold slots and take search down with them, and reviews readers need something to look at rather than a fresh answer nobody can produce. The queue and the extra attempts are for the rush at second 20 and do nothing at second 70. More workers is not on the panel at all, and it is the reflex worth losing: this course is about the failures that no amount of hardware would have prevented.

What you can do now

  • Work out how often a chain of parts fails, and how much a copy buys you, from the numbers on the parts.
  • Name the event that takes out every copy of something, which is the number that actually matters.
  • Recognise a retry stampede, and write a waiting rule with growth and randomness in it.
  • Choose a timeout with both of its costs in view, and say what a caller's slots have to do with it.
  • Make an operation safe to retry by giving it a key and remembering keys.
  • Cut off a dead service instead of queueing behind it, and let one probe find out when it is back.
  • Put a wall between two kinds of work so that the slow one cannot take the quick one with it.
  • Size a queue, and decide what a full one should do rather than letting it grow.
  • Decide which pieces of a page are required, and serve the rest from a cache or leave them out.

Where this goes

  • Distributed Consistency. This course kept one copy of the truth. That one asks what happens when two machines hold copies of the same fact and cannot both be right. Clocks that disagree, reads that return yesterday's answer, and a network split that leaves two halves each certain they are in charge.
  • Raft and Consensus. The other side of the same problem. A group of machines elects a leader, copies a log of decisions to each other, and keeps working while you kill whichever one is in charge.
Step 12

Name the faults you must tolerate

A design cannot tolerate “failure” in general. A process may stop, a message may be lost, a clock may be late, storage may return old data, or a component may send contradictory answers. The fault model says which behaviours the design must handle and which assumptions it is allowed to make. A test outside that model can still be useful, but it does not disprove a narrower claim.

CrashA component stops and sends nothing more.
OmissionA request or response goes missing.
TimingAn answer arrives outside the useful time bound.
ArbitraryA component can send wrong or conflicting messages.

A retry addresses omission only when repeating the operation is safe. Replication addresses a crash only when replicas do not share the same failure and clients know which result to trust. A deadline limits waiting, but it cannot tell whether the remote component is dead or merely slow. Arbitrary, sometimes called Byzantine, behaviour needs validation and protocols designed for that stronger model. Naming the model prevents a familiar mistake: adding a mechanism whose guarantee does not cover the actual fault.

Fault, error and failure are different points in a chain

A fault is the cause, such as a flipped bit or bad deployment. It creates an error, an incorrect internal state. A failure is when the service delivered at its boundary no longer meets its promise. Redundancy can keep an internal error from becoming a visible failure. Monitoring only visible failures misses repaired faults; alarming on every internal fault wakes people for events the system already contained.

Lab 23 · Match a defence to its fault model
Try this firstSelect message omission and enable only restart. Then add a bounded retry with an idempotency key.
The result names the uncovered assumption. A larger collection of controls is not automatically a stronger design; each must cover a fault in the stated model.
A timeout expires. What has the client learned for certain?
A timeout is a timing observation. The server may be dead, slow, partitioned or finished after the client stopped waiting.
Step 13

Turn reliability into a budget

A service-level indicator is a measured user-facing result, such as the fraction of valid requests answered correctly within 300 ms. A service-level objective sets a target over a window. If the target is 99.9 per cent, the remaining 0.1 per cent is the error budget. For one million requests that is one thousand unsuccessful requests. The budget makes the trade explicit: a team can change quickly while evidence says the promise is safe, and must spend time on reliability when the budget is being consumed too fast.

Choose the eligible population carefully. Health checks, load tests and invalid requests may not represent user work. An availability SLI that counts any HTTP response as success can call an error page healthy. A latency objective needs a correctness condition too. Budgets can be request-based or time-based; state which one is used before translating a percentage into minutes.

Burn rate answers “how fast are we spending?”

A burn rate of one consumes the budget exactly across the whole window. A rate of ten would use it ten times as fast. Short fast-burn alerts catch severe outages; longer slow-burn alerts catch persistent damage without paging on a brief measurement spike. Alert on user impact and keep diagnostic component alerts available for investigation.

Reference: Google SRE error-budget policy example.

Lab 24 · Spend a four-week error budget
Try this firstSet the objective to 99.9 per cent and record 400, then 700 unsuccessful requests. Watch the remaining budget and burn policy change.
The arithmetic is simple because the policy is the hard part. Decide in advance what release or incident action follows each burn threshold.
One million eligible requests have a 99.9 per cent success objective. How many may be unsuccessful before the objective is missed?
One tenth of one per cent is one thousand out of one million.
Step 14

Recover data, not only processes

Restarting a program restores computation. It does not restore deleted or corrupted data. A replica can copy a bad write immediately, so replication is not a backup. A backup keeps an older, independently recoverable state. Versioning, immutability, separate credentials and a separate failure domain reduce the chance that the same mistake destroys both the live data and its recovery copy.

Recovery point objective (RPO) is the maximum acceptable amount of recent data loss, measured backward from the incident. Recovery time objective (RTO) is the maximum acceptable time to restore the service. Backup interval, replication lag, restore bandwidth, dependency order and human steps all contribute. A backup job that reported success is not evidence of recovery until a restore is performed and the result is checked.

Restore order and application consistency

A database snapshot, object store and message log taken at unrelated instants may each be internally valid but disagree with one another. Define a recovery point across dependencies or provide reconciliation. Restore identity, encryption keys, schemas and configuration as well as data, using a path that does not depend on the failed control plane.

Reference: AWS Reliability Pillar, including tested backups and disaster recovery.

Lab 25 · Meet an RPO and RTO with a real restore
Try this firstChoose nightly backup and a 4-hour restore. Compare it with a 15-minute RPO and 60-minute RTO, then add continuous logs and a rehearsed restore.
Both objectives must pass. The lab also refuses an untested backup because estimated recovery is not verified recovery.
Why is a continuously updated replica not, by itself, a backup?
Replication preserves availability; independent history supports recovery from bad state.
Step 15

Use quorums and fence stale writers

Replicated data creates a new question: which copies must participate before an operation is accepted? With N replicas, a write quorum W and read quorum R overlap when R + W is greater than N. A majority write quorum also prevents two disjoint groups from both accepting writes in a simple crash-fault model. These inequalities are useful, but a real protocol must also order versions and repair lagging replicas.

A network partition can leave an old leader still running. A lease reduces that risk only under its clock and timing assumptions. A fencing token is a number that increases with each new ownership grant. The storage system rejects a write carrying an older token, even if the stale worker wakes up and still believes it owns the resource. The check must happen at the resource being protected.

Quorum is not a complete consensus protocol

Membership changes, leader election, log ordering, durable votes and recovery after restart need a protocol such as Raft or another design with a stated fault model. The Raft Consensus course builds that protocol. The When Machines Disagree course covers clocks, ordering and consistency choices. This step provides the arithmetic and the stale-writer problem those courses rely on.

Lab 26 · Split five replicas and reject an old writer
Try this firstSet R to 2 and W to 3. Check read/write overlap, then send a write with fencing token 7 after token 8 has been granted.
Two independent checks appear. Quorum overlap protects version visibility; the fencing token protects the external resource from an old owner.
Five replicas use read quorum 2 and write quorum 3. Is read/write overlap guaranteed?
Equality allows disjoint sets. For example, two replicas can answer the read while the other three accepted the write.
Step 16

Separate startup, readiness and liveness

A health check is a control input, not a status badge. A startup check asks whether initialization has finished. A readiness check asks whether this instance should receive new traffic. A liveness check asks whether restarting the process is likely to repair it. Using one check for all three can create a restart loop: a slow start fails liveness, gets killed and never reaches ready.

Readiness may include a required dependency, but liveness usually should not. If every instance restarts when one database is unavailable, the database outage becomes an application restart storm. Thresholds filter brief noise but delay detection. Probe timeouts, frequency and work cost affect the system being measured. A successful local check also says nothing about a broken network path between the load balancer and the instance.

Failure detectors can be useful without being perfect

In an asynchronous network, a silent component may be failed or only delayed. A practical detector therefore makes suspicions from time bounds and can make mistakes. The system must define what a false suspicion costs: briefly stop traffic, restart a process, elect a new leader or revoke a lease. Thresholds and independent observations reduce noise, but they do not turn silence into proof.

Current reference: Kubernetes startup, readiness and liveness probe guidance.

Lab 27 · Diagnose three probe mistakes
Try this firstSelect slow startup and use only a 5-second liveness threshold. Then add a startup allowance and compare restarts and traffic errors.
The same endpoint is not right for every question. Test slow start, internal deadlock and dependency outage separately.
A required database is temporarily unavailable, but the application process is healthy. Which action is normally appropriate?
Stop sending work the instance cannot serve without turning a dependency outage into a restart storm.
Step 17

Run a bounded failure experiment

Chaos engineering is an experiment, not random damage. Write the steady-state measure, the expected result under one injected fault and the observation that will disprove the hypothesis. Verify monitoring and the stop control. Start with a simulator or test environment, then the smallest representative production scope when the risk and authority allow it.

Limit the target, duration and failure type. Keep an unaffected control group. Stop automatically when a user or safety threshold is crossed. Confirm that removing the injection restores the target. Preserve timestamps, configuration and results. A successful experiment says the system handled this fault under these conditions; it is not proof against every failure.

Do not test a recovery path that depends on the experiment tool

If the stop button, metrics or recovery command shares the network or control plane being disrupted, the safety mechanism may disappear with the target. Keep an independent abort path and a firm deadline. Get explicit permission for the named environment and blast radius.

Current practice: Chaos Mesh scope controls and Principles of Chaos Engineering.

Lab 28 · Write the safety envelope before injecting latency
Try this firstTarget 50 per cent of production for 10 minutes without an abort threshold. Reduce the scope and add the stop condition until the experiment is permitted.
The lab grades the plan before running the fault. A useful hypothesis cannot compensate for an unsafe blast radius.
What should be written before a production chaos experiment starts?
The experiment needs a claim and a safety envelope.
Step 18

Build an incident timeline from evidence

During an incident, stabilise the service before searching for a perfect explanation. Name an incident lead, keep an event log and state the current user impact. Prefer a reversible mitigation: stop a rollout, shed optional load, shift traffic or disable a feature. Preserve evidence before it rotates away. Diagnosis can continue after the immediate harm is contained.

Metrics show rates and distributions. Logs record events with context. Traces connect one request across services. Profiles show where code spent resources. Deployment, configuration and dependency events provide the change history. Clocks may differ, sampling may omit events and dashboards may aggregate away the clue, so attach source and uncertainty to each point on the timeline.

Post-incident review without hindsight

Reconstruct what people and systems knew at the time. Separate trigger, contributing conditions, detection, mitigation and recovery. Assign actions that change a control, test, tool or design, with an owner and verification. “Be more careful” is not an engineering control. Share the learning without turning the review into a search for one person to blame.

Current signal definitions: OpenTelemetry traces, metrics, logs and profiles.

Lab 29 · Assemble a defensible incident timeline
Try this firstSelect “bad rollout” and add only latency metrics. Then add deployment events, traces and errors until the trigger and impact can be distinguished.
A timeline item needs a source. The lab leaves unknowns visible rather than filling them with a plausible story.
Users are currently failing requests during a rollout. What comes before a complete root-cause investigation?
Reduce harm first. Keep a timeline so diagnosis and learning remain possible.
Step 19

Rehearse deployment and regional recovery

Changes are a major source of failure, so deployment is part of fault tolerance. A canary exposes a small slice. A rolling update replaces instances gradually. Blue-green keeps old and new environments for a switch. Every plan needs compatibility rules for schemas, messages and mixed versions, plus a rollback path that still works after new data has been written.

A regional recovery plan must meet the RPO and RTO from step 14, not merely start spare machines. Backup and restore is inexpensive but slow. Pilot light keeps critical data services ready. Warm standby runs a smaller working copy. Active-active serves from several sites but adds coordination and failback complexity. Check quotas, credentials, configuration drift, routing and the dependencies the recovery path assumes.

Where incident AI can help, and where it must stop

An assistant can group alerts, search runbooks, draft a timeline and suggest tests. Keep source links and confidence with each claim. Do not let generated commands bypass change control or expand the authorised blast radius. A human incident lead owns destructive actions, external communication and the recovery decision. Test AI-generated runbook edits in the same rehearsal as hand-written ones.

Recovery strategies: AWS Well-Architected disaster recovery guidance.

Lab 30 · Rehearse failover and failback
Try this firstChoose backup-and-restore with configuration drift. Compare its measured RTO and RPO with warm standby, then add a tested failback.
A recovery that cannot fail back is unfinished. The simulator keeps cost, data loss, downtime, configuration and rehearsal evidence in separate columns.
Why must a regional failover be rehearsed rather than judged from architecture diagrams?
The plan is a claim until the whole path is timed and checked.

What you can now do

  • State a fault model and match each control to the behaviour it covers.
  • Turn a user-facing SLO into an error budget and burn policy.
  • Test backups against RPO, RTO and application consistency.
  • Check quorum overlap and reject stale writers with fencing tokens.
  • Separate startup, readiness and liveness decisions.
  • Run a bounded chaos experiment and preserve a sourced incident timeline.
  • Rehearse compatible deployment, regional failover and failback.