HomeResourcesCase study
Case study

Notion's Database Had a Doomsday Counter

By The SDL team·4 min read·Updated Sep 16, 2026

It wasn't disk space. A 32-bit counter was ticking toward the day Postgres would refuse every write.

Notion didn't shard Postgres because it ran out of space. It sharded because a counter was quietly ticking toward a number that would have frozen every write in the company.

Five years of growth on a single Postgres monolith. Two problems converged: the cleanup process (VACUUM) couldn't keep up, and a 32-bit transaction-ID counter was creeping toward wraparound — a limit that, if hit, forces Postgres to stop accepting writes to protect your data. Not slow. Stopped.

Plain English

Every change Postgres makes gets a transaction ID — a ticket number that only counts up. The counter is 32 bits wide, so it eventually runs out of numbers and must wrap around to reuse old ones. To do that safely, a background process (VACUUM) has to "freeze" old rows first. If writes outrun VACUUM, the counter approaches the danger zone — and Postgres slams the brakes on all writes to avoid corrupting data.

So Notion wasn't fighting disk space. They were fighting time. The single database had a structural ceiling, and they could see the date approaching.

One database became 480 shards

Notion sharded the monolith by workspace ID — every workspace's data lives entirely on one shard, which keeps the queries that matter (everything inside your workspace) single-shard. The structure has two layers: 32 physical databases, each holding 15 logical shards. That's 480 logical shards in total.

Two layers of splitting: 480 logical shards across 32 physical databases 1 database VACUUM stalling 32 physical databases inside ONE physical database: 15 logical shards 32 × 15 = 480 logical shards, keyed by workspace ID Logical shards can later be moved to new physical DBs — no re-keying. That headroom is the whole point.
Why two layers? Logical shards decoupled from physical machines means future rebalancing is a relocation, not a re-keying. They built headroom into the design on day one.

The two-layer trick — logical shards mapped onto physical machines — is the same instinct Figma reached for independently. The first time you shard, you'll want to do it once. Decoupling logical from physical means the next rebalance is a config change, not another migration.

Now the engineering

Sharding a live database that the entire product depends on, with zero downtime, is the actual engineering. You can't take Notion offline to copy data. So the cutover runs as a sequence of overlapping, reversible phases.

The zero-downtime cutover, in four overlapping phases 1. Double-write writes hit old + new 2. Backfill copy old history over 3. Audit-log catch-up reconcile any drift 4. Dark reads read both, compare, verify Only after dark reads match for real traffic do you flip. The migration is reversible until the very last moment.
Double-write, backfill, audit-log catch-up, dark reads. Each phase is reversible. You only flip once the new system has proven it returns identical results under real traffic.

Why dark reads are the part that matters

Double-writing and backfilling get the data into the new shards. But how do you know the sharded system returns the same answers? You don't trust it — you verify. In the dark reads phase, production reads hit both the old monolith and the new shards, and the results are compared. Mismatches are bugs, surfaced before a single user depends on the new path. Only when they match consistently do you cut over.

480
logical shards
32
physical databases
0
seconds of downtime

Worth knowing

The choice of workspace ID as the shard key is doing quiet, heavy lifting. Notion's access pattern is overwhelmingly "give me everything in this workspace." Co-locating a workspace's data on one shard means the common query never crosses shard boundaries. Pick a key that matches your dominant access pattern, and cross-shard queries stay rare. Pick wrong, and every page load becomes a scatter-gather.

The gap it reveals

"Postgres can't scale, so shard it" is surface-level. The real understanding is knowing why a single Postgres hits a wall — VACUUM throughput and transaction-ID wraparound, not raw size — and that the dangerous, skill-defining part of sharding is the live, zero-downtime cutover, not the data split. The dark-reads phase is the kind of detail you only know if you've actually done a migration that couldn't fail.

In the interview room

"How would you migrate a live database with no downtime?" is a top-tier system-design question precisely because the naive answer ("take a backup and restore it") reveals you've never done it. Walk the interviewer through double-write → backfill → reconcile → dark-read verify → flip, and emphasise that every step is reversible. That sequence is the senior-engineer fingerprint.

The reframe

Notion's hardest problem wasn't designing the sharded layout — it was changing the engine of a moving car without the passengers noticing. The scaling limits forced the migration; the caution of the migration is what made it succeed. Reversibility at every step, and verification before trust, are not slowdowns. They're the entire discipline.

The schema redesign is the easy half. Getting there without breaking is the job.

Primary source →
notion.com — Herding Elephants: Lessons Learned from Sharding Postgres at Notion

Want feedback on your design?

The weekly teardown

One real-world architecture, every week.

How real companies actually built it: the design, the trade-offs, and what to say about it in an interview. Free, and one click to unsubscribe.

Related articles