Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 3 – Performance and reliability fundamentals

Redundancy, failover and static stability

Size N+1 and N+2 capacity across zones, keep serving when the control plane fails, prefer constant work, and stop health checks causing outages.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free: 30 minutes

What you will learn

  • Calculate the per-zone capacity that surviving the loss of one or two zones needs, for two to five zones
  • Design data planes that keep serving when the control plane that manages them is impaired
  • Compare constant-work designs with designs that switch into a different mode during failures
  • Identify health checks that can take a whole fleet out of service at once, and fix them

Before you start

What you’ll learn · Before you start

What you will learn

  • Calculate the per-zone capacity that surviving the loss of one or two zones needs, for two to five zones
  • Design data planes that keep serving when the control plane that manages them is impaired
  • Compare constant-work designs with designs that switch into a different mode during failures
  • Identify health checks that can take a whole fleet out of service at once, and fix them

Before you start

On this page

Redundancy means keeping more than one of everything that matters, so that losing one copy does not stop the service. Failover is what happens when a copy fails: its work moves to the copies that remain, either at once, as a load balancer does for identical servers, or by promoting a standby, as a database does for its primary. Static stability is the property that makes failover trustworthy: the system keeps working through a failure without having to change anything, because the spare capacity is already running and every server already holds what it needs. The opposite design, which plans to launch servers or fetch configuration after something breaks, depends on machinery that is likely to be impaired at that very moment. This lesson sizes spare capacity across zones, separates control planes from data planes, compares constant work with failure modes, and keeps health checks from causing the outages they should detect.

How much spare capacity: surviving the loss of a zone

Cloud regions are divided into zones, separate locations built so that a power, cooling or network failure in one is unlikely to take the others down with it (Compute Engine documentation), and every AWS region has at least three (AWS documentation). A service spread over three zones survives the loss of one only if the other two can carry the whole peak. That is the arithmetic people get wrong: each zone needs half of the peak, not a third, so the fleet holds 150 % of the peak and runs at about two thirds of its capacity at a normal peak. The Builders’ Library describes exactly this sizing, three zones overprovisioned by 50 %, each running at about 66 % of the load it was tested for (static stability using Availability Zones).

In general, a service in z zones that must survive the loss of k of them needs each zone to carry a share of 1 ÷ (z − k) of the peak. The script turns that into servers for a peak of 30,000 requests a second and servers that handle 500 each.

Capacity per zone for two to five zones Python · az_capacity.py
# How much capacity must each zone hold so that the service still carries its peak after losing zones?
# A zone that may fail must not be needed: the zones that remain carry the whole peak.
import math

PEAK = 30_000  # requests a second at the daily peak
PER_SERVER = 500  # requests a second one server handles at its load-tested limit

print(f"Peak {PEAK:,} requests a second; one server handles {PER_SERVER} at its load-tested limit")
print(f"{'zones':>5} {'survives':>9} {'each zone':>10} {'fleet':>7} {'busy, all up':>13} {'servers/zone':>13} {'servers':>8}")
for zones in (2, 3, 4, 5):
    for lost in (1, 2):
        if lost >= zones:
            continue
        share = 1 / (zones - lost)  # of the peak, per zone
        per_zone = math.ceil(PEAK * share / PER_SERVER)
        print(
            f"{zones:>5} {f'{lost} zone' + ('s' if lost > 1 else ''):>9} {share:>10.0%} {zones * share:>7.0%}"
            f" {1 / (zones * share):>13.0%} {per_zone:>13} {zones * per_zone:>8}"
        )

# A deploy takes 10 % of each zone's servers out at a time. To lose a zone during a deploy, size for both.
zones, lost, out_for_deploy = 3, 1, 0.10
per_zone = math.ceil(PEAK / (zones - lost) / PER_SERVER / (1 - out_for_deploy))
print(f"\n3 zones, survive 1 zone while 10 % of servers are out for a deploy: {per_zone} servers per zone, "
      f"{zones * per_zone} in all, busy at {PEAK / (zones * per_zone * PER_SERVER):.0%} of their limit at the peak")

Output

Peak 30,000 requests a second; one server handles 500 at its load-tested limit
zones  survives  each zone   fleet  busy, all up  servers/zone  servers
    2    1 zone       100%    200%           50%            60      120
    3    1 zone        50%    150%           67%            30       90
    3   2 zones       100%    300%           33%            60      180
    4    1 zone        33%    133%           75%            20       80
    4   2 zones        50%    200%           50%            30      120
    5    1 zone        25%    125%           80%            15       75
    5   2 zones        33%    167%           60%            20      100

3 zones, survive 1 zone while 10 % of servers are out for a deploy: 34 servers per zone, 102 in all, busy at 59% of their limit at the peak

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 az_capacity.py

More zones make redundancy cheaper: surviving one lost zone costs 100 % extra with two zones, 50 % with three, 33 % with four and 25 % with five. Surviving two lost zones is far more expensive, 200 % extra with three zones. Real fleets also lose capacity for planned reasons. Google’s SRE book states capacity needs as intents such as “N + 2 redundancy” in each region (SRE book, chapter 18); two spare units cover, for example, one failure while another unit is down for maintenance. The last line applies the same idea to zones: surviving a lost zone while 10 % of the servers are out for a deploy takes 34 servers per zone instead of 30.

Redundancy helps only against failures that do not hit every copy at once. Zones protect against power and network failures in one place; they do not protect against a bad configuration or a bad release pushed to every zone in the same minute. That is why careful teams roll changes out one zone at a time: a defect then fails one copy, not all.

Redundancy inside India

A Reserve Bank of India direction requires payment system operators to store the entire data of their payment systems in India only (RBI notification). For a payments or UPI service, that rules out a recovery region abroad: zone redundancy inside an Indian region carries most of the load, and disaster recovery means a second region in India. Large cloud providers run more than one region in India; AWS, for example, lists two Indian regions with three Availability Zones each.

Failover: active-active and active-standby

Active-active suits servers that keep no state of their own. Every zone serves traffic all the time; when a zone fails, its servers fail their health checks, and the load balancer sends their traffic to the zones that remain. The Builders’ Library’s example, a load-balanced fleet in three zones sized as above, needs no action at all when a zone is impaired. Active-standby suits things with one writer, such as a database primary: all writes go to the primary, and a warm standby in another zone is ready to take over. Its managed database example fails over by promoting the standby and pointing the database’s DNS name at it, and clusters with a leader elect the new leader from standbys that are already running, rather than starting a new node during the failure.

Both kinds of failover share one rule: exercise it. The Builders’ Library warns that a path used only during failures is rarely tested and tends to fail when it is finally needed, and suggests turning such fallback into failover that runs all the time in production, for example by serving from both copies routinely (Avoiding fallback). A failover you have never run is a hope, not a plan.

Static stability: keep working without the control plane

Most systems have two parts. The control plane makes changes: it creates, updates and deletes resources, such as launching a server, creating a storage bucket or changing a route. The data plane does the system’s actual work: a running server answering requests, reads and writes to a disk, a DNS server answering queries. AWS designs its data planes with deliberately fewer moving parts than its control planes, which are complex workflows with databases behind them, so failures are statistically less likely in the data plane (control planes and data planes).

A design is statically stable when it keeps working while a dependency is impaired: it may not see new changes, but everything it was doing before keeps working (static stability using Availability Zones). A running EC2 virtual machine is the textbook case: once launched, it needs no control plane to keep running, and its host already holds the routing information its packets need. The same idea applies to how you recover. A plan that says “if a zone fails, the autoscaler launches more servers in the others” makes recovery depend on the control plane at the moment it is most stressed, and the AWS whitepaper puts the principle plainly: the most reliable recovery mechanisms are the ones that need the fewest changes (static stability).

After a zone fails, one design must launch servers through the control plane; the statically stable one already has the capacity.Recovers by changingStatically stableZone 3 of 3 failsZones 1 and 2 hold34 % of the peak each:68 % is servedControl plane launchesservers: about 8 minutes,if its API worksZones 1 and 2:100 % againZone 3 of 3 failsZones 1 and 2 hold50 % of the peak each:100 % is servedtraffic movesautoscaler asksservers readytraffic moves; nothingelse has to happencompare

Recovering by changing, or not needing to change

Text description of the diagram

The diagram compares two designs after one of three zones has failed, from top to bottom.

The first design recovers by changing.

  1. Zone 3 of 3 fails, and its traffic moves to zones 1 and 2.
  2. Zones 1 and 2 hold 34 % of the peak each, so 68 % of the traffic is served.
  3. The autoscaler asks the control plane to launch servers, which takes about 8 minutes, and only if the control plane's API works during the same event.
  4. Once the servers are ready, zones 1 and 2 serve 100 % again.

The second design is statically stable.

  1. Zone 3 of 3 fails, and its traffic moves to zones 1 and 2.
  2. Zones 1 and 2 hold 50 % of the peak each, so 100 % of the traffic is served, and nothing else has to happen.

The simulation compares the two designs after a zone fails at the peak: one holds 34 % of the peak per zone and launches servers after the failure, which take 8 minutes once the request succeeds; the other holds 50 % per zone. The third row assumes the same event impairs the control plane for 25 minutes, an assumption you can change.

Recovering by scaling up, or not needing to Python · zone_loss_sim.py
# One of three zones fails at minute 0, at the daily peak. Two designs:
#   scale up after the failure: each zone holds 34 % of the peak, and the autoscaler asks the control plane for new
#     servers, which take 8 minutes to launch and warm up once the request succeeds;
#   statically stable: each zone already holds 50 % of the peak, so the two zones left carry it without any change.
# The same event may also impair the control plane (the launch API) for a while: the third scenario assumes
# 25 minutes. Minute by minute, what share of the peak traffic is served?
PEAK = 1.0
BOOT_MINUTES = 8


def served(minute, per_zone, scales_up, control_plane_back):
    capacity = 2 * per_zone  # one zone of three is gone
    if scales_up:
        launched_at = max(0, control_plane_back)  # the launch request succeeds once the control plane answers
        if minute >= launched_at + BOOT_MINUTES:
            capacity = PEAK  # the new servers fill the gap
    return min(PEAK, capacity)


scenarios = [
    ("scale up, control plane works", 0.34, True, 0),
    ("scale up, control plane down 25 min", 0.34, True, 25),
    ("statically stable (50 % per zone)", 0.50, False, 25),
]
minutes = [0, 5, 8, 10, 20, 30, 40]
print(f"{'design':<37}" + "".join(f"{f'{m} min':>8}" for m in minutes) + f"{'lost in 60 min':>16}")
for name, per_zone, scales_up, back in scenarios:
    shares = [served(m, per_zone, scales_up, back) for m in range(60)]
    lost = sum(PEAK - s for s in shares) / 60  # share of an hour's peak traffic that was refused
    row = "".join(f"{served(m, per_zone, scales_up, back):>8.0%}" for m in minutes)
    print(f"{name:<37}{row}{lost:>16.1%}")

Output

design                                  0 min   5 min   8 min  10 min  20 min  30 min  40 min  lost in 60 min
scale up, control plane works             68%     68%    100%    100%    100%    100%    100%            4.3%
scale up, control plane down 25 min       68%     68%     68%     68%     68%     68%    100%           17.6%
statically stable (50 % per zone)        100%    100%    100%    100%    100%    100%    100%            0.0%

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 zone_loss_sim.py

When the control plane works, the reactive design refuses 32 % of the traffic for 8 minutes, 4.3 % of the hour’s peak traffic. When the control plane is impaired too, the gap lasts 33 minutes and costs 17.6 %. The statically stable design loses nothing in either case, because it never asks for anything. The same thinking extends beyond capacity:

  • Keep the last good configuration. A server that reads its routing rules or feature flags from a configuration service should cache the last version it loaded and keep serving with it when that service is down.
  • Push what servers need before they need it. The Builders’ Library’s example is credentials delivered to every server ahead of time and valid for hours, so that requests keep working even if delivery stops for a while.
  • Keep standbys warm and running, and avoid recovery steps that need the thing that has failed.

Constant work: the same work in good times and bad

A coffee urn makes coffee for one person or for a hundred with the same effort: it does a constant amount of work whoever shows up. The Builders’ Library uses the urn to describe constant-work systems, which do the same operations in all conditions, have no special modes, and if anything do less work under stress (constant work). Its examples are concrete. Every few seconds, Amazon Route 53’s health check aggregators send the Route 53 DNS servers a fixed-size table of health statuses, so a zone failure that flips thousands of statuses changes the contents of the table, not the amount of work. AWS Hyperplane nodes fetch their whole configuration file every few seconds, even when nothing has changed.

The alternative sends only changes, which is cheaper on a quiet day and much busier on a bad one. The example compares the two for 10,000 health-checked targets when a zone failure takes 3,300 of them down at once, with 1 % of messages lost on the way.

Sending changes, or sending the whole table every second Python · constant_work.py
# Health checkers watch 10,000 targets and tell the DNS servers which are healthy. Two ways to send the news:
#   changes only: one update per target whose state changed; the DNS server applies each update in 5 ms;
#   full table: every second, a fixed-size table of all 10,000 states, applied in one 20 ms step, changed or not.
# Normally 5 targets change state a second. At 30 s a zone fails and 3,300 targets go down at once. Also, 1 % of the
# messages are lost on the way, and a lost change stays lost until that target changes state again.
import random

TARGETS, SECONDS, FAIL_AT = 10_000, 60, 30
UPDATE_MS, TABLE_MS = 5, 20


def run(full_table, seed=2):
    rng = random.Random(seed)
    truth = [True] * TARGETS  # what the health checkers see
    seen = [True] * TARGETS  # what the DNS server believes
    backlog = []  # updates waiting to be applied (changes-only design)
    busiest = 0
    for second in range(SECONDS):
        changed = rng.sample(range(TARGETS), 5)
        if second == FAIL_AT:
            changed += range(3_300)  # a whole zone goes down
        for t in changed:
            truth[t] = not truth[t] if second != FAIL_AT or t >= 3_300 else False
        if full_table:
            work_ms = TABLE_MS  # the same work every second
            if rng.random() > 0.01:  # this second's table arrived
                seen = truth[:]
        else:
            backlog += [(t, truth[t]) for t in changed if rng.random() > 0.01]
            applied, backlog = backlog[: 1000 // UPDATE_MS], backlog[1000 // UPDATE_MS :]
            for t, state in applied:
                seen[t] = state
            work_ms = len(applied) * UPDATE_MS
        busiest = max(busiest, work_ms)
        wrong = sum(1 for a, b in zip(truth, seen) if a != b)
        if second in (FAIL_AT - 1, FAIL_AT, FAIL_AT + 5, FAIL_AT + 15, SECONDS - 1):
            print(f"  {second:>3} s: DNS server busy {work_ms:>4} ms of the second; {wrong:>5,} targets shown wrongly")
    return busiest


for name, full in [("changes only", False), ("full table every second", True)]:
    print(f"{name}:")
    busiest = run(full)
    print(f"  busiest second: {busiest} ms of work\n")

Output

changes only:
   29 s: DNS server busy   25 ms of the second;     1 targets shown wrongly
   30 s: DNS server busy 1000 ms of the second; 3,052 targets shown wrongly
   35 s: DNS server busy 1000 ms of the second; 2,073 targets shown wrongly
   45 s: DNS server busy 1000 ms of the second;   175 targets shown wrongly
   59 s: DNS server busy   25 ms of the second;    41 targets shown wrongly
  busiest second: 1000 ms of work

full table every second:
   29 s: DNS server busy   20 ms of the second;     0 targets shown wrongly
   30 s: DNS server busy   20 ms of the second;     0 targets shown wrongly
   35 s: DNS server busy   20 ms of the second;     0 targets shown wrongly
   45 s: DNS server busy   20 ms of the second;     0 targets shown wrongly
   59 s: DNS server busy   20 ms of the second;     0 targets shown wrongly
  busiest second: 20 ms of work

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 constant_work.py

Sending changes, the DNS server spends whole seconds on nothing but updates: the 3,300 changes take about 16 seconds to apply, and in the meantime it shows up to 3,000 dead targets as healthy. Lost messages leave errors behind for good: 41 targets are still wrong at the end of the minute. The full table costs 20 ms every second, the same in the failure as before it, shows every change within a second, and repairs a lost table with the next one: it is always in “repair everything” mode. The price is waste, sending 10,000 states when 5 changed, which is negligible for configuration and status but not for everything. The same article notes that for web servers, scaling elastically is often the better fit, because the savings are large and the scaling runs often enough to stay well tested.

Health checks that do not take the fleet down

Health checks decide which servers get traffic, so a bad one can cause the outage it should prevent. The Builders’ Library describes four kinds: liveness checks that a process answers at all; local checks of resources the server does not share with its peers, such as its disk; dependency checks of the services it calls; and anomaly detection, which compares each server with its peers (implementing health checks). The danger is a check whose failure is correlated across servers. If every server’s check calls the same database and the database slows down, every server fails its check at the same moment, and a load balancer that obeys removes the whole fleet, including servers that could have served cached pages.

Three habits avoid it:

  1. Keep fast load-balancer checks local (liveness and local checks), and leave dependency checks to monitoring that alarms, or acts slowly with a cap on how many servers it may remove.
  2. Fail open. When every server looks unhealthy, the check is more likely wrong than the fleet; the Builders’ Library notes that some load balancers then send traffic to all servers.
  3. Answer health checks first under load. A server that is too busy to answer its check is removed, and its load lands on the others, which can start a downward spiral.

Kubernetes separates the two questions clearly. A failing liveness probe restarts the container, and a failing readiness probe only stops sending it traffic; its documentation warns that badly made liveness probes can cause cascading failures, and suggests a liveness probe that checks the app itself while the readiness probe also checks the back-end services it needs (Kubernetes probes).

Practice

Exercise · Medium · Python

Size a fleet that survives the loss of a zone

Write three capacity helpers in zones.py for a service spread evenly over several zones.

zone_share(zones, zones_lost) returns the share of the peak load that each zone must be able to carry so that the service still carries the whole peak after zones_lost zones have failed: the zones that remain carry it all, equally. If zones_lost is not smaller than zones, nothing remains, so raise ValueError.

servers_per_zone(peak_rps, per_server_rps, zones, zones_lost, out_for_deploy=0.0) returns how many servers each zone needs. One server handles per_server_rps requests a second at most. out_for_deploy is the share of each zone's servers that may be out of service at once during a deploy (0.1 means 10 %), so the servers that remain must still be enough. Round up to whole servers.

busy_at_peak(peak_rps, per_server_rps, servers) returns how busy the servers are at the peak when all of them are up: the peak divided by what all of them can handle together, as a fraction (0.5 means half of their capacity).

For example, zone_share(3, 1) is 0.5, servers_per_zone(30_000, 500, 3, 1) is 30, and the same with out_for_deploy=0.1 is 34.

Starter code · zones.py

import math


def zone_share(zones, zones_lost):
    """The share of the peak each zone must carry so that the zones left after a failure carry it all."""
    # Replace this line with your code.
    return 0


def servers_per_zone(peak_rps, per_server_rps, zones, zones_lost, out_for_deploy=0.0):
    """Servers each zone needs to survive the loss of `zones_lost` zones, with a share out for a deploy."""
    # Replace this line with your code.
    return 0


def busy_at_peak(peak_rps, per_server_rps, servers):
    """How busy all the servers are at the peak when none has failed (a fraction)."""
    # Replace this line with your code.
    return 0
The sample tests · test_zones.py
import math

from zones import busy_at_peak, servers_per_zone, zone_share


def raises_value_error(function, *args):
    try:
        function(*args)
    except ValueError:
        return True
    return False


def test_zone_share():
    """the zones that remain share the whole peak"""
    assert zone_share(3, 1) == 0.5
    assert zone_share(2, 1) == 1
    assert math.isclose(zone_share(4, 1), 1 / 3)
    assert math.isclose(zone_share(5, 2), 1 / 3)


def test_no_zone_left():
    """losing every zone cannot be planned for"""
    assert raises_value_error(zone_share, 2, 2)
    assert raises_value_error(zone_share, 3, 4)
    assert not raises_value_error(zone_share, 3, 2)


def test_servers_per_zone():
    """servers for the zone's share of the peak, rounded up"""
    assert servers_per_zone(30_000, 500, 3, 1) == 30
    assert servers_per_zone(30_000, 500, 4, 1) == 20
    assert servers_per_zone(30_000, 500, 3, 2) == 60
    assert servers_per_zone(10_001, 1_000, 2, 1) == 11


def test_servers_with_a_deploy():
    """servers out for a deploy must not be needed either"""
    assert servers_per_zone(30_000, 500, 3, 1, out_for_deploy=0.1) == 34
    assert servers_per_zone(12_000, 400, 3, 1, out_for_deploy=0.25) == 20


def test_busy_at_peak():
    """with every zone up, the spare capacity shows as low utilisation"""
    assert math.isclose(busy_at_peak(30_000, 500, 90), 2 / 3)
    assert math.isclose(busy_at_peak(30_000, 500, 120), 0.5)
    assert math.isclose(busy_at_peak(10_000, 1_000, 40), 0.25)
A hint

After the failure, zones - zones_lost zones remain, and each carries an equal part of the peak. For the servers, divide the zone's share of the peak by what one server handles, then divide by the share of servers still in service (1 - out_for_deploy), and round up with math.ceil only at the end.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 A service runs in 4 zones and must carry its full peak after losing any one of them. What share of the peak must each zone be able to carry, in per cent?

    Type a number.

    Show the answer to question 1

    Answer: 33.3 % of the peak (anything from 32.8 to 33.8 counts)

    After the loss, 3 zones remain and carry the whole peak, so each needs a third of it, 33.3 %. With all 4 zones up the fleet holds 133 % of the peak, so its servers run at about 75 % of their limit at the peak.

  2. Question 2 of 5 With 3 zones and the same rule, survive the loss of one zone, how much capacity does the whole fleet hold, as a percentage of the peak?

    Type a number.

    Show the answer to question 2

    Answer: 150 % of the peak

    Each zone must carry half of the peak, because 2 zones remain after the loss: 3 × 50 % = 150 %. The extra 50 % is the price of surviving a zone without any change during the failure.

  3. Question 3 of 5 These are steps from a runbook for losing a zone. Which of them depend on a control plane working during the failure, so that the design is not statically stable?

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • Create a new database replica and point the application at it
    • Launch replacement servers in the healthy zones

    Launching servers and creating replicas are changes to the system, and they need the control plane's API at the moment it is most likely to be impaired. Health-check routing, cached configuration and a standby that already exists use only what was in place before the failure.

  4. Question 4 of 5 Why should a load balancer's health check not depend on a shared database?

    Choose one answer.

    Show the answer to question 4

    Answer: When the database has a problem, every server fails the check at once and the whole fleet is taken out of service

    A problem in a shared dependency makes every server's check fail together, and the load balancer removes them all, even servers that could still answer from cache. Keep the load balancer's checks local, leave dependency checks to separate monitoring, and prefer load balancers that send traffic to every server when all appear unhealthy.

  5. Question 5 of 5 In the lesson's simulation, why did the full-table design do the same 20 ms of work in the second a zone failed?

    Choose one answer.

    Show the answer to question 5

    Answer: It sends and applies the whole table every second, so a failure changes what the table says, not how much work it is

    A constant-work design does the same work in good times and bad: the table always has 10,000 entries, whatever changed. The changes-only design had 3,300 updates to apply at once and was busy for the whole second, again and again, while it showed thousands of targets wrongly.

Python Online Compiler Change the zone count, the boot time or the control plane's outage in the simulations above.

Interview questions

Warm-up (fresher to mid level): what does static stability mean? A statically stable system keeps working when a dependency fails, without having to change anything. It may stop seeing new changes, such as new servers or new configuration, but whatever it was doing before keeps working. In practice that means the spare capacity for a zone failure is already running instead of being launched afterwards, servers keep the last configuration and credentials they loaded, and recovery never depends on a control plane, the part that makes changes, which is more complex and more likely to be impaired during the same event. The test is simple: list the steps your recovery needs, and remove every step that asks some system to do something new.

Key takeaways

  • To survive the loss of k of z zones, each zone must carry 1 ÷ (z − k) of the peak: half of it with three zones and one loss, so the fleet holds 150 % of the peak.
  • Add room for planned work too: N + 2, or surviving a zone while a deploy has servers out.
  • Redundancy helps only against failures that do not hit every copy at once: roll changes out zone by zone.
  • Active-active fleets fail over without any action; active-standby stores promote a standby that is already running. Exercise both regularly.
  • Statically stable designs keep working without the control plane: capacity already provisioned, last good configuration cached, credentials pushed ahead of time.
  • Constant-work designs do the same work in all conditions, and repair themselves on the next cycle.
  • Keep load-balancer health checks local, fail open when every server looks unhealthy, and answer health checks before other work.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.