← Back to Real-World Case Studies

How Figma Scaled Postgres 100x

How Figma scaled Postgres with replication, partitioning, and workload isolation.

Figma's Postgres story matters because it challenges a common instinct in system design: the idea that serious scale always requires abandoning a relational core early. The more practical lesson is that a well-understood database can stretch a long way if the team is disciplined about load shape, operational safety, and where complexity is introduced.

Postgres was a strong fit early because it gave Figma transactional correctness, indexes, constraints, and mature recovery tools while the product was still evolving quickly. That combination matters more than people sometimes admit. A database that is easy to reason about lets a product team move faster because schema changes, query behaviour, and consistency rules are familiar.

The first scaling step in systems like this is usually not sharding. It is removing accidental load. Teams often discover that expensive queries, oversized transactions, missing indexes, and chatty application code are doing more damage than the raw amount of data. A single endpoint that fires several avoidable reads can matter more than a table with millions of rows.

Read replicas help, but only for the right workload. They are useful when heavy read traffic can tolerate some lag and when the application is clear about which queries require primary freshness. Replicas do not solve everything. They can even hide design problems if the primary still carries oversized writes, locking pressure, or maintenance work that blocks normal traffic.

At higher volume, write amplification becomes a central concern. An apparently simple update may touch multiple indexes, generate WAL, create vacuum work, and increase replication lag. That is why serious Postgres scaling is not only about query latency. Teams need to watch lock duration, bloat, autovacuum behaviour, WAL growth, and transaction size, because those signals expose costs that the application layer does not show directly.

Workload separation is another recurring theme. Product traffic, analytics, backfills, migrations, and internal tools should not all compete on the same critical path. When heavy jobs run on the primary at the wrong time, users feel it as slower saves, delayed page loads, or timeouts. Isolating those workloads, batching them carefully, or moving them elsewhere protects the interactive path that actually matters.

Eventually some datasets outgrow a single contention domain, and that is where logical partitioning or sharding enters the discussion. The hard part is not splitting data. It is choosing a shard key that matches stable access patterns. If teams shard by the wrong dimension, they preserve cross-shard coordination while adding operational complexity. Good sharding reduces hotspots and keeps most work local.

There is also a human side to scaling. Safe migrations, feature flags around storage changes, rehearsed failover, and clear runbooks are part of database architecture, not admin overhead. The database is a shared dependency, so mistakes there have unusually large blast radius.

The practical takeaway is not "Postgres scales forever". It is narrower and more useful: do not leave a relational database because growth looks scary in abstract. Measure the real bottlenecks, trim waste, isolate competing workloads, and add harder architecture only when the simpler fixes are truly exhausted. That is how teams get surprising distance from familiar tools without turning the system into guesswork.