PG17:逻辑复制槽故障转移
逻辑复制的复制槽长在发布端(primary),而发布端自己也要做高可用——这是逻辑复制在生产环境最尴尬的矛盾:主库宕机切换到备库后,新主库上没有那个逻辑槽(槽不随物理复制同步),下游订阅端瞬间断流,唯一出路是在新主库上重建槽 + 重建订阅 + 重新做全量初始同步。对生产级逻辑复制拓扑来说,这是「主库也做了 HA,逻辑订阅却没有 HA」的结构性缺陷。
PostgreSQL 17 用一整套「故障转移槽(failover slots)」机制修复了它:发布端指定哪些物理备库必须先同步(synchronized_standby_slots),备库持续把主库上的 failover 逻辑槽镜像过来(sync_replication_slots + pg_sync_replication_slots()),订阅端声明 failover = true 让自己的槽获得同步资格。切主之后,新主库上的镜像槽无缝接管,订阅端不用重建、不用重新同步。
本页把这套机制的每个环节讲透:配置怎么摆、切换时发生了什么、如何验证与监控,以及与 Patroni 等 HA 工具的配合。
前置知识
建议先阅读:PG17:VACUUM 内存改进 与 Patroni 高可用。
基础概念
| 概念 | 英文 / 记法 | 说明 |
|---|---|---|
| 故障转移槽 | failover slot | 标记为「可同步到备库」的逻辑复制槽 |
| 槽同步 | slot synchronization | 备库把主库逻辑槽的解码状态镜像过来的机制 |
synchronized_standby_slots | — | 主库参数:列出必须先完成槽同步的物理备库 |
sync_replication_slots | — | 备库参数:开启本库的槽同步功能 |
pg_sync_replication_slots() | — | 备库上调用的槽同步函数(由 sync worker 执行) |
订阅 failover 选项 | CREATE SUBSCRIPTION ... WITH (failover = true) | 订阅声明其发布端槽需要故障转移能力 |
synced 列 | pg_replication_slots.synced | 标记「该槽是从主库同步来的镜像槽」 |
| 镜像槽 | synced slot | 备库上的槽副本;不可用于解码、不可手动删除 |
| WAL 保留协调 | — | 主库等备库确认槽同步后才允许 WAL 回收,防止切主后丢变更 |
pg_createsubscriber | — | PG17 新增工具:把物理备库直接转成逻辑副本 |
之前版本的结构性缺陷
在 PG16 及之前,逻辑复制的槽只存在于发布端本机。发布端做流复制高可用(主备 + 切换)时:
┌────────────┐ 流复制 ┌────────────┐
│ 主库 A │ ─────────► │ 备库 B │
│ slot: sub1 │ │ (无 slot) │
└─────▲──────┘ └────────────┘
│ 逻辑订阅
┌─────┴──────┐
│ 订阅端 C │
└────────────┘A 宕机 → promote B 为新主 → B 上没有 sub1 槽 → 订阅端 C 断流 → 人工在 B 上重建槽(起点怎么选?选早了重复消费,选晚了丢变更)→ 重建订阅 → 初始同步。整个恢复过程既有数据风险又有人工介入,这是很多「逻辑复制 + HA」组合不敢上生产的核心顾虑。
PG17 的解法:槽随备库一起同步
PG17 让逻辑槽的状态可以随物理复制一起流动:
┌────────────┐ 流复制 + 槽同步 ┌────────────┐
│ 主库 A │ ─────────────────► │ 备库 B │
│ slot: sub1 │ │ synced 槽 │
└─────▲──────┘ │ (sub1 镜像) │
│ 逻辑订阅 └────────────┘
┌─────┴──────┐
│ 订阅端 C │ failover = true
└────────────┘
A 宕机 → promote B → B 上的镜像槽转正 → C 重连后从槽的确认位点继续三个角色各出一项配置,缺一不可:
- 订阅端:
CREATE SUBSCRIPTION ... WITH (failover = true),声明「我依赖的这个槽要能故障转移」; - 发布端(主库):
synchronized_standby_slots = '备库列表',声明「这些物理备库确认槽同步之前,我的 WAL 不回收」; - 备库:
sync_replication_slots = on,开启本库的槽同步;订阅端的 apply 进程(或手动调用pg_sync_replication_slots())驱动备库上的镜像槽持续推进。
一句话总结:主库管 WAL 保留的协调,备库管槽的镜像,订阅端管声明资格。
语法与用法
订阅端:failover 选项
-- 创建带故障转移能力的订阅
CREATE SUBSCRIPTION sub_orders
CONNECTION 'host=primary dbname=app user=sub_user password=secret'
PUBLICATION pub_orders
WITH (failover = true); -- 新增于 PG17
-- 已有订阅补充开启 / 关闭(不能在事务块内执行)
ALTER SUBSCRIPTION sub_orders SET (failover = true);
ALTER SUBSCRIPTION sub_orders SET (failover = false);failover = true 的效果:订阅端连接发布端时,会请求其复制槽带上 failover 属性(复制协议层面的 failover 属性,新增于 PG17);发布端随即把该槽标记为可同步,备库开始镜像它。
注意:订阅的 enabled 状态与此无关——即使订阅被禁用(subenabled = false),只要 failover = true 且发布端连接建立过,槽的同步资格就成立。
发布端:synchronized_standby_slots
# 主库 postgresql.conf(新增于 PG17)
# 逗号分隔的备库 application_name 列表:
# 这些备库确认「槽已同步到我的位点」之前,主库的 WAL 不回收
synchronized_standby_slots = 'standby_b,standby_c'语义是「逻辑变更的 WAL 保留以最慢的同步备库为准」:主库为逻辑订阅保留 WAL 的位置,不再只看逻辑槽自身的消费进度,还要看指定备库的镜像槽是否已同步到那里。这防止了一个致命场景:订阅端已确认消费 LSN X,备库镜像槽还停在 X-1000,此时主库把 X-1000 之后的 WAL 回收了,备库的镜像槽永久失去推进能力。
常见错误与处置速查
| 现象 | 原因 | 处置 |
|---|---|---|
| 备库上迟迟不出现镜像槽 | 备库未开 sync_replication_slots;或订阅从未以 failover 姿态连接过发布端 | 核对三件套配置;确认订阅端曾以 failover = true 建立连接 |
| 镜像槽出现但位点长期不推进 | 备库参数需重启而未重启;订阅端 apply 长期停滞 | 重启备库使参数生效;排查订阅端 worker 状态 |
| 主库 pg_wal 持续膨胀 | synchronized_standby_slots 里的备库失联,WAL 拒绝回收 | 修复备库;或临时将其移出列表(评估风险后执行) |
| 切主后订阅断流 | 升主节点不在列表 / 镜像槽滞后 / 连接串写死旧主机名 | 按巡检 SQL 逐项核对;连接串改为 HA 接入点 |
invalidation_reason 非空 | WAL 被过早回收,槽已失效 | 只能重建槽与订阅;先修复保留协调再重建 |
| 切主后镜像槽未转正 | 升主节点不在 synchronized_standby_slots 列表 | 修订列表,覆盖所有可升主节点 |
| 订阅端报槽冲突 | 新主上同名槽已被其他订阅占用 | 槽命名纳入环境/租户规范,冲突前就避免 |
错误信息层面的对照:备库日志出现 logical replication slot synchronization 相关 ERROR 时,先核对两端版本(主备都要 PG17+)与流复制连通性——槽同步依赖物理复制链路,物理断则同步停。
备库:sync_replication_slots 与同步函数
# 备库 postgresql.conf(新增于 PG17)
sync_replication_slots = on开启后备库会由内部的槽同步 worker 周期调用 pg_sync_replication_slots()(新增于 PG17),把主库上带 failover 属性的逻辑槽镜像过来。手工触发也可:
-- 备库上手动同步一次(一般由 worker 自动执行,手工用于调试)
SELECT * FROM pg_sync_replication_slots(); slot_name | failover | synced | ...
-----------+----------+--------+-----
sub_orders| t | t | ...
(1 row)观测:pg_replication_slots 的新列
-- 主库 / 备库上都可查询
SELECT
slot_name,
slot_type,
active,
failover, -- 新增于 PG17:该逻辑槽是否启用故障转移
synced, -- 新增于 PG17:该槽是否为从主库同步来的镜像槽
restart_lsn,
confirmed_flush_lsn
FROM pg_replication_slots; slot_name | slot_type | active | failover | synced | restart_lsn | confirmed_flush_lsn
------------+-----------+--------+----------+--------+-------------+---------------------
sub_orders | logical | t | t | f | 0/3000018 | 0/3012A40 ← 主库上的原槽
sub_orders | logical | f | t | t | 0/3000018 | 0/3012A40 ← 备库上的镜像槽
(2 rows)synced = true 的镜像槽在备库上有两条硬性约束(官方文档明确):
- 不能用于逻辑解码——它是备品,不是成品;
- 不能手动删除——它的生命周期跟随主库原槽,切主转正后才成为正常槽。
其他同步相关的新列:inactive_since(PG17)在备库上报告「镜像槽最近一次停止同步的时间」,invalidation_reason(PG17)报告槽失效原因(wal_removed / rows_removed / wal_level_insufficient)。
三件套的最小配置清单
最后把三处配置收进一张核对清单。① 备库与 ② 主库是配置文件参数,③ 订阅是 SQL 动作:
# ① 备库 postgresql.conf(需重启生效)
sync_replication_slots = on
# ② 主库 postgresql.conf(reload 即可)
synchronized_standby_slots = 'standby_b,standby_c'-- ③ 订阅端:声明 failover 资格
CREATE SUBSCRIPTION sub_orders
CONNECTION 'host=primary dbname=app user=sub_user password=secret'
PUBLICATION pub_orders
WITH (failover = true, copy_data = true);三者的生效顺序有讲究:先开备库的 sync_replication_slots(重启生效),再配订阅的 failover(连接建立后资格生效),最后在主库配置 synchronized_standby_slots(收口 WAL 保留协调)。顺序颠倒的典型症状是「主库列表已配、镜像槽却迟迟不出现」——按「备库 → 订阅 → 主库」的顺序核对每一步的生效条件,多数配置类排障可以直接绕开。
详细说明
端到端工作流程
把整条链路串起来,正常运行时每个环节做什么:
- 订阅端 apply 进程连接主库消费变更,周期性把消费进度回传给主库的槽(
confirmed_flush_lsn推进)。 - 主库发现槽带 failover 属性,把它的状态(含解码进度)加入「待同步清单」;
synchronized_standby_slots列出的备库通过流复制收到变更。 - 备库的槽同步 worker(
sync_replication_slots = on)把镜像槽推进到主库槽的当前位点。 - 主库的 WAL 回收检查同时看两个水位:逻辑槽自己的消费水位 + 指定备库镜像槽的同步水位,取更保守者。
- 故障发生:备库 promote 为新主,其上的镜像槽自动「转正」为普通逻辑槽(
synced变为无意义/复位)。 - 订阅端重连(连接串经过 HA 工具或 DNS 指向新主)后,从镜像槽的确认位点继续消费——不丢、不重、无需重建。
切主时订阅端为什么不丢数据
关键的正确性论证在 WAL 保留协调上:
- 订阅端确认消费到 LSN X 之后,主库原槽的
confirmed_flush_lsn = X; - 备库镜像槽只有确认同步到 LSN X 之后,主库才允许回收 X 之前的 WAL;
- 因此切主时,镜像槽的位点必然覆盖订阅端已确认的全部变更;
- 新主从镜像槽位点继续供订阅消费,衔接点正是原槽的最后确认位。
反过来,如果没配 synchronized_standby_slots(或备库没开 sync_replication_slots),failover 槽机制就退化为「标记生效但镜像落后」,切主后可能丢变更或镜像槽失效——三个角色的配置必须齐全且一致。
failover 槽的创建方式
带 failover 属性的逻辑槽有两种创建路径:
-- 路径一:订阅创建/变更(推荐,订阅端一条 SQL 搞定)
CREATE SUBSCRIPTION sub_orders ... WITH (failover = true);
ALTER SUBSCRIPTION sub_orders SET (failover = true);
-- 路径二:发布端手工创建(第五个参数 failover,新增于 PG17)
SELECT * FROM pg_create_logical_replication_slot(
'manual_slot', -- 槽名
'pgoutput', -- 输出插件
false, -- temporary
false, -- two_phase
true -- failover(新增于 PG17,默认 false)
);手工创建的 failover 槽通常用于「订阅端直连发布端手工消费」的场景(如自研的 CDC 管道);走内置逻辑复制的场景一律用订阅的 failover 选项。
与 Patroni 的配合
Patroni 管理 HA 切换,failover 槽管逻辑订阅的连续性,两者配合的要点:
- 连接串指向 VIP/DNS。订阅端的
CONNECTION应指向由 Patroni 管理的接入点(VIP 或 DNS),切主后自动指向新主;Patroni 的机制见 Patroni 高可用。 - 备库参数要进 Patroni 配置。
sync_replication_slots等参数写进 Patroni 的postgresql.conf管理段,避免手工配置被 Patroni 覆盖。 - 备库身份在切换前后对齐。
synchronized_standby_slots列表里的 application_name 必须与各备库的实际名称一致——HA 集群里备库可能随时升主,列表要覆盖所有可能成为新主的节点。 - ** Patroni 自身的 standby 集群(级联场景)**:Patroni 3.x 支持 standby cluster(从另一个集群的物理备库再起一套 HA),与 failover 槽的叠加配置要分别验证,特别是镜像槽在级联链路上的行为。
pg_createsubscriber:从物理备库快速生成逻辑副本
PG17 还带来了一个相关的实用工具 pg_createsubscriber(新增于 PG17):把一个物理备库原地转换成逻辑副本(自动建立发布/订阅关系,无需传统逻辑复制初始化的全量拷贝)。它改变了「逻辑副本扩容」的成本模型:以前要做逻辑复制的初始同步(导出+导入+追增量),现在可以从一个已追平的物理备库直接转换。
适用场景:把某个物理备库「释放」出来给下游做数据分发、或给逻辑复制拓扑快速增加一个订阅端。它与 failover 槽互补——一个解决「逻辑副本怎么来」,一个解决「发布端 HA 后逻辑订阅怎么续」。
pg_upgrade 对逻辑槽的保留
PG17 的 pg_upgrade 新增能力:迁移时保留发布端的有效逻辑槽与订阅端的完整订阅状态(新增于 PG17)。此前用 pg_upgrade 升级逻辑复制的两端,槽与订阅都要手工重建;PG17 起升级后逻辑复制可以直接续跑,为「大版本升级不中断逻辑订阅」铺平了道路。官方同时注明:这一能力只对 PG17 及之后的旧集群生效(旧版本集群要先升到 17 再享受)。
监控与告警设计
failover 槽拓扑的监控面板建议包含:
-- 1. 槽的同步状态总览(备库上)
SELECT slot_name, failover, synced, active, restart_lsn, confirmed_flush_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_wal
FROM pg_replication_slots
WHERE failover;
-- 2. 主库与备库镜像槽的位点差(同步滞后量)
-- 在主库与备库分别取 confirmed_flush_lsn 做差
-- 3. 槽失效原因
SELECT slot_name, conflicting, invalidation_reason
FROM pg_replication_slots
WHERE invalidation_reason IS NOT NULL;告警阈值:
- 镜像槽滞后量(主库槽与镜像槽的 LSN 差)持续增长 → 备库同步 worker 异常;
invalidation_reason非空 → 立即告警(通常意味着synchronized_standby_slots没配好,WAL 被过早回收);- 订阅端
pg_stat_subscription的 worker 消失或延迟突增 → 切主后的接管异常; - 主库 WAL 保留量异常增长 → 指定备库的确认通道阻塞。
位点滞后与切换丢失窗口的量化
failover 槽承诺的是「不丢已提交的订阅变更」,但切换瞬间接管到哪个位点值得量化理解:
主库槽位点 L_primary = 订阅端已确认消费到的位置
镜像槽位点 L_mirror = 备库侧同步到的位置(≤ L_primary,差 = 同步延迟)
新主接管位点 L_takeover = L_mirror(failover 槽转正后从镜像位点继续)
订阅端将重复消费 [L_mirror, L_primary) 之间的变更三个推论:
- 切换丢失窗口 ≈ 镜像槽的滞后量。正常是毫秒到秒级(流复制延迟 + 同步周期),备库异常时该窗口持续增长——这正是镜像槽滞后要纳入告警的原因;
- 重复消费由订阅端的幂等性兜底。逻辑复制 apply 本身可安全重放(订阅端按事务与唯一约束处理),但订阅端下游(业务触发器、导出管道)要有幂等意识;
- WAL 保留协调以最慢的镜像为准,
synchronized_standby_slots列表越长、确认路径越多,主库 WAL 保留水位越保守——这是「列表只放可能升主的节点」背后的代价说明。
对「订阅断流比丢变更更严重」的业务(金融对账类),切主演练里 T1 的 B ≤ A 校验就是丢失窗口的直接测量。这组记号也是与上层沟通的语言:向业务方解释「切主对订阅的影响」时,「接管位点 = 镜像位点、重复消费窗口 = 滞后量」一句话就能说清,比笼统的「可能有点重复」专业得多。
槽同步的运行模型:谁在什么时候推进什么
把镜像槽的推进过程拆开看,有助于定位滞后:
- 主库:订阅端 apply 持续回传消费位点,主库槽的
confirmed_flush_lsn推进;带 failover 属性的槽进入「待同步清单」。 - 备库:sync worker 依托流复制收到 WAL 后,按
pg_sync_replication_slots()的机制把镜像槽推进到「不超过主库槽确认位点」的位置;镜像槽永远略滞后于主库槽——滞后量 = 流复制延迟 + 同步周期。 - WAL 回收检查:主库回收 WAL 前检查「所有 failover 槽的镜像已覆盖到哪」,回收水位取最保守者。
故障形态对照:
| 故障 | 表现 | 影响 |
|---|---|---|
| 备库 sync worker 异常 | 镜像槽位点冻结 | 主库 WAL 停止回收(膨胀);切主后接管位点偏旧 |
| 流复制中断 | 镜像槽停止推进 | 同上,且备库自身数据也滞后 |
| 订阅端 apply 停滞 | 主库槽位点冻结 → 镜像槽跟着冻结 | 主备 WAL 保留双双增长(订阅滞后向上传导) |
规律:镜像槽的位点永远不会超过主库槽,订阅端的停滞最终传导为全链路 WAL 保留增长。监控面板上三条位点曲线——主库槽、镜像槽、当前 WAL 写入位点——的间距就是整条链路的健康画像。
把这条画像与告警阈值绑定,槽同步的问题就能在切主演练之前暴露——演练验证正确性、监控保证日常性,两者缺一不可。
实战示例
示例一:三节点环境搭建 failover 槽
环境:primary(主)、standby_b(物理备库)、subscriber(逻辑订阅端)。
# 1. 主库:配置 WAL 保留协调
psql -h primary -U postgres <<'SQL'
ALTER SYSTEM SET synchronized_standby_slots = 'standby_b';
SELECT pg_reload_conf();
SQL
# 2. 备库:开启槽同步
psql -h standby_b -U postgres -c "ALTER SYSTEM SET sync_replication_slots = on;"
# Patroni 环境写入其配置管理;重启后生效(该参数需重启)# 3. 订阅端:创建/变更订阅启用 failover
psql -h subscriber -U postgres -c "
ALTER SUBSCRIPTION sub_orders SET (failover = true);"ALTER SUBSCRIPTION验证三个角色的状态:
# 主库:槽带 failover 属性
psql -h primary -c "SELECT slot_name, failover, synced FROM pg_replication_slots;" slot_name | failover | synced
------------+----------+--------
sub_orders | t | f
(1 row)# 备库:出现 synced 镜像槽
psql -h standby_b -c "SELECT slot_name, failover, synced, active FROM pg_replication_slots;" slot_name | failover | synced | active
------------+----------+--------+--------
sub_orders | t | t | f
(1 row)示例二:模拟切主并验证订阅续跑
# 1. 记录切换前两端的位点
psql -h primary -c "SELECT confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name='sub_orders';"
psql -h standby_b -c "SELECT confirmed_flush_lsn FROM pg_replication_slots WHERE slot_name='sub_orders' AND synced;"
# 2. 在主库写入一批变更
psql -h primary -d app -c "INSERT INTO orders (user_id, amount) VALUES (1, 99.00) RETURNING id;"
# 3. 模拟主库故障,提升备库(Patroni 环境用其命令;手工环境直接 pg_ctl promote)
patronictl -c /etc/patroni.yml switchover --leader primary --candidate standby_b
# 4. 新主上确认镜像槽已转正
psql -h standby_b -c "SELECT slot_name, failover, synced, active FROM pg_replication_slots;" slot_name | failover | synced | active
------------+----------+--------+--------
sub_orders | t | f | t
(1 row)# 5. 订阅端观察接管(apply worker 重连新主并继续推进)
psql -h subscriber -c "
SELECT subname, pid, received_lsn, latest_end_lsn, last_msg_receipt_time
FROM pg_stat_subscription;" subname | pid | received_lsn | latest_end_lsn | last_msg_receipt_time
-----------+-------+--------------+----------------+------------------------------
sub_orders| 31218 | 0/305B210 | 0/305B210 | 2026-10-07 11:52:03.881+08
(1 row)订阅端无需任何重建动作,apply worker 重连新主后从镜像槽位点继续——这就是 failover 槽的端到端价值。
示例三:配置不全时的行为对照
# 对照组:备库未开启 sync_replication_slots
psql -h standby_b -c "ALTER SYSTEM SET sync_replication_slots = off;"
# 主库槽仍带 failover 属性,但备库上不会出现镜像槽
psql -h standby_b -c "SELECT slot_name, synced FROM pg_replication_slots;" slot_name | synced
-----------+--------
(0 rows)# 此时强行切主:新主上没有槽,订阅断流(回到 PG16 时代的行为)
patronictl switchover --leader primary --candidate standby_b
psql -h standby_b -c "SELECT * FROM pg_replication_slots;" slot_name | slot_type | ...
-----------+-----------+----
(0 rows) ← 无任何槽,订阅端只能重建对照结论:failover = true 只提供「资格」,备库的 sync_replication_slots 才提供「能力」,主库的 synchronized_standby_slots 提供「正确性」。三项检查应纳入部署清单与巡检脚本。
示例四:巡检 SQL(一键体检)
-- failover 槽健康巡检(可在主备分别执行并汇总)
WITH slots AS (
SELECT slot_name, slot_type, failover, synced, active,
restart_lsn, confirmed_flush_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_wal_bytes
FROM pg_replication_slots
)
SELECT
slot_name,
CASE
WHEN slot_type <> 'logical' THEN 'SKIP (physical slot)'
WHEN NOT failover THEN 'WARN: failover 属性未开启'
WHEN NOT active AND NOT synced THEN 'WARN: 原槽未被消费'
ELSE 'OK'
END AS health,
pg_size_pretty(retained_wal_bytes) AS retained_wal
FROM slots
ORDER BY slot_name;把这段巡检纳入每日巡检脚本,三个角色配置缺失都能被第一时间的 WARN 暴露。
示例五:部署检查清单(脚本化)
把「三件套 + 拓扑一致性」的检查固化成脚本,部署与巡检两用:
#!/bin/bash
# failover_slot_check.sh <host> —— failover 槽三件套体检
set -e
H="${1:-primary}"
echo "== 1) 主库:synchronized_standby_slots 与槽属性 =="
psql -h "$H" -Atc "SHOW synchronized_standby_slots;"
psql -h "$H" -c "SELECT slot_name, failover, active, confirmed_flush_lsn
FROM pg_replication_slots WHERE slot_type = 'logical';"
echo "== 2) 逐备库:sync_replication_slots 与镜像槽 =="
for S in $(psql -h "$H" -Atc "SELECT application_name FROM pg_stat_replication;"); do
echo "-- standby: $S"
psql -h "$S" -Atc "SHOW sync_replication_slots;"
psql -h "$S" -c "SELECT slot_name, synced, active FROM pg_replication_slots WHERE synced;"
done
echo "== 3) 槽失效检查 =="
psql -h "$H" -c "SELECT slot_name, invalidation_reason
FROM pg_replication_slots WHERE invalidation_reason IS NOT NULL;"脚本要点是逐备库遍历:pg_stat_replication.application_name 就是 synchronized_standby_slots 列表里的名字,列表与实际拓扑的偏差在这里现形。退出码非零即告警,接入现有巡检调度即可。
示例六:切主演练的观察点清单
failover 槽的可靠性要靠定期演练证明。一次完整的演练记录这些观察点:
T0 记录主库槽 confirmed_flush_lsn = A
T1 记录备库镜像槽位点 = B(应满足 B ≤ A,差距 = 流复制延迟)
T2 执行 HA 切主(Patroni:patronictl switchover)
T3 新主上检查:failover 槽是否转正、位点不回退(≥ B)
T4 订阅端恢复:worker 重新出现,apply 从 B 继续、无重复消费报错
T5 业务核对:切主期间主库上新增的写入全部出现在订阅端对应的 SQL 观察命令:
-- T3:新主上确认槽存活与位点
SELECT slot_name, plugin, active, confirmed_flush_lsn
FROM pg_replication_slots WHERE failover;
-- T4:订阅端健康
SELECT * FROM pg_stat_subscription;演练通过的标准是 T4 与 T5 全部达成(订阅无断流、无重复消费、无丢变更);任何一步失败都回推到三件套配置与巡检脚本,修复后重演。演练频率建议每季度一次,HA 拓扑变更后立即一次——failover 槽是「为故障时刻准备」的机制,不演练就等于没配置。演练记录(T0~T5 的实际位点数值与时间戳)要归档到运维知识库:它既是故障复盘的参照系,也是「failover 槽真的在工作」的证据链。
示例七:多订阅环境的位点总览报表
一个主库挂多组订阅时,位点总览比逐个查快得多:
-- 主库:全部逻辑槽的保留与滞后一览
SELECT slot_name, plugin, active,
pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS retain_bytes,
failover
FROM pg_replication_slots
WHERE slot_type = 'logical'
ORDER BY retain_bytes DESC;
-- 备库:镜像槽与物理回放位点的差
SELECT slot_name, synced,
pg_wal_lsn_diff(pg_last_wal_replay_lsn(), restart_lsn) AS lag_bytes
FROM pg_replication_slots
WHERE synced;报表口径提示:retain_bytes 是「主库当前写入位点 − 槽确认位点」的即时差值,受瞬时写入影响,看趋势不看单点;长期为正且持续增长的槽才是问题槽。把这报表接入 Grafana,synchronized_standby_slots 的每个成员都有了滞后曲线,pg_wal 磁盘的容量规划从此有数。
注意事项
三个角色的配置缺一不可。 订阅 failover = true、备库 sync_replication_slots = on、主库 synchronized_standby_slots 齐全,机制才完整成立;任何一项缺失都会让切主后的接管失效或丢数据。
synchronized_standby_slots 必须覆盖所有可能升主的节点。 HA 集群里任何一个备库都可能成为新主;列表只写一部分,等于给「升主节点恰好不在列表」的情况埋下丢变更的雷。
主库的 WAL 保留以同步备库为下限。 列表里的备库长期宕机会阻止 WAL 回收(pg_wal 膨胀);要为「备库永久失联」准备运维动作(从列表移除并清理其镜像槽)。
镜像槽在备库上不能用于解码、不能手动删除。 想在备库上消费变更请用级联逻辑复制等正规方式;强行 DROP 镜像槽会报错,这是设计行为不是 bug。
ALTER SUBSCRIPTION ... SET (failover = ...) 不能在事务块内执行。 与其他订阅变更一样需要独立事务边界;迁移脚本里注意包事务的位置。
切主后第一时间核对订阅接管。 pg_stat_subscription 的 apply worker 是否重连、received_lsn 是否推进——自动化巡检要覆盖切换后的 5 分钟窗口。
镜像槽的同步滞后是正常现象但要有阈值。 高写入压力下镜像槽天然落后主库槽几个 LSN;持续增长才异常,告警阈值按正常波动幅度的数倍设定。
pg_upgrade 的槽保留只对 PG17+ 源集群生效。 从 PG16 及更早版本升级时槽仍需手工处理,升级预案按旧版本行为编写。
不要绕过 HA 工具做 promote。 Patroni 管理的集群手工 pg_ctl promote 会让集群状态混乱,failover 槽的转正也依赖受控的切换流程。
镜像槽不等价于「备库上可用的解码槽」。 它的存在意义是「切主后转正」,日常状态下不可消费、不可删除;「把备库当解码服务器」的方案要重新设计,而不是借用镜像槽。
版本与拓扑变更后重跑三件套检查。 大版本升级、备库重命名、Patroni 配置重构之后最容易发生「配置漂移」——巡检脚本的意义就是把漂移当天暴露。
配置生效顺序影响排障方向。 备库参数需重启、订阅 failover 随连接建立生效、主库列表 reload 即可——「镜像槽没出现」时按这个顺序核对生效状态,比乱猜配置快得多。
最佳实践
- 凡是「主库做 HA + 逻辑复制」的拓扑,默认启用 failover 槽三件套。 把
failover = true、sync_replication_slots、synchronized_standby_slots写进部署模板,不再逐项目评审。 synchronized_standby_slots用配置管理工具维护并与集群拓扑同步。 备库增减时同步更新列表;巡检脚本校验「列表 ⊆ 当前集群节点名」。- 订阅端连接串指向 HA 接入点。 VIP/DNS 而不是具体主机,切主后订阅自动跟随;连接串写死主机名是断流时长翻倍的直接原因。
- 把镜像槽滞后量接入监控。 主备槽位点差是「同步通道健康度」的核心指标,持续增长告警。
- 切主演练纳入例行运维。 每季度模拟一次 switchover,验证订阅端自动接管、记录断流时长;演练结果作为 HA 能力的验收依据。
- 备库失联的预案写进 runbook。 从
synchronized_standby_slots移除、清理镜像槽、恢复后重新入列——手工步骤固化,避免 pg_wal 膨胀时现想。 - 监控
invalidation_reason,出现即最高优先级。 槽失效意味着逻辑订阅链路已断,需要重建;它几乎总是配置协调问题的最终暴露。 - 逻辑副本扩容优先考虑
pg_createsubscriber。 从追平的物理备库转换,比传统初始同步省去全量拷贝;转换后的拓扑要重审 failover 槽资格。 - 大版本升级用 PG17 的
pg_upgrade槽保留能力。 升级前核对两端均为 PG17+,升级后第一时间验证订阅续跑;预案区分「17→18」与「16→17」两条路径。 - 巡检 SQL 固化为每日任务。 本页的体检 SQL 覆盖配置缺失、槽失效、WAL 膨胀三类主要风险,巡检结果接入告警或工单。
- 为订阅端记录 failover 配置到 CMDB。 哪些订阅启用了 failover、对应的发布端与备库列表是什么——跨系统拓扑信息不落库,故障时只能靠翻配置文件。
- PG18 的相关演进保持关注。 逻辑复制的高可用化仍在持续演进(如订阅端多主、槽级监控增强),升级规划时对照新版 release notes 评估增补。
下一节
继续学习:PG18 概览
参考链接
- PostgreSQL 17 Release Notes(官方发布说明)
- PostgreSQL 官方文档:Logical Replication Subscription(failover 选项与槽管理)
- PostgreSQL 官方文档:Replication Slots Synchronization(47.2.3)
- PostgreSQL 官方文档:pg_replication_slots(failover / synced 列)
- PostgreSQL 官方文档:synchronized_standby_slots
- PostgreSQL 官方文档:sync_replication_slots
- PostgreSQL 官方文档:pg_sync_replication_slots()
- PostgreSQL 官方文档:pg_createsubscriber
- PostgreSQL 官方文档:pg_upgrade(逻辑槽保留)
- Patroni 官方文档(HA 切换与配置管理)