Split the database, and six SQL promises break
Figma called it an order of magnitude harder than its earlier scaling. Its first sharded table took about nine months.
Put workspace_id first in every key
Pick the entity most requests are scoped to. A workspace’s joins, foreign keys and transactions then never leave its shard.
Store workspace_id in comment, though page has it?
Bad: No: join through page
The router can’t place the row, nor check its foreign key locally.
Good: Yes, denormalise on purpose
Key it (workspace_id, comment_id); reference (workspace_id, page_id).
Who knows the shard map?
Your own code (Instagram, Notion), a MySQL proxy (Vitess’s VTGate), or Citus’s coordinator inside PostgreSQL. The shards look the same.
Every query path changes. How do you test that before moving data?
The trick
Shard logically first: Figma routed traffic through a view per hash range on one host, by percentage. Rollback was a config change.
5 min
of downtime, hiding in the catch-up
Once writes stopped, Notion’s script still had to drain its backlog. A week more tuning, to under 30 seconds, could have made it invisible.
Cutover day is the start
- Expand-and-contract schema changes, tracked per shard
- Feed a warehouse by CDC for reporting
- Backups, restores and alerts are per shard
- Every pooler connects to every shard: budget it
Remember
Root every table in one entity’s ID, then pick who routes: your code, Vitess or Citus. Migrate by sync, backfill, dark reads and per-shard cutover, with a reverse stream.