Figma: sharding Postgres without leaving it
At a glance
Section titled “At a glance”| Organization | Figma |
| Period | 2020 to 2023 |
| Problem | Database load grew almost 100x; single tables reached terabytes and billions of rows |
| Architecture | Vertical partitioning, then horizontal sharding of Postgres (opens in a new tab) through a custom proxy |
| Patterns | Sharding, incremental and reversible migration |
The problem
Section titled “The problem”In 2020 Figma ran a single Postgres database on the largest instance AWS (opens in a new tab) offered. Its database load then grew almost 100x. The team first added caching and read replicas and split groups of related tables into their own databases (vertical partitioning); by the end of 2022 there were about a dozen.
That wasn’t enough. Some single tables had grown to several terabytes and billions of rows, causing problems during Postgres maintenance (vacuums), and the busiest tables were approaching the I/O limits of Amazon RDS (opens in a new tab). Vertical partitioning can’t split one table, so they needed horizontal sharding.
Options they rejected
Section titled “Options they rejected”- Distributed SQL databases such as CockroachDB (opens in a new tab), TiDB (opens in a new tab), Spanner (opens in a new tab) and Vitess (opens in a new tab): moving would mean a complex migration between two data stores, losing the team’s deep Postgres experience, all with only months of runway.
- NoSQL: it couldn’t support Figma’s relational data model without rewriting most of the backend.
What they did
Section titled “What they did”- Colos. Groups of related tables share a sharding key and the same physical layout, so joins and transactions still work within one key.
- Logical sharding first. Queries are routed as if the data were already sharded while it still lives in one database, using Postgres views. A feature flag controls the rollout and a configuration change rolls it back.
- Physical sharding second. Once logical sharding is proven, the data is copied onto separate databases and traffic is switched.
- DBProxy. A new service written in Go sits between the application and the connection pooler. It parses each query, works out which shard it needs, and can send a query to all shards and combine the results.
flowchart LR
app[Application] --> proxy[DBProxy: parse, plan, route]
proxy --> pool[Connection pooler]
pool --> s1[(Shard 1)]
pool --> s2[(Shard 2)]
pool --> s3[(Shard ...)]
Results
Section titled “Results”Figma shipped its first horizontally sharded table in September 2023, about nine months into the project. The switch caused about ten seconds of partial availability on the primary databases and none on replicas, and the team saw no regressions in latency or availability afterwards.
Trade-offs
Section titled “Trade-offs”- They own a query proxy, which must understand enough SQL to route correctly and is now critical infrastructure.
- Queries must include the sharding key to stay efficient; queries across all shards are possible but costly.
- Joins and transactions are limited to tables in the same colo and the same sharding key.
Lessons you can reuse
Section titled “Lessons you can reuse”- Exhaust cheaper steps first: caching, read replicas, then splitting tables into separate databases.
- Prefer migrations you can roll back with a configuration change. Logical sharding before physical sharding gives exactly that.
- Weigh team expertise and runway, not just features, when choosing between a new database and extending the one you know.
Related on ArchitectureCompass
Section titled “Related on ArchitectureCompass”- Slack moves to Vitess, which made the opposite choice
- Discord’s message storage
Sources
Section titled “Sources”- Sammy Steele, “How Figma’s databases team lived to tell the scale” (opens in a new tab), Figma, March 2024
Written and edited by Abhinav Taduka, enterprise software architect.