When one Postgres instance is no longer enough
Figma’s database footprint has grown almost 100x since 2020. In 2020, the company ran a single Postgres database on AWS’s largest physical instance. By the end of 2022, that had evolved into a distributed architecture with caching, read replicas, and a dozen vertically partitioned databases. Splitting related tables—such as those for Figma files or organizations—into separate vertical partitions bought meaningful runway, but the team always knew this approach had a ceiling. Vertical partitioning’s smallest unit is a single table, and some tables were growing to several terabytes with billions of rows.
At that scale, reliability issues began to surface. Postgres vacuums—background operations that prevent transaction ID exhaustion—started impacting performance. The highest-write tables were growing fast enough that the team would soon exceed the IOPS limits of Amazon RDS. To keep databases from failing, Figma needed horizontal sharding: splitting a single table or group of tables across multiple physical database instances.
Before committing to a strategy, the databases team set explicit goals. They wanted to minimize developer impact, scale out transparently without future application-layer changes, and skip expensive backfills that could take months on the largest tables. Any solution had to support incremental rollout, allow rollback even after physical sharding operations, and maintain strong data consistency without double-writes. The team also favored approaches that leveraged existing expertise with RDS Postgres, given tight deadline pressure.
Why off-the-shelf sharding didn’t fit
The team evaluated popular horizontally sharded databases compatible with Postgres or MySQL, including CockroachDB, TiDB, Spanner, and Vitess. Each would have required a complex cross-store data migration. Figma had spent years building expertise in running RDS Postgres reliably and performantly; migrating would mean rebuilding that knowledge from scratch. With only months of runway remaining, de-risking an entirely new storage layer was an unacceptable risk.
NoSQL was also ruled out. Figma’s relational data model is complex, and NoSQL APIs lack the versatility needed. Rewriting the backend application would have taken engineers away from feature work without offering a clear path forward.
Instead, the team chose to build a horizontally sharded layer on top of its existing vertically partitioned RDS Postgres infrastructure. Tailoring the solution to Figma’s specific architecture meant a much smaller feature set was acceptable. For example, atomic cross-shard transactions were not supported because the team could work around partial commit failures. The colocation strategy minimized application-layer changes, and backwards compatibility with unsharded Postgres was maintained, making rollbacks straightforward if unknowns surfaced.
The first sharding milestone
Horizontal sharding removes many guarantees that ACID SQL databases provide. Certain queries become inefficient or impossible, application code must supply routing information, schema changes must be coordinated across shards, and foreign keys and global unique indexes can no longer be enforced by Postgres. Transactions spanning multiple shards mean writes can partially fail—moving a team between organizations could theoretically leave half their data behind if care isn’t taken.
The full horizontal sharding effort was expected to take years, so the team needed to prove viability early. The first goal was sharding a relatively simple but high-traffic table in production to extend runway on the most loaded database. From start to finish, it took roughly nine months to shard the first table.
Design choices that made sharding work
Figma’s approach built on common sharding patterns but included several unusual design decisions:
- Colocations (colos): Groups of related tables were sharded together, sharing the same sharding key and physical layout. This gave developers a clean abstraction for working with sharded data.
- Logical vs. physical sharding: The team separated logical sharding at the application layer from physical sharding at the Postgres layer. Views enabled a safer, lower-cost logical rollout before the riskier physical failover.
- DBProxy query engine: A dedicated service intercepts SQL from the application and routes queries to the appropriate databases. DBProxy can parse and execute complex sharded queries and supports dynamic load-shedding and request hedging.
- Shadow application readiness: A framework predicted how live production traffic would behave under different potential sharding keys, giving product teams visibility into what application logic needed refactoring.
- Full logical replication: Instead of filtered logical replication that copies only a subset of data per shard, the entire dataset was copied to each shard. Reads and writes were then limited to the relevant subset.
These choices let the team handle the majority of its relational data model without requiring product developers to refactor large parts of the codebase. Each future scale-out can now happen transparently at the physical layer, with minimal downtime and no application changes—removing one of the last major scaling bottlenecks Figma faced.
Choosing shard keys that fit the data model
Horizontal sharding forces a series of data-model constraints around the shard key: most queries must include the key for routing, foreign keys only work when they match the sharding key, and the key must distribute data evenly across shards to avoid hotspots.
Figma is a browser-based collaboration tool where many users work on the same file simultaneously. That product is powered by a relational data model covering file metadata, organization metadata, comments, and file versions. There was no single candidate sharding key that worked for every table. Adding a unified key would have meant composite keys, schema changes on every table, expensive backfills, and substantial product refactoring. Instead, the team picked a handful of sharding keys — UserID, FileID, and OrgID — that covered nearly every table.
To make that workable for developers, Figma introduced the concept of colos: tables grouped within a colo support cross-table joins and full transactions as long as queries are restricted to a single sharding key. Since most application code already interacted with the database this way, the migration burden for developers was minimal.
The chosen sharding keys relied on auto-incrementing or Snowflake timestamp-prefixed IDs, which would have created hotspots where a single shard held most of the data. Rather than migrate to randomized IDs — an expensive, time-consuming data migration — Figma routes by the hash of the sharding key. A sufficiently random hash function ensures uniform distribution. The trade-off is that range scans on shard keys become inefficient, since sequential keys hash to different shards. That query pattern was rare enough in the codebase to accept.
Logical before physical
To de-risk the rollout, Figma separated logical sharding from physical sharding. Logical sharding makes all reads and writes behave as if the table is horizontally sharded — with the same reliability, latency, and consistency characteristics — while the data remains on a single physical database host. This allowed a low-risk, percentage-based rollout; rolling back was a simple configuration change. Rolling back a physical shard split, by contrast, requires complex coordination to ensure data consistency.
Only after logical sharding was proven did Figma run the physical operation: copying data from a single database, sharding it across multiple backends, and rerouting traffic through the new databases.
Representing logical shards required a physical encapsulation that didn’t itself require data movement. Separate Postgres databases or schemas would have meant physical changes at logical-sharding time, so Figma used Postgres views. Each table gets one view per shard, defined as a filtered subset of the base table, for example:
CREATE VIEW table_shard1 AS SELECT * FROM table WHERE hash(shard_key) >= min_shard_range AND hash(shard_key) < max_shard_range);
All reads and writes go through these views, with each view accessed via its own sharded connection pooler. The poolers still point to the unsharded physical instance, giving the appearance of sharding. Feature flags in the query engine enabled gradual rollout, and traffic could be rerouted back to the main table within seconds if needed. By the time the first reshard ran, the sharded topology was already battle-tested.
Views do add risk: they impose a performance overhead and can change how Postgres’ query planner optimizes. Figma validated the approach with a query corpus of sanitized production queries and load tests, confirming minimal overhead in most cases and less than 10% in the worst. A shadow-reads framework sent live read traffic through views, comparing performance and correctness against non-view queries, before the team committed to the design.
DBProxy: a query engine for sharding
Sharding required a significant re-architecture of the backend stack. Application services had previously talked directly to the connection pooling layer, PGBouncer. Horizontal sharding demanded more sophisticated query parsing, planning, and execution, so Figma built DBProxy, a new Go service sitting between the application and PGBouncer. It handles load-shedding, observability, transaction support, topology management, and a lightweight query engine.
The query engine has three main components:
- A query parser that reads SQL and transforms it into an Abstract Syntax Tree (AST).
- A logical planner that extracts the query type and logical shard IDs from the AST.
- A physical planner that maps logical shard IDs to physical databases and rewrites queries for execution on the correct shard.
Single-shard queries — those filtered to a single shard key — are straightforward: the engine extracts the key and routes to the appropriate physical database, pushing query execution down into Postgres. Queries without a sharding key require scatter-gather: fan out to every shard, then aggregate results. Complex aggregations, joins, and nested SQL make scatter-gather very difficult to implement. And because each scatter-gather touches every database, it adds the same load as a query on an unsharded database, limiting scalability.
Full SQL compatibility would have turned DBProxy into another Postgres query engine. Figma instead built a shadow planning framework where developers could define potential sharding schemes, then run the logical planning phase against live production traffic. Queries and plans were logged to Snowflake for offline analysis. The team picked a query language supporting the most common 90% of queries while avoiding worst-case engine complexity. Range scans and point queries are allowed; joins are only supported when joining two tables in the same colo on the sharding key.
Topology that keeps up
DBProxy relies on a topology service to map tables to shard keys, logical shard IDs to physical databases, and to track changes during shard splits. Vertical partitioning had relied on a simple hard-coded configuration file; horizontal sharding required something dynamic. Topology changes are always backwards-compatible, so they never sit in the critical path. Figma built a database topology system that encapsulates horizontal sharding metadata and delivers real-time updates in under a second.
Keeping logical and physical topologies separate also simplified operations. Non-production environments can mirror production’s logical topology while serving from far fewer physical databases, saving cost without introducing environment drift. The topology library also enforces invariants — for example, every shard ID maps to exactly one physical database — which was critical for correctness as the system scaled.
The physical split
Once a table is logically sharded and validated, the final step is the physical failover from one unsharded database to N sharded ones. Figma reused much of the vertical-partitioning logic, with notable differences: the data movement goes 1-to-N, and the process must handle partial failure where the sharding operation succeeds on only some databases. Because the riskiest components — query routing, logical sharding, view-based encapsulation — were already proven, the first physical sharding operation came much faster than it otherwise would have.
What sharding bought, and what it didn't
The first horizontally sharded table shipped in September 2023. Since then, the team has completed failovers with only ten seconds of partial availability on database primaries and no impact on replicas, with no regressions in latency or availability after the shards went live. The early work focused on the simplest shards drawn from the highest write-rate databases. The next phase targets far more complex databases with dozens of tables and thousands of code call-sites.
Reaching full horizontal sharding across every table removes the last scaling limits, but the payoffs extend beyond raw capacity. The team expects improvements in reliability, cost savings, and developer velocity once the infrastructure is complete. Those benefits do not come free, and the remaining roadmap requires solving a specific set of hard problems:
- Horizontally sharded schema updates
- Globally unique ID generation for sharded primary keys
- Atomic cross-shard transactions for business-critical cases
- Distributed globally unique indexes (unique indexes currently require the sharding key)
- An ORM model that works seamlessly with sharding while keeping developer velocity high
- Fully automated reshard operations, including shard splits run at the click of a button
Revisiting the in-house bet
The team’s original decision to build horizontal sharding on top of in-house RDS was made 18 months ago under tight deadline pressure. With more runway now available, that choice is up for reassessment. NewSQL stores have continued to mature, and the team finally has bandwidth to weigh continuing down the current path against switching to an open source or managed solution.
The sharding work is credited to current and former databases team members Anna Saplitski, David Harju, Dinesh Garg, Dylan Visher, Erica Kong, Gordon Yoon, Gustavo Mezerhane, Isemi Ekundayo, Josh Bancroft, Junhson Jean-Baptiste, Kevin Lin, Langston Dziko, Maciej Szeszko, Mehant Baid, Ping-Min Lin, Rafael Chacon Vivas, Roman Hernandez, Tim Goh, Tim Liang, and Yiming Li. Cross-functional partner teams included Amy Winkler, Braden Walker, Esther Wang, Kat Busch, Leslie Tu, Lin Xu, Michael Andrews, Raghav Anand, and Yichao Zhao. Sammy Steele, tech lead for Figma’s databases team, previously built petabyte-scale metadata storage and search at Dropbox.




