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.
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 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.
| Criterion | One shared deployment | Cells | Shuffle shards |
|---|---|---|---|
| One failure reaches | Everyone | One cell's customers | Few customers fully, more of them partly |
| Copies to run | One | One per cell | One fleet, many overlapping groups |
| Queries across customers | Easy | Fan out to every cell | Easy |
| Moving a customer | Never needed | A planned migration | Change its worker set |
| When to choose | Small systems where one failure is acceptable | Multi-tenant services where blast radius is the priority | Shared 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, 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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Step through the 16-cell row with the fast bug, wave by wave:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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).
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
- Reducing the Scope of Impact with Cell-Based Architecture (AWS Well-Architected) (Amazon Web Services)
- What is a cell-based architecture? (AWS Well-Architected) (Amazon Web Services)
- Cell routing (AWS Well-Architected) (Amazon Web Services)
- Cell sizing (AWS Well-Architected) (Amazon Web Services)
- Cell migration (AWS Well-Architected) (Amazon Web Services)
- Cell deployment (AWS Well-Architected) (Amazon Web Services)
- Deployment Stamps pattern (Azure Architecture Center) (Microsoft)
- Geode pattern (Azure Architecture Center) (Microsoft)
- Workload isolation using shuffle-sharding (Amazon Builders' Library) (Amazon Web Services)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress