惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
博客园 - 三生石上(FineUI控件)
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
Engineering at Meta
Engineering at Meta
I
InfoQ
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
S
SegmentFault 最新的问题
H
Help Net Security
博客园 - 【当耐特】
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
腾讯CDC
雷峰网
雷峰网
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
D
Docker

Blog — PlanetScale

Keeping a Postgres queue healthy — PlanetScale Patterns for Postgres Traffic Control — PlanetScale Graceful degradation in Postgres — PlanetScale High memory usage in Postgres is good, actually — PlanetScale Stripe Projects partnership: Provision PlanetScale Postgres and MySQL databases from the Stripe CLI — PlanetScale Enhanced tagging in Postgres Query Insights — PlanetScale Behind the scenes: How Database Traffic Control works — PlanetScale Introducing Database Traffic Control — PlanetScale Scaling Postgres connections with PgBouncer — PlanetScale Drizzle joins PlanetScale — PlanetScale Video Conferencing with Postgres — PlanetScale Faster PlanetScale Postgres connections with Cloudflare Hyperdrive — PlanetScale Introducing the PlanetScale MCP server — PlanetScale Database Transactions — PlanetScale Automating our changelog with Cursor commands — PlanetScale Postgres 18 is now available — PlanetScale Using MotherDuck with PlanetScale — PlanetScale $50 PlanetScale Metal is GA for Postgres — PlanetScale AI-Powered Postgres index suggestions — PlanetScale $5 PlanetScale is live — PlanetScale Announcing Vitess 23 — PlanetScale $50 PlanetScale Metal — PlanetScale Report on our investigation of the 2025-10-20 incident in AWS us-east-1 — PlanetScale $5 PlanetScale — PlanetScale Benchmarking Postgres 17 vs 18 — PlanetScale Larger than RAM Vector Indexes for Relational Databases — PlanetScale Partnering with Cloudflare to bring you the fastest globally distributed applications — PlanetScale Processes and Threads — PlanetScale PlanetScale for Postgres is now GA — PlanetScale Announcing Neki — PlanetScale
Postgres High Availability with CDC — PlanetScale
Sam Lambert · 2025-09-12 · via Blog — PlanetScale

Sam Lambert [@samlambert] |

Change Data Capture (CDC) from your database is a common practice for most businesses. Postgres’ replication design adds high-availability (HA) constraints and operational coupling in ways that are impractical.

The Postgres approach

Start with a standard HA Postgres cluster topology. One primary. Two standbys configured for semi-synchronous replication. A CDC client reading a logical replication slot via pgoutput. WAL level is logical on the primary, and the standbys are configured in synchronous_standby_names = 'ANY 1 (r1, r2)' so commits on the primary wait for at least one standby to flush. The CDC client doesn’t stream continuously; it polls every few hours.

How Postgres moves data across this cluster:

  • The primary emits WAL
  • Physical standbys stream and apply WAL
  • The CDC client reads a logical slot that decodes WAL into row changes

The critical detail is that the logical replication slot is a durable, primary-local object that carries two pieces of state: the oldest WAL the slot requires (restart_lsn) and the most recent position the subscriber has confirmed (confirmed_flush_lsn). The presence of that slot pins WAL on the primary until the CDC client advances. If the client lags, WAL accumulates. That’s expected. The brittle part shows up when you try and achieve HA.

Postgres 17 introduced logical replication failover, so slot state can be synchronized to promotion candidates, but slot eligibility on the replica has caveats; A standby only becomes eligible to carry the slot after the subscriber has actually advanced the slot at least once while that standby is receiving the slot metadata. This guard exists to prevent promoting a node that has never observed real slot progress and would present an inconsistent stream to the subscriber. In practice, if the CDC client hasn’t connected in hours, any freshly added or recently restarted standby won’t be eligible. Attempting a controlled primary promotion becomes impossible without breaking the CDC stream because no replica candidates have an eligible slot.

Failover readiness for a logical slot is determined by three conditions on the standby:

  • The slot is synchronized on the standby, synced = true.
  • The slot's position in the WAL is consistent with the position of the standby, not too far behind or too far ahead.
  • The slot is persistent and not invalidated, temporary = false AND invalidation_reason IS NULL

Explicit failure scenarios:

  1. During a CDC quiet period, logical slots on standbys may remain in temporary status due to position inconsistencies. If forced failover occurs, the temporary slots are not failover-ready. The CDC stream breaks, requiring connector reinitialization and snapshot reload.
  2. Replacing replicas: You add new replicas (fresh pg_basebackup) and plan to retire the old ones. Each new standby begins synchronizing slot metadata from the primary but, by design, starts at a conservative point (older XID/LSN) and won’t consider the slot synchronized until it has seen the subscriber advance. If the CDC client polls every 6 hours, all new replicas remain ineligible for promotion until that polling event occurs. Any switchover in the interim either stalls or breaks CDC exactly like case 1.
  3. Not just CDC. Any replication client backed by a slot can create a similar problem. A physical standby connected through a physical slot that stops pulling WAL will pin restart_lsn indefinitely. That doesn’t directly affect slot eligibility the way logical failover slots do, but it can fill the primary’s WAL volume and trip the cluster into write unavailability, emergency failover, or drop the slot entirely if the maximum WAL size has been reached. The core fragility is the same: progress of the slowest slot determines how far the system can move without manual intervention.

This happens due to the way Postgres records replication progress. The WAL is a physical redo log for crash recovery and physical standby replication. The fact that a downstream consumer needs certain WAL retained is tracked in a primary-local catalog state inside pg_replication_slots. That state advancement only occurs when the consumer connects and acknowledges data. Historically, this state never rode along in WAL, so standbys had no authoritative copy. Postgres 17’s failover slots serialize slot metadata into WAL so candidates can mirror it, but they still refuse to declare a node eligible until a real subscriber has advanced the slot at least once while that node is following along. This preserves exactly-once CDC semantics at the expense of HA flexibility.

The MySQL approach

MySQL’s approach doesn’t create this coupling. MySQL’s binary log is an action log. Every transaction carries a GTID. Replicas with log_replica_updates=ON re-emit transactions they apply into their own binlogs, preserving GTID continuity. A CDC connector records the last committed GTID set. On reconnect it tells any suitable server, “resume from this GTID.” If the binlog containing that GTID still exists, streaming continues with no slot object and no eligibility gate.

Failover looks like:

  • Promote a replica
  • Point the connector at any replica and it resumes from it's GTID position

The success of this operation is only determined by whether binlog retention covers the downtime, not by whether the connector recently polled. A lagging consumer can’t stall switchover; at worst, if binlogs are purged past the last GTID the connector processed, the connector must resnapshot but HA completes immediately. You can even recover binlogs from other sources and apply those.

Which is better for HA?

Put the two designs side by side in the same topology:

Postgres: primary P, synchronous standbys R1 and R2, CDC slot S on P. Commits require ANY 1 flush by R1 or R2. CDC polls every 6 hours. New R3 is added during maintenance. Until the CDC client advances S, R3 is not eligible to carry S after promotion; neither is R2 if it joined recently or restarted without seeing slot progress. Switchover options are to wait for CDC to advance or promote anyway and accept slot drop. This ties a write-availability action to the behavior of an external downstream system. Tight coupling.

MySQL: primary M, two replicas MR1 and MR2 with GTID and row-based binlog; log_replica_updates=ON on replicas so they have full binlog history. CDC connector persists a GTID position. Maintenance adds MR3; it catches up and emits the same GTIDs. Switchover can proceed immediately. Direct the CDC connector to any replica and it resumes from its GTID position. There is no eligibility concept because replication progress is embedded in the binlog; nothing special needs to be mirrored across nodes. A much more flexible design.

This is the brittle edge in Postgres high availability with logical consumers: slot progress is a single-node concern that must be coordinated across the cluster at failover time, and eligibility depends on subscriber behavior outside your control. Even with failover slots, eligibility deliberately waits for the subscriber to move the slot to prevent broken streams. If the subscriber is slow by design (batch CDC) or temporarily offline, you inherit either long switchover delays or intentional CDC breakage. If a physical slot backs a dormant standby, you inherit WAL growth risk on the primary and potential write outages.