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 13 – Architecture styles at scale

Cells, deployment stamps and blast radius

How cell-based architectures and deployment stamps limit the blast radius of failures and bad deploys, with routing, sizing and tenant moves.

  • Advanced
  • 20 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 intro, Pro: 5 of 20 minutes free
Free: One service cut into cells, each a full copy. With a pass: the other 8 sections and 3 interview answers, about 15 minutes.

What you will learn

  • Explain how cells and deployment stamps limit the blast radius of a failure or a bad deploy
  • Design a cell router that pins large tenants, never falls back and versions its map
  • Size cells from a load-tested limit, headroom and the largest tenant
  • Plan a tenant's move between cells, and name the step that keeps a way back
  • Compare cells with shuffle sharding for the damage one customer's bad requests can do

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain how cells and deployment stamps limit the blast radius of a failure or a bad deploy
  • Design a cell router that pins large tenants, never falls back and versions its map
  • Size cells from a load-tested limit, headroom and the largest tenant
  • Plan a tenant's move between cells, and name the step that keeps a way back
  • Compare cells with shuffle sharding for the damage one customer's bad requests can do

Before you start

On this page

A service for a million customers that runs as one deployment shares every failure with all of them: one bad deploy stops everyone at once. A cell is a complete, independent copy of the service, with its own compute and data, serving only a subset of the customers; a thin cell router in front sends each request to its customer’s cell, chosen by a partition key: The field of a request or a record, such as a tenant id or a user id, that decides which partition, shard or cell owns it. such as the tenant id.

Cells share nothing, so a failure or a bad deploy stays inside one of them: AWS’s guidance gives the example of 10 cells serving 100 requests, where one failed cell leaves 90 % of them unaffected. Azure calls the same idea deployment stamps, also scale units, and the share of customers one failure can reach is the blast radius. Use cells when a single shared failure is unacceptable and the workload divides cleanly by customer, and expect to pay for it: more copies to run, a router simpler than anything behind it, and real work whenever a customer moves.

One service cut into cells, each a full copy

The AWS Well-Architected guidance names three parts: cells, each a complete workload; a cell router, which it calls the thinnest possible layer, whose only job is to send requests to the right cell; and a control plane that creates cells, removes them and migrates customers. Cells need not mean more machines: thirty hosts can stay thirty hosts, grouped into cells. Azure’s deployment stamps give each stamp a predefined number of tenants and add stamps to grow almost linearly, and they also use stamps for staged updates and for keeping a tenant’s data in one region.

A thin router sends tenants to one of three cells, each a full copy of the service; a failed cell affects only its tenants.Tenants' requestsControl plane: creates cells,moves tenants, owns the mapCell router: tenant idto cell, nothing elseCells: full copies of the serviceCell 1: app and database,tenants A to FCell 2, failed: only itsown tenants are affectedCell 3: app and database,tenants G to Mmap updates

A service cut into cells behind a thin router

Text description of the diagram

Tenants' requests reach a cell router, whose only job is to map a tenant id to a cell. It forwards each request to one of three cells. Each cell is a complete copy of the service with its own application and its own database, and serves its own set of tenants: cell 1 holds tenants A to F and cell 3 tenants G to M. Cell 2 has failed, and only its own tenants are affected. A control plane, shown apart from the request path, creates cells, moves tenants between them and sends updates of the tenant-to-cell map to the router.

One shared deployment, cells or shuffle shards
Criterion One shared deploymentCellsShuffle shards
One failure reaches EveryoneOne cell's customersFew customers fully, more of them partly
Copies to run OneOne per cellOne fleet, many overlapping groups
Queries across customers EasyFan out to every cellEasy
Moving a customer Never neededA planned migrationChange its worker set
When to choose Small systems where one failure is acceptableMulti-tenant services where blast radius is the priorityShared fleets that must survive one customer's bad requests

The rest of this lesson takes each design decision in turn:

  • Partition key: the field, usually a customer or tenant id, that decides which cell owns a request.
  • Cell router: a mapping from that key to a cell, kept so simple that it rarely changes.
  • Cell size: the most load a cell may carry, set from a load test rather than hope.
  • Tenant migration: moving one customer’s data and traffic to another cell while it keeps working.
  • Staged rollout: deploying to one cell first and growing the waves only after each has baked.
  • Shuffle sharding: giving every customer its own small random set of workers instead of a fixed cell.

The model below puts numbers on the blast radius of a failed cell and of a bad build, for one, four and sixteen cells, when every rollout goes one cell first and then in growing waves:

Customers hit by one failure in 1, 4 and 16 cells Python · blast/blast_radius.py
"""Customers hit by one failure, for a service in 1, 4 or 16 cells of equal size.
A failed cell takes down only its own customers. A bad build is rolled out cell by cell in waves of 1, 1, 2, 4, 8
cells, baking 30 minutes after each wave. A fast bug is noticed 10 minutes after the first cell gets it, a slow bug
50 minutes after; rolling back every cell that has it then takes 5 minutes. Deploying to everything at once is the
1-cell row. Every number is an assumption of the model."""

CUSTOMERS = 1_000_000
BAKE_MIN = 30
ROLLBACK_MIN = 5


def waves(cells):
    """Cells per wave: one, one, then doubling, until every cell has the build."""
    plan, done = [], 0
    for size in (1, 1, 2, 4, 8, 16, 32):
        if done >= cells:
            break
        size = min(size, cells - done)
        plan.append(size)
        done += size
    return plan


def bad_build(cells, noticed_after_min):
    """Customers who got the bad build, and customer-minutes of impact until it is rolled back everywhere."""
    per_cell = CUSTOMERS / cells
    rolled_back_at = noticed_after_min + ROLLBACK_MIN
    hit = minutes = 0.0
    for i, size in enumerate(waves(cells)):
        starts = i * BAKE_MIN
        if starts >= noticed_after_min:  # the rollout stops once the bug is noticed
            break
        hit += size * per_cell
        minutes += size * per_cell * (rolled_back_at - starts)
    return hit, minutes


print(f"{'cells':>5}{'one cell fails':>16}{'fast bug: hit':>15}{'customer-minutes':>18}{'slow bug: hit':>15}{'customer-minutes':>18}{'rollout':>9}")
for cells in (1, 4, 16):
    fast_hit, fast_min = bad_build(cells, 10)
    slow_hit, slow_min = bad_build(cells, 50)
    rollout = (len(waves(cells)) - 1) * BAKE_MIN
    print(f"{cells:>5}{1 / cells:>16.2%}{fast_hit:>15,.0f}{fast_min:>18,.0f}{slow_hit:>15,.0f}{slow_min:>18,.0f}{rollout:>6} min")

Output

cells  one cell fails  fast bug: hit  customer-minutes  slow bug: hit  customer-minutes  rollout
    1         100.00%      1,000,000        15,000,000      1,000,000        55,000,000     0 min
    4          25.00%        250,000         3,750,000        500,000        20,000,000    60 min
   16           6.25%         62,500           937,500        125,000         5,000,000   120 min

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

Step through the 16-cell row with the fast bug, wave by wave:

Interactive · stage by stage A bad build in 16 cells, wave by wave Step through the rollout of the model below with its fast bug, which shows itself 10 minutes after the first cell gets the build.
A bad build in 16 cells, wave by wave, step 5 of 5. Showing Wave 1: 1 cell (62,500 customers for 15 min), Wave 2: 1 cell, Wave 3: 2 cells, Wave 4: 4 cells, Wave 5: 8 cells. In focus: Wave 1: 1 cell, Wave 2: 1 cell, Wave 3: 2 cells, Wave 4: 4 cells, Wave 5: 8 cells.Wave 1: 1 cell62,500 customers for 15 minWave 2: 1 celldue at 30 minWave 3: 2 cellsdue at 60 minWave 4: 4 cellsdue at 90 minWave 5: 8 cellsdue at 120 min

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The first cell. The new build reaches one cell of 16, so 62,500 of the million customers. The other 15 cells keep the old build while the first one bakes for 30 minutes.
  2. The bug shows at 10 min. Ten minutes later that cell's alarms fire. The next wave was due only after 30 minutes of baking, so it never starts.
  3. Rolled back at 15 min. Rolling the one cell back takes 5 minutes. 62,500 customers met the bug for 15 minutes each, 937,500 customer-minutes, the 16-cell row of the program below.
  4. The waves it never reached. Waves 2 to 5 would have started at 30, 60, 90 and 120 minutes with 1, 2, 4 and 8 cells. A good build needs all 120 minutes to reach everyone, which is the price of the small blast radius.
  5. One deployment instead. With one deployment the same bug reaches all 1,000,000 customers at once, and the same 15 minutes cost 15,000,000 customer-minutes, 16 times as many.

With 16 cells, a bug noticed ten minutes after the first deploy reaches 62,500 customers instead of a million. The price is time: rolling a good build out to all 16 cells in waves takes two hours instead of one deploy.

Checkpoint

Check: Cells and blast radius

  1. Question 1 of 3 What makes a cell different from a replica of the same service?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: A cell holds its own customers' data and serves only them; a replica holds the same data as the others

    A cell is a complete, independent copy of the service for a subset of the customers, chosen by a partition key. Replicas share one dataset, so a bad deploy or a corrupting bug reaches every one of them.

  2. Question 2 of 3 A payments API runs in 20 equal cells. What percentage of its customers does the failure of one cell affect?

    Type a number.

    Show the answer: Question 2 of 3

    Answer: 5 %

    With equal cells each holds one twentieth of the customers, so one failed cell affects 1 ÷ 20 = 5 % of them, provided that nothing else is shared between the cells.

  3. Question 3 of 3 In the model's 16 cells, a slow bug is noticed 50 minutes after the first cell gets the build. Waves of 1, 1, 2, 4 and 8 cells start every 30 minutes. How many customers got the bad build?

    Type a number.

    A hint

    Count the waves that started before minute 50.

    Show the answer: Question 3 of 3

    Answer: 125000 customers

    Waves start at 0, 30, 60, 90 and 120 minutes. By minute 50 the first two have started, one cell each, so 2 of the 16 cells, 2 × 62,500 = 125,000 customers, have the bug, as the model's slow-bug column shows.

The router never sends a tenant to another cell Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Sizing cells from load tests and the largest tenant
  • Moving a tenant between cells without downtime
  • Shuffle sharding: when cells are too coarse
  • Deploying cell by cell, and what cells cost
  • Practise: a cell router that pins tenants and never spills
    • A cell router that pins tenants and never spills
  • Interview questions
    • Design the cells of a B2B SaaS with 3,000 tenants and a peak of 50,000 requests a second, where one tenant brings 30 % of the traffic. How many cells, how does a request find its cell, and what happens to the big tenant?
    • One customer starts sending a malformed request that crashes any server that handles it, and its client retries forever. Compare the damage with one shared fleet of 100 servers, with 10 cells, and with shuffle sharding of 5 servers per customer, with numbers.
    • A tenant must move from cell 3 to a new cell 9 with no downtime, while it keeps writing 2,000 times a second. Plan the move and its rollback.
  • Key takeaways

2 diagrams · 3 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 40 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design a unique ID generator

Terms in this lesson

blast radius
The share of customers or requests that one failure or one bad deploy can reach. Cells, zones and rollouts in waves exist to keep it small.
cell (cell-based architecture)
A complete, independent copy of a service, with its own compute and its own data, that serves only a subset of the customers, chosen by a partition key such as the tenant id.
partition key
The field of a request or a record, such as a tenant id or a user id, that decides which partition, shard or cell owns it.
shuffle sharding
Giving each customer its own small, random set of workers from a shared fleet, so that two customers rarely share all of their workers and one customer's bad requests reach few others.

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.