Shard Postgres behind a router: Figma
Figma kept Postgres and built a query router in front of horizontal shards, after vertical partitioning ran out.
Figma ran one Postgres database on AWS's largest physical instance in 2020. Two years later it had caching, read replicas, and a dozen vertically partitioned databases, and the biggest tables still held several terabytes and billions of rows. VACUUM on those tables risked the transaction ID limit, and the smallest unit of vertical partitioning is one table.
The system#
The team sharded horizontally at the application layer, then moved the shards to physical databases. A service called DBProxy parses each SQL query and sends it to the shard that holds the key. A query without a sharding key becomes a scatter-gather across every shard, so the application has to supply the key. The first table took about nine months, and Figma kept unsharded Postgres as a rollback path.
The board#
Drag to pan. Hold Ctrl or Cmd and scroll to zoom.
| Where | Setting | Value | Why |
|---|---|---|---|
| App Server | Instance size | large | Large, because the app builds the queries the router sends on. |
| App Server | Copies | 3 | Three copies stand for the app tier over the sharded database. |
| Cache | Engine | redis | Redis, the cache in front of the shards. |
| Cache | Cache strategy | aside | The app fills the cache on a miss, so repeated file reads do not cross every shard. |
| Cache | TTL (seconds) | 60 | A minute keeps a file’s hot rows current. |
| Cache | Instance size | medium | Medium, sized for the hot files. |
| Connection Pooler | Pooler mode | transaction | A connection is lent for one transaction. The game’s pooler stands for DBProxy, which routes each query to a shard. |
| Database | Shards | 4 | Figma shards its Postgres tables horizontally. Four is the board’s widest, and it stands for the shards it grew to. |
| Database | Shard key | hash | A hash spreads files and teams, so no shard owns one big organization. |
| Database | Instance size | medium | Medium for each shard, so a hot shard shows. |
| Link App Server → Cache | Kinds carried | page,read | Reads try the cache first. |
| Link App Server → Connection Pooler | Kinds carried | write | Writes go through the router. |
Research this Design needs: Caching, Connection pooling, Sharding, Connection pooling, Caching, Read replicas.
The Connection Pooler stands for DBProxy: every write and transaction goes through it to the Database. The Database has four shards on a hash key. The Cache answers repeated reads before they reach the router. The board keeps the router on the write path, because a read without a key is the expensive case.
Why it works#
Postgres stays, so the product code and the relational model stay. The router hides the shard map from the application, and the cache keeps the hottest rows out of the scatter-gather path. Shard the database and connection pooling are the two pieces.
Where the game stops#
The game's Connection Pooler is PgBouncer, not a query engine. It cannot parse SQL, route by key, or scatter-gather. The board shows the shape: one router, many shards, a cache in front. The caption is Quorum's result, not Figma's query latency.
Sources#
How Figma's databases team lived to tell the scale, Figma: the vertical partitions, DBProxy, the shard rollout, and the nine-month first table.
PostgreSQL documentation: VACUUM: why the transaction ID limit forces the split.
PostgreSQL documentation: Routine Vacuuming: the transaction ID wraparound and the cost of a growing table.