database-replication

3 posts

datadog

When failover isn’t safe: Building high-availability PostgreSQL on Kubernetes (opens in new tab)

Datadog’s gameday testing exposed a PostgreSQL failure mode in which network latency caused replication lag to grow until no standby could be safely promoted. Although the clusters remained writable, they could not fail over without risking data loss, forcing operators to wait for connectivity and replicas to recover. Datadog’s solution was to redesign failover candidates around synchronous replication coordinated by Patroni, balancing stronger durability with acceptable write latency. ## The Zonal Failure That Exposed the Weakness - A simulated availability-zone failure introduced network latency in a staging environment. - Several Kubernetes-based PostgreSQL clusters had primary nodes in the affected zone. - Communication between primaries and replicas degraded, causing: - Rapidly increasing replication lag - Stalled writes - Applications serving stale data - No replica being current enough for safe promotion - The clusters prioritized continued writes over durability, leaving them writable but unable to fail over safely. ## Baseline PostgreSQL Architecture - Each cluster uses a single-writer design: - One active leader handles writes. - Two standby nodes are reserved for failover and do not serve application traffic. - A separate read-replica pool handles read-only traffic and scales independently. - Read replicas are intentionally excluded from failover candidates. - Patroni manages replication, leader elections, and failover. - ZooKeeper acts as Patroni’s distributed configuration store, tracking: - The current leader lock - Cluster configuration - Member replication state and latest LSN - ZooKeeper’s ephemeral leader key ensures that only one node can become primary. - During partitions, Patroni favors safety by pausing or demoting nodes that cannot verify cluster state. ## Why Failover Was Not Safe - Patroni checks replication lag before promoting a standby using `maximum_lag_on_failover`. - During the gameday, all eligible standbys exceeded that threshold. - Patroni correctly rejected promotion because each candidate could have been missing committed transactions. - The cluster therefore had no safe writable primary, even though the original leader was impaired. - The failure was a consequence of asynchronous replication and network latency, not a failure in Patroni’s safety mechanisms. ## Asynchronous Versus Synchronous Replication - **Asynchronous replication**, used originally: - Lets the leader commit and respond without waiting for replicas. - Provides low write latency and high throughput. - Can lose transactions committed on the leader but not yet copied to a standby. - **Synchronous replication**: - Requires the leader to receive acknowledgment from at least one replica before confirming a transaction. - Reduces the chance that a failover candidate is significantly behind. - Provides stronger durability, but may increase write latency when replicas experience network or availability problems. ## The Redesigned Approach - Datadog reworked its PostgreSQL deployment so failover candidates use synchronous replication. - Patroni coordinates these replicas and continues to enforce safe leader election. - The design aims to make failover both automatic and safe while limiting performance impact. - Benchmarking and failure testing were used to evaluate the trade-off between durability and latency. Datadog’s experience demonstrates that asynchronous replication can leave a system operational but unable to fail over during network disruption. For clusters where data durability and automatic recovery are critical, synchronous replication for designated failover candidates offers a safer architecture, provided its latency and availability costs are measured carefully.

figma

How Figma's Databases Team Lived to Tell the Scale | Figma Blog (opens in new tab)

Figma’s database stack grew nearly 100× from 2020, pushing its single-Postgres architecture beyond the limits of vertical partitioning. After adding caching, read replicas, and vertically partitioned databases, the team found that individual tables were reaching terabyte and billion-row scales, creating vacuum reliability issues and approaching AWS RDS IOPS limits. The solution was to pursue horizontal sharding while preserving Postgres, minimizing application changes, avoiding massive backfills, and maintaining consistency and rollback options. ## Scaling from One Postgres Database - In 2020, Figma ran on one large Postgres instance. - By the end of 2022, it had introduced: - Caching - Read replicas - Around a dozen vertically partitioned databases - Related tables, such as those for Figma files and organizations, were grouped into separate database partitions. - Vertical partitioning reduced pressure on the system and provided valuable short-term runway. ## Why Vertical Partitioning Was No Longer Enough - The team monitored multiple scaling constraints, including: - CPU and I/O utilization - Table size - Rows written - Database IOPS - Some tables grew to several terabytes and billions of rows. - Large tables began affecting reliability during PostgreSQL vacuum operations, which prevent transaction ID exhaustion. - High-write tables were on track to exceed the maximum IOPS supported by Amazon RDS. - Because a table is the smallest unit of vertical partitioning, splitting databases by table group could not solve these limits. ## Requirements for the Next Scaling Strategy Figma established several design goals for horizontal scaling: - Minimize developer changes and preserve the existing relational data model. - Make future scale-outs transparent to application teams after initial compatibility work. - Avoid months-long backfills of large tables. - Roll out changes incrementally to reduce outage risk. - Preserve rollback capability after physical sharding. - Maintain strong consistency without relying on difficult double-write schemes. - Support near-zero-downtime scale-outs. - Favor technologies and techniques the database team already understood, given the limited runway. ## Evaluating Alternatives - The team considered CockroachDB, TiDB, Spanner, and Vitess. - Moving to another database would have required a risky migration between storage systems while preserving consistency and reliability. - Figma already had substantial operational expertise running Postgres on RDS; replacing it would mean rebuilding that expertise under severe time pressure. - NoSQL systems were also unsuitable because Figma’s application depends on a complex relational data model and requires the flexibility of relational queries. - The team therefore favored a lower-risk approach that retained Postgres and offered greater control over the migration. ## Practical Direction Figma’s experience shows that vertical partitioning can be an effective intermediate step, but it cannot solve limits imposed by individual tables. For systems with rapidly growing relational workloads, horizontal sharding within a familiar database ecosystem can provide a safer path to scale when it is introduced incrementally and designed around consistency, rollback, and minimal application disruption.

figma

LiveGraph: real-time data fetching at Figma | Figma Blog (opens in new tab)

LiveGraph is Figma’s in-house real-time data-fetching layer built on PostgreSQL. It lets frontend developers declare live data views with GraphQL-like queries, while LiveGraph reads PostgreSQL’s replication stream to deliver updates within milliseconds. Figma built it to replace fragile, manually maintained client events and to support real-time subscriptions at large scale without relying on polling or a new database technology. ## Problems with Figma’s Earlier Real-Time Architecture - React clients initially loaded large data sets through Ruby HTTP endpoints and stored them in Redux. - Backend code manually emitted events whenever database records changed. - Frontends subscribed over WebSockets and applied those events to client state. - As data volumes grew, Figma split requests into incremental loads, making data ownership and availability harder to reason about. - Complex changes—such as permission updates affecting many resources—were difficult to represent with individual events. - Events could arrive out of order or fail to correspond reliably with database writes, causing client state to diverge from server state. ## Why Figma Chose Live Queries - Figma wanted developers to define data subscriptions declaratively rather than manually coordinate fetches and update events. - GraphQL provided a natural interface for describing the relevant portion of the object graph. - LiveGraph uses “live queries,” which keep query results synchronized, rather than GraphQL subscriptions in the narrower sense of consuming event streams. - The system is a query and data-fetching layer over existing PostgreSQL infrastructure, not a replacement persistence layer. ## In-House System Versus Existing Tools - Figma’s multiplayer service handles collaborative writes and conflict resolution within individual files, whereas LiveGraph focuses on reading application data. - Systems such as Hasura, Prisma, and PostGraphile offered GraphQL subscription features but were not designed primarily for Figma’s scale of concurrent live subscriptions. - Polling was rejected because it increases database load and requires developers to choose polling intervals for each query. - Figma’s collaborative product made real-time data central enough to justify building and operating a specialized internal system. - The company did not claim LiveGraph was universally superior; its value came from matching Figma’s specific scale and requirements. ## Replication-Stream-Based Updates - LiveGraph executes queries directly against PostgreSQL. - It tails the database replication log to detect changes instead of repeatedly polling tables. - Reading the replication stream enables update latency measured in milliseconds. - Because the system must process the complete volume of database changes, its architecture needs to distribute updates across machines and database shards. - This approach separates the complexity of detecting database changes from product code, allowing frontend engineers to work with declarative JSON data views. ## Frontend API - Product developers send GraphQL-like queries and receive results as JSON trees. - A schema defines server-side entities and relationships, while views expose queryable subsets of that graph. - The frontend can therefore request the data it needs and rely on LiveGraph to keep the result synchronized as the underlying PostgreSQL data changes. LiveGraph’s central recommendation is architectural: derive live client views from the database’s authoritative change stream rather than maintaining a parallel network of hand-written events. For organizations with similar scale and real-time requirements, this can improve consistency and simplify product development, though Figma’s in-house approach was justified by its unusually collaborative workload.