21.2 物理备份与 WAL 连续性
物理 PITR 可以写成一条依赖式:
[ Recoverable(t)= BaseBackup(b) \sum_{LSN=b_{start}}^{t} WAL TimelineHistory RuntimeCompatibility ]
其中任何一项缺失,都不是“少恢复一点”,而可能是整条恢复链不可执行。
21.2.1 基础备份、检查点与一致性起点
为什么运行中的文件可以备份
在线复制数据目录时,文件并不处在同一个瞬间:
relation A copied at 10:00:01
relation B page modified at 10:00:02
relation B copied at 10:00:03
catalog copied at 10:00:04单看文件副本,它可能不一致。PostgreSQL 的保证来自:
- 备份在一个已知 checkpoint 边界开始;
- 保存恢复所需的 backup metadata;
- 保存备份期间及之后所需的 WAL;
- 恢复时从 redo 起点重放到一致状态。
所以物理备份不是“恰好一致的一篮子文件”,而是:
recoverable file image + exact WAL obligationscheckpoint 在做什么
checkpoint 把一个恢复起点写入控制信息,并推动 dirty buffer 写盘。它不是 “把所有事务历史压成备份”,也不是备份完成点。
在线备份通常在 checkpoint 起点建立 redo requirement:
checkpoint redo LSN
<= backup start LSN
<= copied data interval
<= backup stop LSN
<= selected recovery target恢复必须拥有从要求的 redo/WAL 起点到目标的连续记录。
pg_backup_start / pg_backup_stop
低层协议的核心顺序:
SELECT pg_backup_start(label => 'reviewed-label', fast => false);
-- keep this session alive while the file copy runs
SELECT * FROM pg_backup_stop(wait_for_archive => true);关键语义:
- 调用
pg_backup_start的连接必须保持; fast=true请求立即 checkpoint,可能增加 I/O;- 文件复制期间数据库可以继续写;
pg_backup_stop产生重要的backup_label/tablespace_map内容;- primary 上默认等待所需 WAL 归档;
- 自制工具必须逐步验证,不能只在最后看 exit code。
正式系统通常使用 pg_basebackup 或 pgBackRest,而不是重新实现低层协议。
理解协议是为了判断工具证据。
backup_label 不是备注
backup_label 告诉恢复过程:
which backup session
start WAL file
start LSN
checkpoint location
start time它和 tablespace mapping 是恢复输入,不是方便人看的注释。随意修改、漏拷或 把另一份备份的 label 混入,都会破坏血缘。
PostgreSQL 12 以后在线备份 label 通常作为 pg_backup_stop 输出交给备份工具,
而不是永久留在运行中 primary 的数据目录。不要照搬旧版“拷走
backup_label 文件”的手册。
pg_basebackup
pg_basebackup 通过复制协议取得运行中 cluster 的物理基础备份。常见能力:
plain or tar output
server/client compression
backup manifest
WAL streaming or fetch
rate limiting
tablespace mapping
standby signal/config generation
checkpoint mode
progress reporting它需要具备 REPLICATION 权限或 superuser,并满足 pg_hba.conf 与
max_wal_senders。它只备份整个 cluster,不能只备份一个 database。
示意:
pg_basebackup \
--host=source \
--pgdata=/safe/new/path \
--format=plain \
--wal-method=stream \
--checkpoint=fast \
--progress \
--verbose这不是本章 formal executor;正式实验使用 Pigsty 已交付的 pgBackRest。
--wal-method
概念上:
none
backup output 不带 WAL;必须另有完整 archive
fetch
在备份末尾从 source pg_wal 取所需 WAL;
必须确保 WAL 未被回收
stream
另开 replication connection 同步流式接收;
需要额外 wal sender即使 backup 包含让自身达到一致点的 WAL,若要恢复到更晚时间,仍需要后续 连续 archive。
backup manifest 与校验
manifest 记录文件、大小、checksum、WAL range 等,可用
pg_verifybackup 验证一份 pg_basebackup:
pg_verifybackup /path/to/basebackup它能证明文件与 manifest 一致、所需 WAL 结构满足其检查,却仍不能证明:
- repository credential 在事故时可用;
- PostgreSQL 能在目标环境启动;
- target 之后/之前的业务边界正确;
- extension/OS/配置依赖齐全;
- 服务能够切换。
校验是 restore proof 的一层,不是替代。
data checksum 的边界
PostgreSQL data checksum 可以在读页时发现某些 page corruption。备份工具也 可对仓库对象做 checksum。两者都重要,但:
checksum matches
means bytes match expected checksum
checksum matches
does not mean business values are semantically correct错误事务生成的页面 checksum 完全正确。
tablespace 与外部路径
物理备份必须覆盖 tablespace。恢复环境要检查:
pg_tblspcsymlink;- target path 是否存在、为空、owner 正确;
- 不同主机路径是否需要 remap;
- mount 是否真的是预期设备;
- tablespace 与数据目录是否落在同一快照 consistency group。
忽略 tablespace 常导致“主数据目录恢复成功,启动时才发现一半对象缺失”。
从 standby 备份
从 replica 取备份可降低 primary I/O,但带来不同限制:
- 备份期间 standby 不能被 promote;
- backup 与 primary timeline/WAL archive 仍要协调;
- restartpoint 不等于 primary checkpoint;
- 低活动时某些增量条件可能不成立;
- replication lag 影响备份包含的历史;
- 工具必须知道如何从正确节点取得并验证 WAL。
“从副本备份”是容量/可用性选择,不自动增加备份独立性。
PostgreSQL 18 原生增量
PostgreSQL 18 支持:
pg_basebackup --incremental=/path/to/prior/backup_manifest ...服务端依据 pg_wal/summaries 中的 WAL summary 判断改变的 block。恢复前用
pg_combinebackup 把 full 与后续 incremental 合成为可启动的 synthetic
full。
依赖链:
prior full
-> prior/current manifests
-> every required intermediate incremental
-> WAL summaries at backup time
-> pg_combinebackup
-> normal WAL recoveryPostgreSQL 不会替你管理哪些旧备份仍被新 incremental 依赖。过期策略删除 一个祖先,就可能让后代全部不可恢复。
版本兼容
物理备份通常用于同一 PostgreSQL 大版本。恢复环境还要匹配:
CPU architecture and page format expectations
PostgreSQL major
extension shared libraries
collation/locale providers
tablespace layout
configuration parameters needed during recovery本章开发实验故意把 max_connections 从 source 的 500 降到 20,
PostgreSQL 18 拒绝恢复:
recovery aborted because of insufficient parameter settings
max_connections = 20 is lower than on the primary, where it was 500这不是性能调优问题,而是 recovery safety check。正式 runner 携带 source 的:
max_connections
max_worker_processes
max_wal_senders
max_prepared_transactions
max_locks_per_transaction不能因为“验证实例很小”就任意降低 WAL 所要求的上限。
21.2.2 归档、timeline history 与恢复链
WAL 是有序的物理历史
WAL record 在数据页落盘前耐久化,支持 crash recovery、streaming replication 与 archive recovery。LSN 是逻辑日志位置,例如:
0/200002D0通常每个 segment 16 MiB,但 segment size 可在 initdb 时选择。WAL 文件名
编码 timeline、log 与 segment,不应靠截字符串之外的自造规则做跨配置运算;
使用 PostgreSQL 函数:
SELECT pg_current_wal_lsn();
SELECT pg_walfile_name(pg_current_wal_lsn());
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), '0/20000000');
SELECT pg_switch_wal();连续性要求
一份 base backup 要恢复到目标 (t),必须有:
[ [LSN_{required\ start}, LSN_t] ]
上的每一段 WAL,以及中途 timeline 切换需要的 history。
缺一段不会得到“少几秒数据”;恢复通常停在 gap:
base -> WAL A -> WAL B -> [missing C] -> WAL D -> target即使 D 在仓库里,也不能跨过 C。
archive_mode、archive_command 与 archive_library
归档要求:
wal_level >= replica
archive_mode = on
archive_command or archive_library configuredshell archive command 中:
%p source path relative to data directory
%f WAL filename最重要的返回值合同:
exit 0
PostgreSQL believes WAL is durably archived and may recycle local file
nonzero
PostgreSQL retries错误地返回 0 是数据丢失风险;持续返回非零则形成 backlog 与磁盘风险。
归档必须幂等,但不能覆盖不同内容
crash 后 PostgreSQL 可能再次提交同一个 WAL 文件。正确 archive sink 应:
target absent
atomically write and durably persist
target exists with identical content
return success
target exists with different content
return failure and alert绝不能无条件覆盖。两个不同 system identifier 的 cluster 若误用同一 archive namespace,可能产生相同 WAL 文件名却内容不同。
stanza、repository path、cluster identity 和权限隔离是防碰撞设计的一部分。
timeline 为什么存在
一次恢复或 promote 会从旧历史分叉:
timeline 7: A -> B -> C -> D
\
timeline 8: C' -> E -> F新 timeline 保留父 timeline 与分叉 LSN 的 history。这样旧历史不会被 “回到过去再向前写”覆盖。
需要区分:
system identifier
initdb 生成的物理 cluster 血缘标识
timeline
同一物理血缘中的历史分支
checkpoint timeline
最近 checkpoint 记录的 timeline
current WAL timeline
当前写入 WAL filename 所在 timeline恢复出来的物理副本应与 source system identifier 相同;完成 promotion 后 应进入一个新 timeline。本章 evidence 只记录关系:
system identifier relation = matches source
timeline 7 -> 8不公开原始 identifier。
recovery 输入
现代 PostgreSQL 通过 signal file 进入恢复:
recovery.signal
archive recovery / PITR
standby.signal
standby mode, can continue waiting for WAL/stream常见设置:
restore_command = 'fetch %f into %p'
recovery_target_time = '...+00'
recovery_target_name = '...'
recovery_target_xid = '...'
recovery_target_lsn = '...'
recovery_target_inclusive = on|off
recovery_target_timeline = 'latest'
recovery_target_action = 'pause'|'promote'|'shutdown'同一轮只能选择一种 target kind。没有 target 时通常重放到可用 WAL 尾部。
target form
| target | 优势 | 风险/前提 |
|---|---|---|
| time | 人和事故日志易理解 | 时区、提交时间、精度 |
| XID | 对目标事务明确 | wraparound/识别来源;inclusive |
| LSN | 物理边界精确 | 业务语义难读 |
| name | 预先设置,教学/发布边界清晰 | 事故后无法补建 |
| immediate | 最快到一致点 | 不是最新业务状态 |
| end of WAL | 尽量最新 | 可能包含逻辑错误 |
命名点:
SELECT pg_create_restore_point('before_risky_change');只在 primary 上有意义,并写入 WAL。它不是 backup;没有 base 与 archive, 名字本身什么也恢复不了。
inclusive / exclusive
恢复到 time、XID、LSN 时,要明确目标 record/transaction 是否包含。误删场景 常想恢复到 destructive transaction 之前:
target transaction known
recovery_target_xid = destructive xid
recovery_target_inclusive = false但必须在实验中验证具体 target type 的语义。不要只凭自然语言“到某时刻”。
target timeline
恢复链可能包含多次 promote。latest 让恢复沿 archive 中可达的最新 timeline
继续;current/具体数字会限制选择。
危险例子:
choose an old backup
target time belongs to a child timeline
force target_timeline=current结果可能根本到不了目标,或走错历史。选择前应画出 lineage:
backup timeline
-> history file
-> parent fork LSN
-> target timeline恢复到只读与 promotion
hot standby 到达 consistent state 后可先接受只读连接,然后才到 target 并 执行 action。本章实际日志顺序:
consistent recovery state reached
database system is ready to accept read-only connections
recovery stopping at restore point
selected new timeline ID: 8
archive recovery complete
end-of-recovery checkpoint
database system is ready to accept connections因此完成条件应为:
SELECT pg_is_in_recovery(); -- must be false
SHOW transaction_read_only; -- must be off
BEGIN;
CREATE TEMP TABLE write_probe(x int);
INSERT INTO write_probe VALUES (1);
ROLLBACK;只读 SELECT 成功不是 promotion proof。
配置文件不在 WAL 历史里
恢复到过去不会自动恢复:
postgresql.conf external changes
pg_hba.conf
pg_ident.conf
Patroni YAML/DCS policy
TLS certificates
Pigsty inventory
DNS/proxy config
application secrets这些必须由 versioned declaration、配置备份与平台自动化重建。把配置也塞进 data directory 不是充分答案,因为事故可能同时损坏或需要在新环境重写。
21.2.3 复制槽、归档失败与 WAL 保留者
谁让 pg_wal 不能回收
WAL retention 的常见“持有人”:
checkpoint/recovery requirement
archive not yet successful
physical replication slot
logical replication slot
wal_keep_size
backup in progress
standby/restartpoint needs它们并不是同一种保护,也不能相互替代。
archive backlog
当 archive command 失败:
completed segment remains needed
-> retry
-> pg_wal grows
-> filesystem full
-> PostgreSQL PANIC/offline数据库继续运行一段时间不代表故障无害。官方文档指出,pg_wal 所在文件系统
填满会导致 PANIC;事务不会因此神奇地归档到远端。
监控:
SELECT archived_count,
last_archived_wal,
last_archived_time,
failed_count,
last_failed_wal,
last_failed_time,
stats_reset
FROM pg_stat_archiver;解释要注意:
failed_count = 21
last successful archive is after last failure可能表示历史上失败过、当前已经恢复。不能仅因累计 count > 0 就报“当前失败”。
反之,长时间没有新 WAL 的系统,last_archived_time 老也未必故障。结合:
- current WAL segment;
- archive queue/backlog;
- WAL generation rate;
- repository maximum;
- disk free;
- recent command errors。
archive lag 与数据风险
两个间隙:
operational backlog
current WAL - last archived WAL
disaster data gap
source lost时,last business truth - last independently durable WAL可用 byte 估计:
SELECT pg_wal_lsn_diff(
pg_current_wal_lsn(),
'last known archived LSN'
);但 repository 通常按 segment 报告,边界内还有 partially filled segment。 生产 RPO 应用时间和业务 token 做补充。
为什么低流量也有 archive delay
archive command 通常对完成的 segment 工作。若写入很少:
important transaction commits
segment remains open
no archive object yet选择:
archive_timeout定期强制 switch;- 关键变更后
pg_switch_wal(); - pgBackRest
check触发/验证; - streaming WAL 到独立系统。
代价是更多 segment object 和带宽。策略应由 RPO 与成本反推。
physical replication slot
physical slot 保护某个 consumer 尚未接收的 WAL:
SELECT slot_name,
slot_type,
active,
restart_lsn,
wal_status,
safe_wal_size,
inactive_since
FROM pg_replication_slots;如果 consumer 永久消失而 slot 保留,WAL 可无限增长,除非
max_slot_wal_keep_size 设限。设限后 slot 也可能变为不可继续,需重建
replica。
槽保护 streaming consumer,不等于 archive 成功:
slot retained WAL on primary disk
!= independent disaster copylogical slot 更容易被忽视
logical slot 还关联 catalog horizon:
- 保留 WAL;
- 可能保留 dead tuples/catalog rows;
- consumer lag 影响磁盘与 vacuum;
- failover/同步 slot 有版本与配置前提。
监控 slot 要看:
active
restart_lsn
confirmed_flush_lsn
wal_status
safe_wal_size
xmin/catalog_xmin
owner and consumer没有 owner 的 slot 是容量事故候选。
wal_keep_size
wal_keep_size 是最近 WAL 的最低保留量,帮助无 slot standby 应对短暂断开。
它:
- 不是硬上限;
- 不保证某个 consumer 的准确位置;
- 不写远端仓库;
- 不提供长期 PITR;
- 不替代 slot 或 archive。
多个保留者叠加
最终 pg_wal 需要保留到最老需求:
[ LSN_{recycle\ frontier} =\min( LSN_{checkpoint}, LSN_{archive}, LSN_{slots}, LSN_{backup}, LSN_{standby} ) ]
概念上谁最老,谁控制回收边界。因此磁盘告警时不要只看“archive 正常”, 还要看所有 slot、backup lock 与 standby 状态。
本章沙箱事实
正式演练前后观察到:
archive_mode=on
archive command configured
repository stanza status=ok
one S3-compatible repository
target restore-point segment <= repository maximum segment
source timeline remains 7
restore timeline becomes 8累计 archiver 历史包含早期失败,但最近成功晚于最后失败;本章没有把累计 失败数误判为当前中断。
故障处理顺序
archive backlog:
1. stop unsafe cleanup/expiration
2. measure pg_wal free space and growth rate
3. identify exact archive error and repository availability
4. preserve source WAL
5. restore archive path/credential/capacity
6. confirm backlog drains and repository maximum advances
7. prove a restore target, not just count successes
8. write incident and prevention不要先:
delete pg_wal files
drop unknown slots
reset stanza
expire repository aggressively这些动作可能把可恢复性问题变成不可恢复。
本节原生检查单
SHOW wal_level;
SHOW archive_mode;
SHOW archive_command; -- do not copy secrets into tickets/evidence
SHOW archive_timeout;
SHOW wal_keep_size;
SHOW max_slot_wal_keep_size;
SELECT * FROM pg_stat_archiver;
SELECT slot_name, slot_type, active, restart_lsn,
confirmed_flush_lsn, wal_status, safe_wal_size
FROM pg_replication_slots;
SELECT pg_current_wal_lsn(),
pg_walfile_name(pg_current_wal_lsn());
SELECT system_identifier
FROM pg_control_system(); -- compare securely; do not publish raw id
SELECT timeline_id, redo_lsn, checkpoint_lsn
FROM pg_control_checkpoint();在 replica 上不能调用 primary-only current WAL 函数;使用 replay/receive 位置并标注 observation point。
小结
物理 recoverability 的核心不是“有一个 tar 包”,而是:
one reviewed physical lineage
+ complete base image
+ exact backup metadata
+ continuous WAL
+ reachable timeline history
+ compatible recovery runtime下一节把这些对象放进仓库依赖图:full、diff、incr 应如何保留,怎样防止 正确的自动过期删除仍被后代依赖的祖先,以及为什么加密不等于不可变。
上一节:从恢复场景设计备份 · 返回本章目录 · 下一节:备份仓库与保留策略 · 查看全书目录 · 查看索引中心