PostgreSQL 很清楚什么是主库、备库和故障转移,但它的高可用默认行为并不等于“复制槽也会安全迁移”。普通应用连接通常可以在端点切换后重连,逻辑复制、CDC 和部分物理复制消费者却依赖复制槽中保存的状态。一次看似成功的切换,可能让数据库恢复服务,却让下游数据管道丢失位置、重复消费,甚至不得不重新做全量快照。
真正可靠的做法不是寄希望于切换过程“刚好没问题”,而是把复制槽检查放进控制面:只要无法证明槽可以安全延续,就阻止 cutover。
高可用成功,不代表复制状态完整
复制槽至少承担两类职责:
- 物理复制槽帮助服务端为特定复制消费者保留所需 WAL。
- 逻辑复制槽还维护逻辑解码进度,例如
confirmed_flush_lsn,常被 CDC、事件流或迁移工具依赖。
问题在于,复制槽是带状态的数据库对象。新主库能接受连接,并不自动证明它拥有旧主库上的同名槽,也不证明槽的 LSN、时间线和消费者确认位置连续。
一次切换后可能出现几种结果:
- 新主库没有对应槽,消费者无法继续连接。
- 运维人员临时创建同名槽,但起始位置已经不同。
- 消费者从较早位置恢复,产生重复事件。
- 所需 WAL 已经不可用,只能重新初始化或重新做快照。
- 为保护槽而长期保留 WAL,反过来耗尽磁盘空间。
因此,“应用端点已经恢复”只能说明一部分高可用目标达成。对于依赖复制槽的系统,恢复点还必须覆盖复制协议和消费进度。
为什么应该默认阻止切换
PlanetScale 所强调的思路,是把 PostgreSQL 自身能够提供的复制能力,与平台对切换流程的控制结合起来。关键不只是执行 promotion,而是在 promotion 之前拥有拒绝执行的能力。
一个稳妥的 cutover gate 应回答这些问题:
- 当前主库是否存在复制槽?
- 每个槽由哪个服务、连接器或订阅使用?
- 新主库是否具备可延续的槽状态,而不只是相同的槽名?
- PostgreSQL 版本和配置是否支持计划采用的槽同步或故障转移方案?
- 如果需要重建槽,下游是否接受重新快照、重复事件或停机窗口?
- 槽当前保留了多少 WAL,等待是否会带来磁盘风险?
只要其中任何一项没有明确答案,安全默认值就应当是阻止切换,而不是先切再观察。新版 PostgreSQL 提供了更多与逻辑复制和故障转移槽相关的能力,但具体语义受版本、参数和拓扑影响,不能只根据“版本够新”就假设状态会自动迁移。
把复制槽检查做成可执行的门禁
下面是一个通用的、可直接改造的 Bash 门禁脚本。它不会执行实际切换;它检查当前主库,只要发现复制槽且没有显式审批,就以非零状态退出。
运行前需要安装 psql,并把 DATABASE_URL 设置为当前主库连接串。生产环境应使用只读监控账号,并通过密钥系统注入凭据。
#!/usr/bin/env bash
set -euo pipefail
: "${DATABASE_URL:?Set DATABASE_URL to the current primary}"
printf '%s\n' "Replication slots on the current primary:"
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 <<'SQL'
SELECT
slot_name,
slot_type,
COALESCE(database, '-') AS database,
active,
restart_lsn,
confirmed_flush_lsn,
pg_size_pretty(
pg_wal_lsn_diff(
pg_current_wal_lsn(),
COALESCE(confirmed_flush_lsn, restart_lsn)
)::bigint
) AS approximate_retained_wal
FROM pg_replication_slots
ORDER BY slot_type, slot_name;
SQL
slot_count="$({
psql "$DATABASE_URL" -AtX -v ON_ERROR_STOP=1 \
-c 'SELECT count(*) FROM pg_replication_slots;'
})"
if [[ "$slot_count" -eq 0 ]]; then
echo "PASS: no replication slots found."
exit 0
fi
if [[ "${ALLOW_SLOT_CUTOVER:-no}" != "yes" ]]; then
echo "BLOCKED: found $slot_count replication slot(s)." >&2
echo "Verify slot continuity, consumer offsets, WAL availability, and rollback before cutover." >&2
echo "Set ALLOW_SLOT_CUTOVER=yes only after that review is recorded." >&2
exit 42
fi
echo "OVERRIDE: slot-aware cutover has been explicitly approved."
保存为 check-replication-slots.sh 后,可以这样执行:
chmod +x check-replication-slots.sh
export DATABASE_URL='postgresql://monitor:secret@primary.example.com/postgres?sslmode=require'
./check-replication-slots.sh
在发布流水线中,应让真正的切换命令依赖该脚本成功:
./check-replication-slots.sh && ./run-approved-cutover.sh
这里的 ALLOW_SLOT_CUTOVER=yes 只是演示审批开关,不应成为绕过检查的快捷键。更成熟的实现应要求变更单编号、审批记录,以及旧主库和候选新主库的联合校验。
仅比较槽名也不够。对于逻辑复制,应进一步核对:
- 消费者最后确认的 LSN;
- 新主库上的槽是否来自受支持的同步或故障转移流程;
- WAL 是否覆盖消费者恢复所需的位置;
- 切换后是否可能重复投递;
- 连接器是否会自动创建新槽,从而掩盖状态丢失。
采用时的决策清单
可以按下面的顺序决定是否放行:
- 没有复制槽:按常规高可用流程切换,但仍需验证连接和事务恢复。
- 存在物理槽:确认对应复制消费者如何重新指向新主库,并检查 WAL 保留上限。
- 存在逻辑槽:优先使用当前 PostgreSQL 版本明确支持的槽同步或故障转移机制,并做真实消费测试。
- 计划重建槽:提前接受重新快照、重复数据或停机成本,不要把重建当成无损操作。
- 槽归属不明:直接阻止切换,先找到所有者和下游依赖。
阻止 cutover 看起来会降低操作速度,实际上是在把未知的数据风险转换为可见的发布决策。数据库能够提升为新主库,只说明计算和连接层准备好了;只有复制槽、WAL 和消费者进度也得到验证,连接中的应用与数据管道才真正安全。