代表性面试主题

后端面试:如何设计 PostgreSQL 逻辑复制故障切换?

后端困难
Offer.cc 编辑团队发布 更新

题干

你维护一个从 PostgreSQL 生产库读取变更的 CDC 消费者。主库计划切换或意外故障时,如何让新主库继续提供同一逻辑复制槽,同时避免重复、丢失、WAL 堆积和错误宣称零数据丢失?

题干与适用场景

你维护一个从 PostgreSQL 生产库读取变更的 CDC 消费者。主库计划切换或意外故障时,如何让新主库继续提供同一逻辑复制槽,同时避免重复、丢失、WAL 堆积和错误宣称零数据丢失?请说明 PostgreSQL 18 的 failover slot、同步确认、消费者重连、监控和回退方案。

PostgreSQL 文档说明,逻辑复制槽可以同步到物理 standby,但槽同步是异步的;提升 standby 前必须确认所需槽已同步并处于 failover_ready。这是一道后端可靠性题,重点是把数据库能力、消费者语义和故障切换编排连成可验证的流程。

面试官考察点

  • 能否区分物理 WAL 复制、逻辑复制槽和 CDC 消费者确认位点。
  • 能否解释 failover 槽、槽同步和 synchronized_standby_slots 的作用边界。
  • 能否在 planned switchover 与突发故障下分别处理丢失窗口和重复事件。
  • 是否知道槽同步异步,不能仅凭 standby 在线就宣称可切换。
  • 是否设计 WAL 保留、槽滞后、消费者重连和告警指标。
  • 是否明确 PostgreSQL 之外的协调器、DNS、连接串和幂等下游责任。

回答前需要澄清的问题

  1. 消费者是另一个 PostgreSQL subscriber,还是 Debezium 等非 PostgreSQL CDC 客户端?两者的就绪检查不同。
  2. 目标是计划内零或近零丢失,还是故障后的可接受恢复点?这决定是否允许未同步 WAL 被舍弃。
  3. 下游是否按 LSN、事务 ID 或业务键去重?重复事件能否安全重放?
  4. 主备是否同一集群、同一 PostgreSQL 大版本和相同输出插件?
  5. 谁负责提升、切换连接地址、冻结旧主库并防止双主写入?

30 秒回答框架

我会先定义 CDC 的恢复点和重复容忍度,再把槽同步、提升、消费者重连和下游幂等拆成状态机。为需要跨故障继续消费的逻辑槽启用 failover 能力,并在计划切换前检查 standby 上每个槽的存在、同步状态和 failover_ready。切换时先隔离旧主库,再更新连接发现;消费者从新主库继续读取,可能重复的事务由 LSN 或业务幂等处理。持续监控槽滞后、WAL 保留、消费延迟和重连结果,不把异步槽同步当成零丢失保证。

分步骤深入解答

1. 先定义数据语义

把 RPO、RTO 和重复策略写进方案。计划内切换可等待所有目标槽同步,突发故障只能从新主库已持久化且已同步的位置继续。下游至少要保存最后确认的 LSN 或等价位点;业务写入应使用幂等键,避免一次事务重放造成重复副作用。

2. 配置可故障切换的逻辑槽

PostgreSQL 18 支持创建可同步到 standby 的逻辑槽,创建槽或订阅时需要启用 failover 选项。对需要继续消费的每个槽建立清单,避免只配置主流槽而遗漏表同步槽或其他消费者。

sql
-- 仅示意:创建逻辑槽时允许其同步到 standby
SELECT *
FROM pg_create_logical_replication_slot('cdc_orders', 'pgoutput', false, true);

实际语法和参数应以运行版本文档为准;面试回答应说明该 SQL 只是示意,不能绕过权限、插件和订阅配置检查。

3. 让物理复制把槽状态带到 standby

配置物理 standby 接收 WAL,并按文档要求设置 synchronized_standby_slots,使涉及逻辑故障切换的操作等待指定物理槽确认 WAL 已到达。槽复制仍可能有延迟,因此必须在切换前读取 standby 的 pg_replication_slots,确认目标槽存在、synced 状态有效且 failover_ready 为真。

4. 计划内切换流程

先暂停或排空 CDC 消费者,记录最后确认位点;再停止旧主库写入并等待物理复制追平。对每个消费者核对主库和 standby 的槽清单,确认所有所需槽已就绪后才提升 standby。切换连接发现并解除消费者暂停,消费者使用同一逻辑槽继续读取;如果从位点边界重放,依靠 LSN 或下游幂等消除重复。

5. 突发故障与双主保护

突发故障无法等待槽同步完成,应先阻止旧主库恢复后继续写入,再根据新主库的已确认 WAL 评估恢复点。协调器必须保证同一时间只有一个可写主库;DNS、VIP 或服务发现切换后,消费者仍需验证服务器身份和槽存在。未同步的逻辑槽不能被描述为完整继承,必要时应进入人工恢复或重建订阅流程。

6. 监控 WAL 堆积和消费者进度

监控每个槽的 restart_lsnconfirmed_flush_lsn、槽滞后、WAL 保留量、消费者重连次数和端到端延迟。槽长期无人消费会阻止 WAL 回收,最终耗尽磁盘;消费者恢复时应有超时、限速和人工接管阈值。把“槽存在”与“消费者真正处理到目标 LSN”分开告警。

7. 回退、重建与验收

若 standby 不满足就绪条件,不执行自动提升,保留旧主库或进入明确的降级流程。故障演练要覆盖计划切换、主库突然断电、消费者断线、槽同步延迟和旧主库误恢复。验收记录切换时最后提交、首个新主库事件、重复数、缺口数、RTO 和磁盘峰值;任一缺口都应追溯到具体 LSN。

高质量示范回答

我会把方案建模为“槽同步就绪、隔离旧主、提升 standby、切换发现、消费者续读、下游确认”六个状态。对 CDC 所需逻辑槽启用 failover,并用 synchronized_standby_slots 和 standby 上的 pg_replication_slots 检查,确认每个槽存在且 failover_ready 为真;槽同步是异步的,所以 standby 在线不等于可安全提升。

计划切换时暂停消费者、冻结旧主库写入并等待物理复制和槽同步完成,再提升 standby。突发故障则先防止旧主库复活成双主,接受文档和监控所能证明的恢复点,不承诺零丢失。消费者保存 LSN,切换后从新主库重连;重复事务由 LSN 检查和下游幂等处理。最后持续监控槽滞后、WAL 堆积、消费延迟、重连和缺口,并通过断电与延迟演练验证 RPO、RTO。

常见错误

  • 看到 standby 在线就直接提升 → 槽同步可能仍未完成 → 检查每个槽的存在、同步状态和 failover_ready
  • 把逻辑槽当成消费者已经处理的位点 → 槽状态与下游确认不同 → 分别保存和监控 LSN、消费位点。
  • 承诺故障切换零数据丢失 → 突发故障可能有未同步 WAL → 明确 planned 与 unplanned 的不同 RPO。
  • 只切换 DNS 不隔离旧主库 → 可能出现双主写入 → 先 fencing,再提升和切换发现。
  • 忽略槽导致 WAL 堆积 → 无人消费会阻止 WAL 回收 → 设置滞后、磁盘和最长保留告警。
  • 只测试数据库提升 → 消费者、输出插件和下游也可能失败 → 做端到端切换与重放演练。

追问及应对

failover_ready 为真就代表不会丢事件吗?

不代表。它说明相关逻辑槽已同步到目标 standby 并可在提升后继续使用;是否丢失还取决于故障发生时的物理复制确认、消费者确认位点和下游处理语义。

计划切换为什么还要暂停消费者?

暂停可以固定最后确认位点,避免切换期间消费者同时连接旧主和新主,便于判断重复与缺口。也可以使用更复杂的协调器,但必须证明不会产生双写或乱序。

非 PostgreSQL CDC 客户端如何验证?

它不能直接复用订阅查询,应维护自己的槽清单和健康检查,确认槽在 standby 存在且已同步,再以客户端协议验证新主连接和位点连续性。

如果槽不同步但业务必须恢复怎么办?

先宣布实际 RPO,选择保守恢复点;可重建槽、重新快照或从业务备份补偿。不能把未经验证的复制状态包装成无损恢复。

如何防止旧主库重新上线?

使用云平台或集群协调器的 fencing、隔离网络和写入凭证轮换;仅修改 DNS 不足以阻止旧主继续接受写入。

怎样证明下游没有重复副作用?

让下游按事务 LSN、事件 ID 或业务幂等键去重,并在演练中记录重放数量、最终业务计数和顺序约束,比较切换前后的可审计位点。

公开来源

同类题目