跳至内容
33.5 旧主重加入与集群重建

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/A2B1/B2 做业务 merge。target 分叉事实若需要保留,必须在 rewind 前从隔离实例提取。

前提清单

前提原因验证
同 system identifier必须来自同一 cluster ancestrypg_controldata / control function
timeline 有共同祖先才能确定分叉点history files
target 已停止文件不能继续变化service/PID/lock evidence
target 启用 checksums 或 wal_log_hints识别修改页面所需init/control/config
full_page_writes=onrewind 安全前提source/target config evidence
source 一致且可信source 是要保留的权威历史authority decision
所需 WAL 可取得target 启动后要从共同 checkpoint replaysource/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 \
  --progress

target 会被改写,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 后只看到 basenew-primaryafter-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 authorization

Patroni 管理 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               130

30 个 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 · 查看全书目录 · 查看索引中心

最后更新于