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 write | checkpoint 后页面首次修改时把整页写入 WAL,是 hints 机制的基础 |
wal_log_hints | 参数 | 把每个页面首改时的整页内容记入 WAL,pg_rewind 的前提之一 |
| 数据校验和 | data checksums | initdb -k 开启,效果等价于 wal_log_hints 的前提 |
| 干净关闭 | clean shutdown | 目标库在 rewind 前必须处于的状态,或由 pg_rewind 自动达成 |
| 复制槽 | replication slot | 重接入后防止 WAL 被回收的机制 |
目标库与源库
pg_rewind 是一个单向的数据修复工具:数据只从源库流向目标库,目标库上分叉之后产生的所有变更会被丢弃。方向不能搞反——如果你想保留旧主库上的数据,那么先别运行 pg_rewind,而应先把那部分数据比对导出。
两点重要推论:
- 运行中的源库无需停机。源库通常就是正在对外服务的新主库,pg_rewind 通过连接协议在线读取它,不要求源库停止。
- 目标库必须先停止(或由 pg_rewind 自动处理关闭)。目标库的数据文件会被直接改写,带着运行中的 PostgreSQL 修改文件是危险的。
时间线分叉与分叉点
时间线分叉是 pg_rewind 存在的根本原因。回顾一下故障转移的时间轴:
时间线 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 的明确前提。
# 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)。后者是生产环境的主要用法。
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 | 强制提示输入密码 |
典型用法:从新主库回收旧主库
# 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 是首选:
# ~/.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 读取。如果必须在连接串里写密码(例如容器化环境),执行后应核对该文件的读取权限。
演练先行
# dry-run 会打印将要拷贝的文件列表与块数,但不落盘
pg_rewind -D /var/lib/postgresql/18/main \
--source-server="host=10.0.0.11 port=5432 user=postgres" \
--dry-runservers 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 的一次运行分为几个明确的阶段,理解这些阶段有助于在出错时判断卡在哪一步。
阶段 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 后的首次修改写入整页内容,这样:
- WAL 中明确出现某页 → 该页被修改过 → 加入变更清单;
- 回退时直接用源库的整页覆盖即可,无需理解页内语义。
数据校验和机制下,页面写入 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 在完成后自动完成「备库化」的两件事:
- 在数据目录创建
standby.signal; - 把
primary_conninfo写入postgresql.auto.conf,连接串内容取自--source-server参数。
# 执行后可以检查生成的配置
ls -l /var/lib/postgresql/18/main/standby.signal
cat /var/lib/postgresql/18/main/postgresql.auto.conf# 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:
-- 在新主库上执行:为重新接入的备库创建复制槽
SELECT pg_create_physical_replication_slot('old_primary_slot');# 在目标库的 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_rewind | pg_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 流程:
# 切换的善后:与故障转移完全相同的流程
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 是「一年用一次、用的时候不能出错」的路径,必须演练:
- 搭一主一备,开启
wal_log_hints; - 手工提升备库(制造时间线分叉);
- 在旧主库上写入一些数据(模拟分叉期间的写入);
- 停旧主库,执行
pg_rewind --dry-run观察输出; - 正式执行并启动为备库,验证复制追平;
- 记录耗时,作为真实 RTO 善后环节的依据。
演练中重点验证两件事:pg_is_in_recovery() 返回 t(确实成了备库),以及新主库上的新写入能出现在备库上(复制链路真正恢复)。
演练闭环的验收清单:
□ 前提检查通过(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 之前(最好是在架构建设阶段),确认集群满足前提。
# 方法一:pg_controldata 查看校验和状态
pg_controldata /var/lib/postgresql/18/main | grep -i checksumData page checksum version: 1# 方法二:看 wal_log_hints 参数
psql -c "SHOW wal_log_hints;" 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)取代,现在回收旧主。
# 1. 停止旧主库
pg_ctl stop -D /var/lib/postgresql/18/main -m fastwaiting for server to shut down.... done
server stopped# 2. 演练
pg_rewind -D /var/lib/postgresql/18/main \
--source-server="host=10.0.0.11 port=5432 user=postgres" \
--dry-run --progressconnected 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。可以放心执行。
示例三:完整执行并重接入
# 3. 正式执行(-R 顺手生成备库配置)
pg_rewind -D /var/lib/postgresql/18/main \
--source-server="host=10.0.0.11 port=5432 user=postgres" \
--progress --write-recovery-confconnected 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!# 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示例四:验证重接入成功
-- 在重接入的备库上执行
SELECT pg_is_in_recovery() AS is_standby,
pg_last_wal_receive_lsn() AS receive_lsn,
pg_last_wal_replay_lsn() AS replay_lsn; is_standby | receive_lsn | replay_lsn
------------+-------------+------------
t | 0/3000C100 | 0/3000C100
(1 row)-- 在新主库上执行:确认备库已出现在复制视图里
SELECT application_name, state, sync_state
FROM pg_stat_replication; 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。
pg_rewind -D /var/lib/postgresql/18/main \
--source-server="host=10.0.0.11 port=5432 user=postgres" --dry-runpg_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,避免下次再栽。
示例六:源库连不上的排查
# 用同样的连接串先验证连通性(排除 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。
# 旧主库已停机,直接看 pg_wal 中时间线 1 的最后一段的结束位置
ls -l /var/lib/postgresql/18/main/pg_wal/ | grep ^00000001 | tail -2-rw------- 1 postgres postgres 16777216 Sep 30 10:05 0000000100000000000000A5.partial# 或者用 pg_controldata 看停机前的检查点位置
pg_controldata /var/lib/postgresql/18/main | grep -i "checkpoint location"Latest checkpoint location: 0/3000B2C0-- 在任意可用实例上计算分叉点与停机位置的字节差
SELECT pg_wal_lsn_diff('0/3000B2C0'::pg_lsn, '0/3000A5B8'::pg_lsn) AS diverged_bytes; 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。 即使是计划内切换,旧主库降级为备库也涉及时间线回退(若旧主库在切换期间有过写入),流程与故障转移完全相同。
最佳实践
在架构建设阶段就开启
wal_log_hints = on。 这是成本最低、收益最大的一个决定:代价是 checkpoint 后首改页面的轻微 WAL 放大,换来的是故障后快速回收旧主库的能力。把 pg_rewind 写进故障转移 Runbook 的善后章节。 明确命令、连接串、预期输出,与手工提升页面的流程衔接。
每次正式执行前先跑
--dry-run。 它能在不改写任何文件的情况下暴露前提缺失、时间线交叉失败、WAL 缺失等所有结构性问题。用
-R自动生成恢复配置,减少手工步骤。 手工创建standby.signal和primary_conninfo容易漏掉或写错,让工具代劳更可靠。重接入后立即补齐复制槽。 先在新主库建槽、配置
primary_slot_name,再启动备库,避免无槽期间 WAL 被回收。重接入完成后立即验证三个信号。
pg_is_in_recovery()为t、pg_stat_replication中出现该备库、新主库写入能在备库读到。三个都通过才算善后完成。保留
~/.pgpass管理凭据。 避免-R把密码明文写入postgresql.auto.conf。把「旧主库分叉期间的写入是否需要保留」作为决策点写进流程。 若需要保留,先做数据比对导出,再执行 rewind;这个问题在执行前回答,比执行后补救容易得多。
每季度演练一次完整的「提升 → rewind → 重接入」闭环。 演练同时验证 pg_rewind 与应用重连两条路径,记录实际耗时。
自动化工具集群(Patroni / repmgr)要理解工具对 pg_rewind 的依赖。 不要在工具之外手工对节点做时间线操作,避免状态机与实际时间线脱节。
归档脚本必须覆盖
.history文件。 用通用的archive_command(按文件名归档)而不是只按 24 位数字文件名过滤的脚本,确保时间线历史文件进入归档。重建与 rewind 的选择规则要事先写死。 「前提满足就 rewind、失败一次就重建」——不要在现场纠结,重试第三遍 pg_rewind 基本没有意义。
下一节
继续学习:时间线与时间线历史
参考链接
- PostgreSQL 官方文档:pg_rewind
- PostgreSQL 官方文档:Write Ahead Log — Configuration Parameters(wal_log_hints)
- PostgreSQL 官方文档:pg_basebackup
- PostgreSQL 官方文档:pg_controldata
- PostgreSQL 官方文档:pg_ctl
- PostgreSQL 官方文档:Log-Shipping Standby Servers
- PostgreSQL 官方文档:Recovery Configuration
- PostgreSQL 官方文档:High Availability, Load Balancing, and Replication
- Patroni 官方文档
- repmgr 官方文档