32.3 隔离恢复策略
隔离恢复的目标不是“找一台空机器”,而是让恢复候选同时满足:
cannot overwrite source evidence
cannot join source HA control plane
cannot receive application traffic
cannot emit unintended external effects
cannot contaminate source WAL archive
can still read the exact backup/WAL it needs
can be identified, validated, stopped and deleted exactly这六条不是同一个开关。listen_addresses='' 能关闭 TCP 入站,不会自动阻止逻辑订阅、
HTTP 扩展或脚本向外连接;custom PGDATA 不接入 Patroni,也不会自动得到只读 repository
凭据。隔离要逐层证明。
32.3.1 不覆盖仍可取证的原集群
原集群同时是现场、差异源和回退资产
即使原数据已经错误,它仍保存:
- 错误之后的合法提交;
- 当前业务 key、version 与外部事件身份;
- 未归档 WAL 和最近 commit 线索;
- session、job、slot、subscription 与路由状态;
- 与恢复候选进行差异比较的基准;
- 如果目标判断错误,重新选择候选所需的证据。
因此默认拓扑应是:
live source --------------------------> preserved
| |
+--> backup/WAL --> candidate A +--> good-after audit
\--> candidate B +--> current-state manifest而不是:
live source PGDATA --overwrite--> guessed targetPostgreSQL 官方恢复流程也建议在空间允许时先复制整个 cluster data directory 与
tablespace;至少保存可能尚未归档的 pg_wal
(Recovering Using a Continuous Archive Backup)。
对仍在线的 source,不应把这理解成随意复制活动 PGDATA;应通过受支持的备份、存储快照
或停机证据流程完成。
先决定 source 的运行状态
三种常见状态:
| source 状态 | 何时选择 | 必须控制 |
|---|---|---|
| 继续服务 | 影响局部,可围住错误对象 | bad writer、受影响 key、事后写审计 |
| 只读/全局写围栏 | 影响广,good-after 难追踪 | 所有入口、后台作业、长事务 |
| 停止并保全 | 完整性未知或仍快速恶化 | WAL、内存外证据、启动权限 |
“source 继续服务”不能写成“什么都不做”。至少要冻结错误 job/service identity,记录围栏 时间,保存合法写 manifest。反之,也不要因为要做 PITR 就自动停全站;停写是业务可用性 决策,必须由影响范围与对账能力支撑。
保存事实,不保存秘密扩散
事故证据包可以记录:
cluster and member identity
system identifier relation or protected digest
timeline and LSN
backup label and archive range
effective recovery parameters
object/row manifests and business-key digests
UTC action timeline
source-file and evidence hashes不应把这些内容直接放进公开工单:
SCRAM verifiers
TLS private keys
cloud/repository credentials
raw connection URIs
unredacted business rows
unbounded query text or bind values私有证据目录需要最小权限、保留期限与访问日志;公开摘要只保留足以复核结论的环境轮廓、 计数、状态和散列。
任何覆盖动作都要单独授权
managed PGDATA restore 至少会:
- 停止或绕开 HA 生命周期;
- 替换当前物理数据;
- 改变 timeline 与可继续恢复的路径;
- 使旧节点与 DCS/其他成员产生身份冲突风险;
- 让回到当前状态依赖另一条恢复路径。
它是独立的高风险状态迁移,不应因为 side restore 验证通过就自动获批。本章实验从不 执行 managed restore,也不把 side candidate 注册为 Patroni member。
32.3.2 选择备份、WAL 与目标环境
备份必须早于目标并能走到目标
对候选 backup $B_i$,最低条件是:
$$ \operatorname{stop}(B_i) < T_{\text{target}} $$
并且从 backup 一致点到 target 的所有必需 WAL 与 timeline history 都可读。选择最近的 合格 backup 通常减少重放量,但不能只按 label 字符串或“最新”按钮决定:
stanza identity matches source
database system lineage matches
PostgreSQL major and block/checksum properties compatible
backup status has no error
full/diff/incr dependencies all retained
backup stop point is before target
archive min/max and history cover target timeline
tablespace mapping is available
repository key and credentials are usable本章正式实验在 fixture base 创建后新做一份 full backup,再提交 safe、damage 与 post-target 三笔事务;这样既测试 backup 后 WAL 重放,也避免选到目标之后才完成的 backup。
“archive max 大于目标”只是必要条件
WAL segment 名的范围比较可以证明仓库至少走到某个 segment,却不能单独证明:
- 中间每一个 segment 都存在;
- 对象内容未损坏;
- 所需 timeline history 存在;
- repository key 可用;
- restore command 权限和网络可用;
- 目标记录确实在宣称的 segment 中。
所以先运行 repository check,再实际恢复。测试环境可以主动 pg_switch_wal() 缩短等待,
生产不能把频繁 switch 当作归档性能修复;archive_timeout 太短会生成大量未填满但仍为
完整尺寸的 segment。
固定 exact backup,而不是让工具临场猜
恢复票据应保存:
stanza pg-test
repo 1
backup label 20260730-030910F
backup type full
backup stop timestamp + LSN + WAL
target XID/time/LSN/name
inclusive true/false
timeline current/latest/N + rationale
target action pause/promote/shutdown运行 pig pitr 时使用 -b/--set 指定这份 backup。若 plan 解析出的 effective label、
target、timeline 或 data directory 与票据不一致,应在 restore 前阻断。
目标环境要匹配物理要求
物理恢复不是把 data directory 交给任意 PostgreSQL:
- server major 必须与物理格式匹配;
- 所需 extension shared libraries、locale/collation provider 要可用;
- data checksums、page/block/WAL segment 属性要被理解;
- tablespace 目标必须存在、映射正确且容量足够;
- 恢复关键参数不能低于源端需要;
- OS 用户、owner、mode 与文件系统语义要正确。
恢复关键参数常包括:
max_connections
max_worker_processes
max_wal_senders
max_prepared_transactions
max_locks_per_transaction如果恢复主机当前配置比 backup 要求低,recovery 可能拒绝启动。正式实验读取 source effective settings,再显式传给隔离 postmaster;这不是把整个 source 配置无审查复制过来。
先算空间,再决定保留策略
至少预算:
$$ \text{space} \ge \text{restored PGDATA}
- \text{tablespaces}
- \text{temporary WAL}
- \text{export/intermediate data}
- \text{safety margin} $$
若同时恢复 inclusive 与 exclusive,可顺序复用主机端口和空间,但应保留各自 manifest 与日志;不要同时启动两个继承相同 identity、port 和外部配置的副本。
空间不足时,禁止以 expire 旧 backup 或删除 archive WAL 作为脚本的隐式副作用。本章
实验会留下新 full backup,因为清理 repository 不在授权范围内。
32.3.3 控制网络、凭据和外部副作用
六层隔离清单
| 层 | 本章正式实验 | 真实生产演练还要考虑 |
|---|---|---|
| 文件 | 随机 marker 下的 custom -D | 独立卷、tablespace、快照权限 |
| 进程 | 手工 pg_ctl,一次只启一个 | cgroup/container/systemd identity |
| 入站 | listen_addresses='',mode-0700 Unix socket | host firewall/security group |
| 身份 | private HBA,仅本机 postgres peer | secrets replacement、least privilege |
| 控制面 | 不加入 Patroni/DCS/HAProxy/PgBouncer | DNS/VIP/controller admission |
| 出站/副作用 | fixture 没有 dispatcher,archive off | egress deny、subscription/job/webhook 隔离 |
最后一行最容易被漏掉。listen_addresses='' 只是不接受 TCP 连接,不会阻止数据库或宿主
进程主动向外连接。真正的取证环境应在网络层默认 deny egress,再为只读 backup/WAL
读取开精确 allowlist。
恢复出来的凭据仍然有效
物理 backup 包含当时的:
- database roles 与 password verifier;
pg_hba.conf、证书引用与部分服务配置;- extension、job、subscription 和 FDW metadata;
- application-owned secret(若错误地存进业务表)。
因此不能让恢复实例监听原端口或加载原 HBA。本章在 command line 覆盖:
listen_addresses=''
unix_socket_directories=<private-root>/socket
unix_socket_permissions=0700
hba_file=<private-root>/pg_hba.restore.conf
ssl=off
shared_preload_libraries=''
primary_conninfo=''
primary_slot_name=''
archive_mode=offprivate HBA:
local all postgres peer
local all all reject这些设置适合本章沙箱,不是所有生产恢复的通用模板。例如需要业务验证账号时,应新建 一次性、最小权限的验证入口,而不是重新开放原应用角色。
archive_mode=off 防止污染 source 历史
隔离 candidate promote 后会产生新 timeline 和新 WAL。若它继承
archive_mode=always/on 与 source repository 写凭据,实验分支可能写入共享 archive,
增加 timeline 选择歧义,甚至与 retention 发生交互。
本章通过 pgBackRest restore 参数和 postmaster command line 双重确认
archive_mode=off。这不影响 restore_command 在 recovery 期间读取所需 WAL;
它只禁止候选成为新的 archive producer。
更严格的设计还应让恢复凭据 repository read-only。因为具备 restore 权限的 host 往往 也配置了 backup 写权限,单靠操作人员承诺不是权限隔离。
控制数据库内的自动执行器
恢复实例中可能存在:
pg_cron / pgAgent jobs
logical subscriptions
background worker extensions
LISTEN/NOTIFY consumers
outbox relay
trigger-driven HTTP calls
foreign data wrappers
application sidecars and local service units应在启动前列出它们,并从三层阻断:
- 网络层:默认拒绝出站;
- 进程层:不启动 application/relay/agent,按评审禁用 background workers;
- 数据层:把 outbox/queue 置隔离状态,使用幂等键和 sandbox endpoint。
本章只证明 synthetic fixture 的 external_dispatch_count=0,没有宣称通用 egress 隔离
已经通过。读者把实验迁移到真实系统时,必须补这一门。
Pigsty 的 managed 与 side restore 边界
当前 pig pitr 有两种生命周期:
managed data directory
may stop Patroni
ensures PostgreSQL is stopped
restores managed PGDATA
may start PostgreSQL
leaves Patroni stopped
does not rejoin HA or switch traffic
custom -D side restore
requires pre-created postgres-owned directory
requires --no-restart
does not stop Patroni
does not manage default PostgreSQL service
operator starts isolated postmaster manually完整语义见
pig pitr。这里最重要的不是记选项,而是从 plan
核对实际 boundary。路径是否为 side restore 由有效 managed PGDATA 与解析后的路径决定,
不是简单看字符串里有没有 /pg/data。
正式实验先执行:
pig pitr \
-s pg-test -r 1 \
-b "$BACKUP_LABEL" \
--xid "$DAMAGE_XID" \
--target-action=promote \
--target-timeline=current \
-D "$CANDIDATE/data" \
--no-restart \
--plan \
-o json \
-- \
--archive-mode=offplan 必须明确:
boundary pitr:side-restore
data directory exact candidate path
service lifecycle not-managed
backup set exact reviewed label
target exact source-audited XID
timeline current
confirmation required结构化执行不会交互询问;只有审批完成后才使用 --yes。不要把 --yes 写进没有 guard、
没有 exact target、会指向 managed PGDATA 的通用脚本。
隔离验收
启动候选后,同时从 OS 与 SQL 两侧检查:
OS:
no TCP listener on candidate port
private socket exists and mode is 0700
postmaster.pid belongs to exact candidate
managed Patroni process remains unchanged
SQL:
cluster_name identifies candidate
listen_addresses is empty
archive_mode is off
ssl is off for private socket-only lab
shared_preload_libraries is empty in this fixture
pg_is_in_recovery() eventually becomes false停止时只允许:
pg_ctl -D <exact-marker-root>/data stop
verify PID and socket absent
delete <exact-marker-root>宽泛 pkill postgres、按端口杀进程、删除未解析变量路径都不属于可接受清理。
上一节:恢复目标与时间线 · 返回本章目录 · 下一节:执行恢复并观察进度 · 查看全书目录 · 查看索引中心