33.5 旧主重加入与集群重建
新主稳定后,旧主不能直接“启动看看”。它可能仍停在共同祖先,也可能已经在旧 timeline 产生分叉写。归队的目标不是让进程起来,而是构造一个只读 follower:
same system identifier
same accepted history
standby.signal / recovery configuration correct
receiver streaming from accepted primary
replay reaches verification point
old divergent facts absent
no stale client route or external side effect若旧主在 promotion 前已经干净停止、没有产生分叉,它可能直接沿 timeline history 继续 recovery;若已经分叉,则要 rewind 或全量重建。
33.5.1 pg_rewind 的前提、失败与验证
pg_rewind 做什么
pg_rewind 将 target PGDATA 同步到 source 所在的权威分支。它根据 timeline history
找到共同祖先,从 target 读取分叉之后发生变化的数据块,并从 source 复制所需页面;
新文件、配置文件和 WAL 等按文件复制。它通常比全量 base backup 少复制很多数据。
典型关系:
target old primary: F -> A1 -> A2
source new primary: F -> B1 -> B2
pg_rewind(target=A, source=B)
-> discard A after F
-> copy B-required changes
-> configure A to recover/follow B它不会把 A1/A2 与 B1/B2 做业务 merge。target 分叉事实若需要保留,必须在 rewind
前从隔离实例提取。
前提清单
| 前提 | 原因 | 验证 |
|---|---|---|
| 同 system identifier | 必须来自同一 cluster ancestry | pg_controldata / control function |
| timeline 有共同祖先 | 才能确定分叉点 | history files |
| target 已停止 | 文件不能继续变化 | service/PID/lock evidence |
target 启用 checksums 或 wal_log_hints | 识别修改页面所需 | init/control/config |
full_page_writes=on | rewind 安全前提 | source/target config evidence |
| source 一致且可信 | source 是要保留的权威历史 | authority decision |
| 所需 WAL 可取得 | target 启动后要从共同 checkpoint replay | source/archive coverage |
| target 可写、空间充足 | rewind 会修改整个目录 | filesystem preflight |
| recovery config 正确 | 启动后必须跟随 source | -R 与配置复核 |
PostgreSQL 18 默认 initdb 启用 data checksums,但不能把“默认”当现场事实。老集群、升级 集群或定制 initdb 可能不同。
source 与 target 方向不能写反
pg_rewind \
--target-pgdata=/path/to/old-primary \
--source-server='host=<new-primary> dbname=postgres user=<rewind-role>' \
--write-recovery-conf \
--progresstarget 会被改写,source 被读取。生产命令应从 inventory/incident record 生成,并在执行 前打印 secret-free plan:
target member and PGDATA
source member and system identifier
target/source timeline
common ancestor
target clean-stop evidence
required WAL source
recovery destination
expected post-state
rollback = fresh rebuild, not "undo rewind"不要把带密码的连接串写入工单或证据;使用受控 service/password file 或短期凭据。
clean shutdown 与失败语义
pg_rewind 要求 target cleanly shut down。默认情况下,若 target 非干净停止,工具会
尝试单用户模式完成 crash recovery;--no-ensure-shutdown 可让它直接报错。生产流程
更适合先显式处理 crash recovery、保留日志与控制信息,再决定是否允许工具自动动作。
更重要的是:PostgreSQL 官方文档警告,rewind 中途失败后 target 很可能不再处于可恢复 状态,推荐取得 fresh backup。不要:
retry start target as primary
reverse source/target and "rewind back"
rsync a few reported files
delete control/history files to force startup保留失败日志、目录 manifest 与 source 身份,然后把 target 视为待重建。
配置与 WAL 复核
rewind 会从 source 复制配置文件,target 的本机差异可能被覆盖:
port、socket、listen address;- SSL key/certificate symlink;
- tablespace path;
primary_conninfo与 slot;- archive/restore command;
- include 文件和本机路径。
使用 -R/--write-recovery-conf 会创建 standby.signal 并写 recovery connection,但仍要
检查目标端本机覆盖。缺失从共同 checkpoint 到 source 当前状态的 WAL 时,target 启动
仍会失败;应确保 source pg_wal、archive 或 --restore-target-wal 路径满足需求。
验收不是“pg_rewind done”
SELECT
pg_is_in_recovery(),
(pg_control_system()).system_identifier,
(pg_control_checkpoint()).timeline_id,
pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn();
SELECT
status,
sender_host,
sender_port,
written_lsn,
flushed_lsn,
latest_end_lsn
FROM pg_stat_wal_receiver;还要验证:
accepted new-primary marker present
known old-primary divergent marker absent
receiver status = streaming
replay reaches post-rewind verification LSN
Patroni registers member as replica, not primary
client write health does not route to it本章一次性实验的 A 在 rewind 后只看到 base、new-primary、after-divergence,
看不到 old-primary-divergent,并以 streaming standby 启动。
33.5.2 从备份或新基础备份重建
什么时候跳过 rewind
任一项成立时优先全量重建:
- system identifier 或共同祖先不可信;
- checksums/
wal_log_hints前提不满足; - 所需 WAL 缺失且无法恢复;
- target 存储疑似损坏;
- rewind 中途失败;
- target 文件权限、tablespace 或 symlink 状态复杂且不可验证;
- 数据规模不大,全量路径更简单、更可预测;
- 合规要求使用已验证 backup lineage。
优化目标不是复制字节最少,而是总风险最低:
$$ Cost = copy_time
- uncertainty
- validation
- rollback_risk
- operator_complexity $$
fresh base backup
原生流程示意:
pg_basebackup \
--host=<accepted-primary> \
--username=<replication-role> \
--pgdata=<empty-authorized-target> \
--wal-method=stream \
--write-recovery-conf \
--checkpoint=fast必须确认 target 是精确授权的空目录。不要对变量为空、符号链接或宽泛 glob 执行删除; managed member 的数据目录应由 Patroni/Pigsty reinit 流程管理。
Pig/Patroni 路径:
pig pt reinit <replica> --plan
pig pt reinit <replica> --wait当前 Pig 帮助明确警告:reinit 会删除目标成员数据并从 leader 重建。它是破坏性动作, 必须核对:
target is replica, never current leader
target member identity and PGDATA
accepted source leader
backup/basebackup method and bandwidth
tablespaces and encryption keys
WAL retention during copy
failure cleanup
post-rebuild validation本章没有执行 managed reinit,因为“编写书籍”并不等于授权删除托管副本。实验用 exact
临时目录 C 真实运行 pg_basebackup -R,证明机制后立即停止并删除。
从已有 backup 重建
大集群从对象存储/pgBackRest backup restore,可能比从 primary 传全量 base backup 更 少占生产网络,并提供明确 lineage。选择时比较:
| 来源 | 优点 | 代价 |
|---|---|---|
| current primary basebackup | 最新、路径直接 | 消耗 primary I/O/网络,长 copy 要保 WAL |
| replica basebackup | 减少 primary 压力 | source 必须合格且允许 |
| pgBackRest backup + archive | 可复用已验证备份 | 需要 restore + WAL catch-up |
| volume snapshot | 快 | 一致性、加密、跨主机与 lineage 要证明 |
无论来源,最终都必须 streaming 到 accepted primary,并验证业务 marker,而不只看目录 复制完成。
估算恢复窗口
粗略下界:
$$ T_{\text{rebuild}} \ge \frac{bytes\ to\ transfer}{effective\ throughput}
- WAL\ catchup
- validation $$
若 copy 期间 primary 持续产生 WAL:
$$ WAL_{\text{retained}} \ge write_rate \times T_{\text{copy}} + safety\ margin $$
还要计算 replication slot 导致的磁盘增长,避免“为了重建副本”把 primary pg_wal
撑满。
33.5.3 复制槽、端点和客户端状态清理
复制槽不是自动清理垃圾
成员失联期间,physical slot 可能继续保留 WAL。重建前后检查:
SELECT
slot_name,
slot_type,
active,
active_pid,
restart_lsn,
wal_status,
safe_wal_size,
invalidation_reason
FROM pg_replication_slots
ORDER BY slot_name;不要仅因 slot active=false 就删除。它可能正为待恢复成员、logical subscriber 或
备份流程保留 WAL。先映射:
slot -> owner/member/subscriber
required restart LSN
retained bytes
rebuild plan
drop authorizationPatroni 管理 permanent/member slots 时,还要核对 DCS 成员与 slot policy,避免手工 删除后被重建或导致 WAL gap。
服务端点要清掉陈旧状态
重建成功后:
- Patroni REST role 为 replica;
- HAProxy write health 不接受它;
- read service 是否允许加入由 lag/业务策略决定;
- PgBouncer server connection 已重建;
- VIP/DNS owner 与 TTL 正确;
- direct-IP 配置与运维脚本没有指向旧角色;
- application
target_session_attrs=read-write等约束生效。
“节点回到集群”与“可以承载读流量”不是同一门。刚重建副本可能仍在 catch-up、缓存 全冷、统计未热、备份未覆盖。
客户端 unknown outcome
故障窗口内:
acknowledged 客户端收到成功;必须在新主存在一次
rejected 明确未提交;可按协议重试
unknown 连接中断,提交结果未知;必须查询幂等身份对账:
SELECT token, count(*)
FROM app.idempotency_record
WHERE token = ANY (:unknown_tokens)
GROUP BY token;每个 unknown 应归类为 absent 或 committed once。没有 idempotency identity 时,无法用 数据库技术准确判断“同一业务动作是否可重试”,必须升级业务 owner。
本章实验:
attempts 160
acknowledged 130
unknown 30
acknowledged missing 0
duplicates 0
unreconciled unknown 0
persisted rows 13030 个 unknown 最终均 absent;如果其中有 committed once,也仍可正确归类。验证器关心 “全部可对账”,不要求网络故障时 unknown 必须为零。
重建完成清单
member:
patroni_role: replica
sql_recovery: true
system_identifier: matches
timeline_history: accepted
receiver_status: streaming
replay_at_verification_lsn: true
data:
accepted_markers_present: true
divergent_markers_absent: true
service:
write_route: excluded
read_route: policy-dependent
client:
unknown_outcomes_unreconciled: 0
operations:
slots: reconciled
archive: healthy
monitoring: healthy
backup: scheduled-and-tested上一节:DCS 故障的安全处理 · 返回本章目录 · 下一节:切换与重建 runbook · 查看全书目录 · 查看索引中心