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.
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.
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.
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
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.
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.
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.
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.
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.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.
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.
charge so that they are, then press Send every
request twice and check the money.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.
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.
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.
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.
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.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.