What is cell-based architecture, what does it actually buy, and how do you decide cell size and routing?
Show the full answer Hide the answer
The idea
Instead of one large deployment serving all customers, run many complete, independent instances of the stack — cells — each serving a subset of customers. A cell contains everything needed to serve a request: compute, data, cache, and ideally its own control plane. Cells do not share state and do not call each other.
A thin routing layer maps each customer to a cell.
What it actually buys
- A bounded blast radius for almost every class of failure. A bad deploy, a poison message, a corrupted cache, a hot tenant, a resource exhaustion bug — each is contained to one cell. The outage becomes "2% of customers" rather than "all customers."
- A safe deployment unit. Roll out cell by cell with health gates; a defect that survives testing is caught after affecting one cell.
- Known scaling behaviour. A cell has a tested maximum capacity; growth adds cells rather than pushing an untested system further up a curve nobody has measured. This eliminates the "we have never run at this size" category of failure.
- Testability of the whole system, because a cell is small enough to load-test to destruction.
What it does not buy
It does not protect against anything in the shared layer. The router, the customer-to-cell mapping, the identity system and the deployment pipeline are all still global. The engineering discipline is keeping that shared layer as small and as simple as possible, because it is the residual single point of failure — and a cell-based architecture with a complex shared control plane has moved the problem rather than solved it.
Sizing
The trade-off is direct:
- Smaller cells → smaller blast radius, more cells to operate, more per-cell overhead, more fragmentation of capacity, and a harder time with tenants that exceed one cell.
- Larger cells → less overhead, better utilisation, and a bigger blast radius.
Practical anchors: a cell should be small enough that losing one is tolerable and that it can be load-tested in full, and large enough to hold the biggest single tenant with headroom. The largest tenant is usually the binding constraint, and the awkward case — a tenant bigger than a cell — must be answered explicitly, usually by sharding that tenant across cells with a tenant-specific routing rule.
Routing
The router must be the simplest possible component: a mapping lookup with an aggressively cached, rarely changing dataset. It should fail static — if the mapping store is unavailable, serve from the cached mapping rather than failing.
Shuffle sharding is the refinement worth knowing. Assign each customer to a random subset of cells rather than one. With enough cells, two customers rarely share their full subset, so a customer whose behaviour poisons a cell affects only the small number of others sharing that cell — and the probability that any particular other customer shares all of their cells with the offender becomes negligible. It converts "everyone on cell 7 is affected" into "almost nobody loses all of their capacity."
The migration problem
Moving a tenant between cells is the operationally hardest part and must be designed at the start: dual writes, a verified data copy, a cutover with a brief write pause, and a rollback path. Retrofitting tenant migration into a cell-based system that did not plan for it is a project, not a task.