PostgreSQL 切换前先踩刹车:别让复制槽在主库迁移中消失

2026-09-21 19 预计阅读时间: 1 分钟
来源: planetscale.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

PostgreSQL 很清楚什么是主库、备库和故障转移,但它的高可用默认行为并不等于“复制槽也会安全迁移”。普通应用连接通常可以在端点切换后重连,逻辑复制、CDC 和部分物理复制消费者却依赖复制槽中保存的状态。一次看似成功的切换,可能让数据库恢复服务,却让下游数据管道丢失位置、重复消费,甚至不得不重新做全量快照。

真正可靠的做法不是寄希望于切换过程“刚好没问题”,而是把复制槽检查放进控制面:只要无法证明槽可以安全延续,就阻止 cutover。

高可用成功,不代表复制状态完整

复制槽至少承担两类职责:

  • 物理复制槽帮助服务端为特定复制消费者保留所需 WAL。
  • 逻辑复制槽还维护逻辑解码进度,例如 confirmed_flush_lsn,常被 CDC、事件流或迁移工具依赖。

问题在于,复制槽是带状态的数据库对象。新主库能接受连接,并不自动证明它拥有旧主库上的同名槽,也不证明槽的 LSN、时间线和消费者确认位置连续。

一次切换后可能出现几种结果:

  1. 新主库没有对应槽,消费者无法继续连接。
  2. 运维人员临时创建同名槽,但起始位置已经不同。
  3. 消费者从较早位置恢复,产生重复事件。
  4. 所需 WAL 已经不可用,只能重新初始化或重新做快照。
  5. 为保护槽而长期保留 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 和消费者进度也得到验证,连接中的应用与数据管道才真正安全。


相关推荐