Skip to content

pg_rewind 时间线回退 ​

故障转移完成之后,有一件必做的善后工作:旧主库怎么办。它带着一条与新主库分叉的旧时间线,直接重启会以主库身份产生冲突的 WAL,直接当备库启动又会因为时间线不一致而拒绝跟随。传统做法是用 pg_basebackup 从新主库全量重建——数据量大的库这可能意味着几个小时的拷贝与带宽消耗。pg_rewind 提供了一条捷径:它把旧主库的数据目录「倒拨」回分叉点,再从新主库拷贝分叉点之后的变更块,让旧主库以备库身份重新加入集群,全程只传输增量。

pg_rewind 的价值体现在两个场景:一是故障转移后旧主库的回收再利用(这是它的本职);二是切换(switchover)后原主库的降级归位。在 Patroni、repmgr 这类自动化工具内部,节点重新加入集群时调用的正是 pg_rewind。可以说,没有 pg_rewind,自动故障转移的「善后成本」会高出一个数量级。

但 pg_rewind 不是一个「开箱即用」的工具:它对 wal_log_hints 或数据校验和有硬性前提,而这些开关在故障发生时才想起来开启就太晚了。本页讲清它的原理、前提、参数、完整操作流程与常见错误,并给出它与全量重建的取舍对比。

前置知识

建议先阅读:手动故障转移(pg_ctl promote)

基础概念 ​

使用 pg_rewind 之前,需要先建立一组术语。这些概念大多与时间线、WAL 相关,理解了它们,后面「为什么必须开 hints」「为什么 WAL 丢了就回不去」这些问题就不再是死记硬背。

概念英文 / 记法说明
目标库target要被「倒拨」的实例,通常是旧主库,pg_rewind 的 -D 所指
源库source对齐的参照方,通常是新主库,通过 --source-server 或 --source-pgdata 指定
分叉点fork point两条时间线最后一次相同的 WAL 位置,pg_rewind 的回拨基准
时间线分叉timeline divergence提升备库后,新旧主库从同一位置各自产生不同的 WAL
全页写full page writecheckpoint 后页面首次修改时把整页写入 WAL,是 hints 机制的基础
wal_log_hints参数把每个页面首改时的整页内容记入 WAL,pg_rewind 的前提之一
数据校验和data checksumsinitdb -k 开启,效果等价于 wal_log_hints 的前提
干净关闭clean shutdown目标库在 rewind 前必须处于的状态,或由 pg_rewind 自动达成
复制槽replication slot重接入后防止 WAL 被回收的机制

目标库与源库 ​

pg_rewind 是一个单向的数据修复工具:数据只从源库流向目标库,目标库上分叉之后产生的所有变更会被丢弃。方向不能搞反——如果你想保留旧主库上的数据,那么先别运行 pg_rewind,而应先把那部分数据比对导出。

两点重要推论:

  1. 运行中的源库无需停机。源库通常就是正在对外服务的新主库,pg_rewind 通过连接协议在线读取它,不要求源库停止。
  2. 目标库必须先停止(或由 pg_rewind 自动处理关闭)。目标库的数据文件会被直接改写,带着运行中的 PostgreSQL 修改文件是危险的。

时间线分叉与分叉点 ​

时间线分叉是 pg_rewind 存在的根本原因。回顾一下故障转移的时间轴:

text
时间线 1 ──────────────●──────────────────────►
                       │ 分叉点(提升时刻)
                       │
时间线 1(旧主继续) ───●────A────A────A──►  旧主库的 WAL(A = 旧主写入)
                       │
时间线 2(新主提升) ───●────B────B────B──►  新主库的 WAL(B = 新主写入)

提升备库时,新主库从分叉点切换到时间线 2。此后旧主库若恢复运行,它在时间线 1 上继续写 WAL(A 段),与新主库的 B 段互不相认。pg_rewind 要做的,就是找到这个分叉点,把目标库从「分叉点之后 + A 段」回退到「分叉点」,再从源库拷贝 B 段对应的变更块。

分叉点的判定依据是时间线历史文件(.history)的交叉比对,细节见时间线与时间线历史。

wal_log_hints 与数据校验和 ​

pg_rewind 只拷贝「分叉点之后目标库修改过的块」这一增量的前提,是它能知道哪些块被修改过。这个信息来自目标库自身分叉之后的 WAL——只要 WAL 记录了足够信息(整页内容),pg_rewind 就能据此回退页面。

wal_log_hints = on 强制每个页面在 checkpoint 后首次修改时把整页内容写入 WAL。默认关闭(off)。开启数据校验和(initdb -k 或 pg_checksums)也能达到同样效果,因为校验和机制下页面必须带 hint 写入 WAL。二者满足其一即可,这也是官方文档对 pg_rewind 的明确前提。

conf
# postgresql.conf —— 让 pg_rewind 未来可用
wal_log_hints = on

这两个开关必须提前开启

wal_log_hints 随时可以开启(需重启),但只对开启之后产生的变更有效——如果故障已经发生、WAL 已经缺 hints,事后开启救不了这一次。数据校验和则只能在 initdb 时启用(初始化之后可以用 pg_checksums 工具离线开启,但需要停机扫描全部数据文件)。因此高可用规划阶段就应开启其一。

干净关闭 ​

pg_rewind 要求目标库在运行前处于干净关闭(cleanly shut down)状态,即正常停机、控制文件中记录的关闭状态是干净的。如果目标库是崩溃的(unclean shutdown),pg_rewind 会先尝试对它做一次崩溃恢复再关闭;如果你已经手工保证它干净关闭了,可以用 --no-ensure-shutdown 跳过这一步。

这一步存在的意义:pg_rewind 要扫描目标库「分叉点之后」的 WAL 并改写数据文件,这些操作要求一个一致的数据目录状态,而只有干净关闭(或完成崩溃恢复)的目录才有这个保证。

语法与用法 ​

命令格式 ​

pg_rewind 有两种源库指定方式:直接读源库数据目录(--source-pgdata),或通过连接协议访问运行中的源库(--source-server)。后者是生产环境的主要用法。

bash
pg_rewind [选项] -D 目标数据目录

常用参数 ​

参数说明
-D / --target-pgdata=dir目标库数据目录,即被回退的一方
--source-pgdata=dir源库数据目录的路径(要求源库也停止,很少用于生产)
--source-server=connstr源库的 libpq 连接串,生产环境的标准用法
-n / --dry-run演练模式:只做检查与统计,不真正改写文件
-P / --progress显示进度
-R / --write-recovery-conf结束后自动生成 standby.signal 与 primary_conninfo
--config-file=filename指定目标库的配置文件路径(配合自动崩溃恢复使用)
--no-ensure-shutdown不做「确保干净关闭」的处理,要求目标库已被干净关闭
-c / --restore-target-wal目标库自动崩溃恢复所需的 WAL 改从目标库的 pg_wal / 归档获取,而不是从源库下载
-w / --no-password不提示输入密码
-W / --password强制提示输入密码

典型用法:从新主库回收旧主库 ​

bash
# 1. 停止旧主库(保证干净关闭)
pg_ctl stop -D /var/lib/postgresql/18/main -m fast

# 2. 先演练,确认可行且无意外
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres dbname=postgres" \
          --dry-run --progress

# 3. 正式执行,并用 -R 自动生成备库恢复配置
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres dbname=postgres" \
          --progress --write-recovery-conf

# 4. 启动,实例将以备库身份跟随新主库
pg_ctl start -D /var/lib/postgresql/18/main

--source-server 的连接串中,用户需要对源库有足够的读取权限(实践中通常直接使用超级用户),且 pg_hba.conf 需放行该连接。

凭据与连接管理 ​

连接串中的凭据管理遵循 libpq 的通用规则,~/.pgpass 是首选:

bash
# ~/.pgpass 中放一条凭据(权限必须为 0600)
# hostname:port:database:username:password
echo "10.0.0.11:5432:*:postgres:S3cret_Pass" >> ~/.pgpass
chmod 600 ~/.pgpass

之后 --source-server 连接串里不再需要密码,-R 写入 postgresql.auto.conf 的 primary_conninfo 也不含密码——备库启动时同样从 ~/.pgpass 读取。如果必须在连接串里写密码(例如容器化环境),执行后应核对该文件的读取权限。

演练先行 ​

bash
# dry-run 会打印将要拷贝的文件列表与块数,但不落盘
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres" \
          --dry-run
text
servers diverged at WAL position 0/3000A5B8 on timeline 1
rewinding from last common checkpoint at 0/3000A000 on timeline 1
...
Number of removed files:      3
Number of copied files:      12
Number of blocks skipped: 1048576
Number of blocks changed:     428

--dry-run 的输出里最有价值的是 Number of blocks changed:分叉之后旧主库写入了多少块。这个数字既反映了本次 rewind 的传输量,也侧面印证了分叉期间旧主库接受了多少写入。

详细说明 ​

工作原理:找分叉点、扫变更块、拷贝 ​

pg_rewind 的一次运行分为几个明确的阶段,理解这些阶段有助于在出错时判断卡在哪一步。

text
阶段 1  确认目标库干净关闭
        └─ 运行中则自动关闭;崩溃过则先做崩溃恢复(可用 --no-ensure-shutdown 跳过)

阶段 2  获取双方时间线历史,计算分叉点
        └─ 比对源库与目标库的 timeline history,找到最后一次相同的 WAL 位置

阶段 3  扫描目标库分叉点之后的 WAL
        └─ 逐条读出目标库在分叉之后修改过的所有数据块,形成变更块清单

阶段 4  从源库拷贝数据
        └─ 只拷贝清单中的块、分叉后新建的整个文件,以及控制文件状态

阶段 5  对齐完成
        └─ 目标库的 WAL 位置回到源库当前末尾,可启动为备库

几个容易被误解的点:

  • pg_rewind 不重放源库的 WAL。它直接从源库的数据文件里拷贝变更块,源库的 WAL 由备库启动之后通过流复制正常补齐。
  • pg_rewind 也不拷贝配置文件。postgresql.conf、pg_hba.conf、pg_ident.conf 不会被覆盖,目标库保留自己的配置——这通常正是你想要的(端口、监听地址等本机配置不随数据走),但意味着源库上改过的运行参数不会同步过来,需要自行核对。
  • 目标库分叉之后的写入会被丢弃。第 3 阶段扫出的变更块会被源库版本覆盖,这是设计使然。

为什么必须开启 wal_log_hints 或校验和 ​

第 3 阶段的关键是「从 WAL 里找出目标库改过哪些块」。PostgreSQL 默认只把修改行的部分写入 WAL(增量),而 pg_rewind 需要的是「哪些页面被碰过」并且要能精确回退这些页面。wal_log_hints 让每个页面在 checkpoint 后的首次修改写入整页内容,这样:

  1. WAL 中明确出现某页 → 该页被修改过 → 加入变更清单;
  2. 回退时直接用源库的整页覆盖即可,无需理解页内语义。

数据校验和机制下,页面写入 WAL 时天然携带整页(因为校验和计算要求),所以效果等价。两种机制的满足方式:

机制开启方式生效范围
wal_log_hints = on配置文件 + 重启开启之后的变更
数据校验和initdb -k,或初始化后用 pg_checksums 离线开启全库页面均记录 hints

如果两者都没有,pg_rewind 会在启动检查阶段直接报错退出,而不是部分执行。此时唯一的出路是全量重建。

目标库的关闭要求与 --no-ensure-shutdown ​

三种目标库状态对应三种处理:

目标库状态pg_rewind 的行为
已干净关闭直接继续
正在运行自动发出快速关闭并等待,之后继续
崩溃状态(未干净关闭)先对它执行崩溃恢复再关闭,继续(较新版本行为)

如果你明确知道目标库已经被干净关闭(例如自己刚执行过 pg_ctl stop -m fast),可以加 --no-ensure-shutdown 跳过自动处理,避免 pg_rewind 反复启停实例。反之,如果目标库的关闭不是你亲手做的,不要加这个参数——让 pg_rewind 自己确认状态更安全。

自动崩溃恢复还有一个附带问题:恢复需要 WAL。默认 pg_rewind 会从源库下载所需 WAL;如果源库上这些 WAL 已被回收,或者你希望就地取材,用 -c(--restore-target-wal)让 pg_rewind 改从目标库自己的 pg_wal 目录或其 restore_command 归档里获取。

-R 自动生成的恢复配置 ​

-R(--write-recovery-conf)让 pg_rewind 在完成后自动完成「备库化」的两件事:

  1. 在数据目录创建 standby.signal;
  2. 把 primary_conninfo 写入 postgresql.auto.conf,连接串内容取自 --source-server 参数。
bash
# 执行后可以检查生成的配置
ls -l /var/lib/postgresql/18/main/standby.signal
cat /var/lib/postgresql/18/main/postgresql.auto.conf
ini
# postgresql.auto.conf 中追加的内容(示意)
primary_conninfo = 'user=postgres host=10.0.0.11 port=5432 dbname=postgres'

这两样东西正是备库启动的声明文件与连接配置。有了 -R,pg_rewind 之后直接 pg_ctl start 即可作为备库跟随新主库,不需要手工再补配置。这也与 pg_basebackup -R 的行为对称——两者都是「拷贝完成顺手把恢复配置写好」。

如果连接串中包含密码,密码也会被明文写入 postgresql.auto.conf。更规范的做法是在目标库本机的 ~/.pgpass 里放凭据,连接串中不写密码。

与复制槽的配合 ​

备库重新接入后,应当像普通备库一样使用物理复制槽,防止长时间宕机时新主库回收它需要的 WAL。标准做法是在新主库上先建槽,再让备库配置 primary_slot_name:

sql
-- 在新主库上执行:为重新接入的备库创建复制槽
SELECT pg_create_physical_replication_slot('old_primary_slot');
bash
# 在目标库的 postgresql.auto.conf 中补充槽配置
echo "primary_slot_name = 'old_primary_slot'" >> /var/lib/postgresql/18/main/postgresql.auto.conf

顺序上要注意:先建槽、再启动备库。如果备库先启动(不带槽),新主库在槽建立之前的 WAL 仍可能被回收(受 wal_keep_size 保护,保护力度取决于容量)。复制槽机制本身见复制槽。

另一个配合点是 pg_basebackup -C -S 的组合用法:如果选择重建而非 rewind,可以在基础备份的同时创建槽。而 pg_rewind 没有这个能力,槽需要手工补。

WAL 不足与 --restore-target-wal ​

pg_rewind 扫描的是目标库自身分叉之后的 WAL。这意味着目标库的 WAL 必须仍然可用:

情况后果对策
目标库 pg_wal 中 WAL 完整正常执行—
目标库 pg_wal 部分被回收扫描报错退出若归档里还有,用 -c 从归档拉取
目标库 WAL 归档也没有无法执行全量重建
源库 WAL 被回收(自动崩溃恢复需要时)崩溃恢复阶段报错用 -c 改从目标库获取

-c(--restore-target-wal)的本质是给「目标库自动崩溃恢复所需的 WAL」换一个来源:默认从源库下载,-c 之后改读目标库自己的 pg_wal 与 restore_command。这对「源库已经回收了旧 WAL、但目标库归档完好」的场景很实用。

排查 WAL 是否充足,可以直接看目标库 pg_wal 目录里分叉点之后的段文件是否连续(用时间线与文件名比对),或在报错信息里确认是哪个段文件找不到。

与 pg_basebackup 全量重建的对比 ​

pg_rewind 不可用或失败时的替代方案是全量重建。两者各有适用场景:

维度pg_rewindpg_basebackup 全量重建
传输内容分叉点之后的变更块 + 少量文件整个数据目录
耗时通常远短于重建与库大小成正比,大库可能数小时
带宽占用低高(全部数据 + 索引 + 空闲空间)
前提条件wal_log_hints 或校验和;双方时间线历史可交叉;目标库 WAL 完整无特殊前提
对源库的压力小(读取少量页面)大(全库顺序读)
复杂度较高,有多种失败模式低,行为可预期
旧主库分叉期间的写入丢弃丢弃

选择逻辑很简单:前提满足就走 pg_rewind,前提不满足或执行失败就重建。不要为了「不重建」而反复重试 pg_rewind——它的失败通常是结构性的(缺 hints、缺 WAL),重试无济于事。pg_basebackup 的用法见物理基础备份。

上表刻意不给出具体耗时数字:实际差异取决于库大小、分叉时长、变更率与网络带宽,任何脱离环境的数字都没有意义。演算自己环境的最好方式是演练(见实战示例)。

自动化工具中的 pg_rewind ​

手工故障转移的善后规则,在自动化工具里被固化为标准流程:

工具pg_rewind 的使用方式
Patroni节点(通常是前主库)重新加入集群时,若检测到时间线分叉且前提满足,自动调用 pg_rewind 对齐后再以备库身份启动
repmgr提供 repmgr node rejoin 重新接入流程,配合 --force-rewind 标志在需要时执行 pg_rewind
pg_auto_failover对无法安全重新接入的节点选择重建,由其内部状态机决定
云托管服务内部机制不公开,通常以重建为主

这带来一个重要的规划含义:选用 Patroni / repmgr 自动化,就等于选择了 pg_rewind 作为回收路径,因此 wal_log_hints 或数据校验和必须作为集群的基线配置。工具层面一般可以通过配置项显式开启(例如 Patroni 会在需要时自行管理相关参数),但底层 PostgreSQL 参数的物理前提无法被工具绕过。

切换场景下的 pg_rewind ​

pg_rewind 不只服务于紧急故障转移,计划内切换(switchover)的善后同样依赖它。切换时序回顾:等备库追平 → 摘流量 → 提升备库 → 旧主库降级。最后一步「降级」有一个容易被忽略的细节:从摘流量确认追平,到提升完成之间,旧主库可能仍有零星活动(延迟提交的事务、autovacuum 的写动作),因此旧主库的时间线 1 上几乎总会多出少量 WAL——它无法直接以备库身份跟随时间线 2。

标准做法是把切换流程中的「旧主库降级」也固化为 rewind 流程:

bash
# 切换的善后:与故障转移完全相同的流程
pg_ctl stop -D /var/lib/postgresql/18/main -m fast
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres" \
          --progress --write-recovery-conf
pg_ctl start -D /var/lib/postgresql/18/main

计划内场景的优势是全程可控:可以先 --dry-run,分叉点之后的变更块数应当接近零——如果明显大于零,说明摘流量不彻底,要先排查流量泄漏再继续。切换的完整流程见手动故障转移。

时间线冲突与失败排查 ​

pg_rewind 报错大致分几类,先判断类别再对症处理:

前提检查类:目标库或源库未满足 wal_log_hints / 校验和 / wal_level 要求。这类错误在开始拷贝之前就会报出,不会破坏数据。对策是改配置重启后重建(本次已经来不及,pg_rewind 只能用于「hints 一直开着」的集群)。

连接类:--source-server 连不上。常见原因:pg_hba.conf 未放行、密码错误、源库 max_connections 已满、网络不通。先用 psql 用同样的连接串验证连通性,再跑 pg_rewind。

状态类:目标库无法被关闭或无法完成崩溃恢复。检查目标库日志中崩溃恢复是否报错;必要时手工启动一次让它完成恢复,再干净关闭,然后带 --no-ensure-shutdown 重新执行。

WAL 缺失类:分叉点之后的 WAL 在目标库与归档中都找不到。此类错误无法绕过,只能重建。

时间线历史类:双方时间线历史文件对不上、缺失或交叉失败。典型场景是历史过于复杂(多级提升)或 .history 文件丢失。同样只能重建,并且事后要修复归档中 .history 文件的保存。

pg_rewind 失败不会损坏数据,但重试前要停手分析

pg_rewind 在执行拷贝之前会做完整检查,多数失败发生在「未改写任何文件」的阶段。但也存在执行到一半失败的可能(例如网络中断)。此时目标库处于部分对齐状态,不要直接重启它当备库用,应当重新完整执行一次 pg_rewind,或改用全量重建。

演练:把 rewind 纳入故障转移演练 ​

pg_rewind 是「一年用一次、用的时候不能出错」的路径,必须演练:

  1. 搭一主一备,开启 wal_log_hints;
  2. 手工提升备库(制造时间线分叉);
  3. 在旧主库上写入一些数据(模拟分叉期间的写入);
  4. 停旧主库,执行 pg_rewind --dry-run 观察输出;
  5. 正式执行并启动为备库,验证复制追平;
  6. 记录耗时,作为真实 RTO 善后环节的依据。

演练中重点验证两件事:pg_is_in_recovery() 返回 t(确实成了备库),以及新主库上的新写入能出现在备库上(复制链路真正恢复)。

演练闭环的验收清单:

text
□ 前提检查通过(wal_log_hints / checksums 二选一为开启状态)
□ --dry-run 输出分叉点,双方时间线历史交叉成功
□ 分叉点之后的变更块数与预期一致(切换场景应接近 0)
□ 正式执行成功,standby.signal 与 primary_conninfo 已生成
□ 复制槽已创建并配置 primary_slot_name
□ 备库启动后 pg_is_in_recovery() = t
□ pg_stat_replication 中出现该备库且 state = streaming
□ 新主库写入的业务数据在备库可读
□ 全流程耗时已记录(作为 RTO 善后环节的实测值)

实战示例 ​

示例一:检查前提条件 ​

在需要 pg_rewind 之前(最好是在架构建设阶段),确认集群满足前提。

bash
# 方法一:pg_controldata 查看校验和状态
pg_controldata /var/lib/postgresql/18/main | grep -i checksum
text
Data page checksum version:           1
bash
# 方法二:看 wal_log_hints 参数
psql -c "SHOW wal_log_hints;"
text
 wal_log_hints
---------------
 on
(1 row)

Data page checksum version 非 0 表示校验和已开启;或 wal_log_hints 为 on。两者满足其一即可。如果两个都为「关/0」,应尽快补上其中一个(推荐 wal_log_hints,随时可开)。

示例二:dry-run 演练一次 rewind ​

场景:10.0.0.10(旧主,时间线 1)在故障转移中被新主 10.0.0.11(时间线 2)取代,现在回收旧主。

bash
# 1. 停止旧主库
pg_ctl stop -D /var/lib/postgresql/18/main -m fast
text
waiting for server to shut down.... done
server stopped
bash
# 2. 演练
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres" \
          --dry-run --progress
text
connected to server
servers diverged at WAL position 0/3000A5B8 on timeline 1
rewinding from last common checkpoint at 0/3000A000 on timeline 1
checking source server state
Source server timeline history:
1: 0/3000A5B8 - current timeline
Target server timeline history:
1: 0/3000A5B8 - current timeline
target server is in consistent state
reading source file list
reading target file list
reading WAL in target
need to copy 428 blocks (total 3 MB)
Need to copy 13 files (total 8 MB)
Number of removed files:      3
Number of copied files:      12
Number of blocks skipped: 1048576
Number of blocks changed:     428

输出确认了三件事:分叉点在 0/3000A5B8、双方时间线历史能交叉、需要拷贝的量只有几 MB。可以放心执行。

示例三:完整执行并重接入 ​

bash
# 3. 正式执行(-R 顺手生成备库配置)
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres" \
          --progress --write-recovery-conf
text
connected to server
servers diverged at WAL position 0/3000A5B8 on timeline 1
rewinding from last common checkpoint at 0/3000A000 on timeline 1
syncing target data directory
creating backup label and updating control file
syncing target data directory
Done!
bash
# 4. 检查生成的恢复配置
ls -l /var/lib/postgresql/18/main/standby.signal
grep primary_conninfo /var/lib/postgresql/18/main/postgresql.auto.conf

# 5. 在新主库上为它建复制槽
psql -h 10.0.0.11 -U postgres -c \
  "SELECT pg_create_physical_replication_slot('old_primary_slot');"

# 6. 补充槽配置并启动
echo "primary_slot_name = 'old_primary_slot'" \
  >> /var/lib/postgresql/18/main/postgresql.auto.conf
pg_ctl start -D /var/lib/postgresql/18/main

示例四:验证重接入成功 ​

sql
-- 在重接入的备库上执行
SELECT pg_is_in_recovery() AS is_standby,
       pg_last_wal_receive_lsn() AS receive_lsn,
       pg_last_wal_replay_lsn() AS replay_lsn;
text
 is_standby | receive_lsn | replay_lsn
------------+-------------+------------
 t          | 0/3000C100  | 0/3000C100
(1 row)
sql
-- 在新主库上执行:确认备库已出现在复制视图里
SELECT application_name, state, sync_state
FROM pg_stat_replication;
text
 application_name |   state   | sync_state
------------------+-----------+------------
 walreceiver      | streaming | async
(1 row)

pg_is_in_recovery() 返回 t、pg_stat_replication 里出现 streaming 的备库行,说明回收完成。最后再做一次业务级验证:在新主库写一行,确认能在备库读到。

示例五:未开启 wal_log_hints 时的失败 ​

场景:一个从未开启 hints、也没有校验和的集群尝试 pg_rewind。

bash
pg_rewind -D /var/lib/postgresql/18/main \
          --source-server="host=10.0.0.11 port=5432 user=postgres" --dry-run
text
pg_rewind: fatal: source and target cluster are on the same timeline
pg_rewind: error: target server must have wal_log_hints = on, or data checksums enabled, or wal_level >= logical

解读:错误发生在检查阶段,目标库未被改动。这不是可绕过的软限制——分叉期间的变更块已经无法被可靠识别。对策只有全量重建,并在重建完成后立即开启 wal_log_hints,避免下次再栽。

示例六:源库连不上的排查 ​

bash
# 用同样的连接串先验证连通性(排除 pg_rewind 自身的问题)
psql "host=10.0.0.11 port=5432 user=postgres dbname=postgres" -c "SELECT 1;"

# 检查常见故障点:
# 1. pg_hba.conf 是否放行了本机 IP
# 2. 源库 max_connections 是否已满
# 3. 网络与防火墙
grep -n "host.*postgres" /etc/postgresql/18/main/pg_hba.conf

用 psql 验证能把「网络/认证问题」与「pg_rewind 问题」清晰分开:psql 通而 pg_rewind 不通,通常是没有带 dbname 或用户权限不足;两者都不通,就是连接层的问题。

示例七:旧主库分叉期间写入量的快速评估 ​

场景:故障转移善后前,需要回答「旧主库分叉之后写了多少数据」,决定是否需要先抢救。分叉点 LSN 来自时间线历史文件(见时间线与时间线历史),假设为 0/3000A5B8。

bash
# 旧主库已停机,直接看 pg_wal 中时间线 1 的最后一段的结束位置
ls -l /var/lib/postgresql/18/main/pg_wal/ | grep ^00000001 | tail -2
text
-rw------- 1 postgres postgres 16777216 Sep 30 10:05 0000000100000000000000A5.partial
bash
# 或者用 pg_controldata 看停机前的检查点位置
pg_controldata /var/lib/postgresql/18/main | grep -i "checkpoint location"
text
Latest checkpoint location:           0/3000B2C0
sql
-- 在任意可用实例上计算分叉点与停机位置的字节差
SELECT pg_wal_lsn_diff('0/3000B2C0'::pg_lsn, '0/3000A5B8'::pg_lsn) AS diverged_bytes;
text
 diverged_bytes
----------------
           3528
(1 row)

解读:分叉之后旧主库只产生了约 3.5KB 的 WAL——通常是一两个收尾事务或后台活动。此时直接 rewind 丢弃是安全的;如果差值是 GB 级,就要先明确这些写入的业务含义。这个 1 分钟的评估,能避免「rewind 完才发现丢数据」的被动局面。

注意事项 ​

pg_rewind 会丢弃目标库分叉之后的全部写入。 执行前必须确认旧主库上没有需要抢救的业务数据。若有,先把数据比对导出,再做 rewind。

前提必须在故障发生前就准备好。 wal_log_hints 事后开启只影响之后的变更;校验和需要停机开启。故障发生时才想起这两个开关,意味着本次只能全量重建。

目标库必须停止运行。 不要尝试对运行中的实例执行 pg_rewind(工具会尝试自动关闭它)。也不要在 rewind 过程中启动目标库。

配置文件不会被拷贝。 postgresql.conf、pg_hba.conf 等保留目标库自己的版本。重接入后要核对这些配置与新主库的参数是否仍然匹配(例如 hot_standby、primary_conninfo)。

-R 会把连接信息明文写入 postgresql.auto.conf。 若连接串带密码,注意该文件的权限控制,或改用 ~/.pgpass。

pg_rewind 成功不等于集群恢复。 重接入只是复制链路恢复,监控、归档、备份等保护措施要立即补齐——新主库此刻可能还没有被备份体系覆盖。

失败后的目标库不要直接启动。 执行到一半失败的 rewind 留下的是部分对齐的目录,直接启动可能产生更混乱的状态。完整重跑一次 rewind,或改用重建。

时间线历史文件是 pg_rewind 的命脉。 归档目录必须妥善保存 .history 文件;归档脚本如果只归档 WAL 段而漏掉 .history,pg_rewind 的时间线交叉会失败。

不要把 pg_rewind 当成日常同步工具。 它是为「故障转移后的单次对齐」设计的,不是持续同步机制。日常同步就是流复制本身。

两个节点都「分叉过」的复杂历史要格外小心。 多次提升、多级分叉的集群,时间线历史交叉可能选出非预期分叉点。此类集群的善后建议直接重建,把时间线历史「洗干净」。

切换(switchover)后的旧主库同样需要 pg_rewind。 即使是计划内切换,旧主库降级为备库也涉及时间线回退(若旧主库在切换期间有过写入),流程与故障转移完全相同。

最佳实践 ​

  1. 在架构建设阶段就开启 wal_log_hints = on。 这是成本最低、收益最大的一个决定:代价是 checkpoint 后首改页面的轻微 WAL 放大,换来的是故障后快速回收旧主库的能力。

  2. 把 pg_rewind 写进故障转移 Runbook 的善后章节。 明确命令、连接串、预期输出,与手工提升页面的流程衔接。

  3. 每次正式执行前先跑 --dry-run。 它能在不改写任何文件的情况下暴露前提缺失、时间线交叉失败、WAL 缺失等所有结构性问题。

  4. 用 -R 自动生成恢复配置,减少手工步骤。 手工创建 standby.signal 和 primary_conninfo 容易漏掉或写错,让工具代劳更可靠。

  5. 重接入后立即补齐复制槽。 先在新主库建槽、配置 primary_slot_name,再启动备库,避免无槽期间 WAL 被回收。

  6. 重接入完成后立即验证三个信号。 pg_is_in_recovery() 为 t、pg_stat_replication 中出现该备库、新主库写入能在备库读到。三个都通过才算善后完成。

  7. 保留 ~/.pgpass 管理凭据。 避免 -R 把密码明文写入 postgresql.auto.conf。

  8. 把「旧主库分叉期间的写入是否需要保留」作为决策点写进流程。 若需要保留,先做数据比对导出,再执行 rewind;这个问题在执行前回答,比执行后补救容易得多。

  9. 每季度演练一次完整的「提升 → rewind → 重接入」闭环。 演练同时验证 pg_rewind 与应用重连两条路径,记录实际耗时。

  10. 自动化工具集群(Patroni / repmgr)要理解工具对 pg_rewind 的依赖。 不要在工具之外手工对节点做时间线操作,避免状态机与实际时间线脱节。

  11. 归档脚本必须覆盖 .history 文件。 用通用的 archive_command(按文件名归档)而不是只按 24 位数字文件名过滤的脚本,确保时间线历史文件进入归档。

  12. 重建与 rewind 的选择规则要事先写死。 「前提满足就 rewind、失败一次就重建」——不要在现场纠结,重试第三遍 pg_rewind 基本没有意义。

下一节

继续学习:时间线与时间线历史

参考链接 ​