跳至内容
21.2 物理备份与 WAL 连续性

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 的保证来自:

  1. 备份在一个已知 checkpoint 边界开始;
  2. 保存恢复所需的 backup metadata;
  3. 保存备份期间及之后所需的 WAL;
  4. 恢复时从 redo 起点重放到一致状态。

所以物理备份不是“恰好一致的一篮子文件”,而是:

recoverable file image + exact WAL obligations

checkpoint 在做什么

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.confmax_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_tblspc symlink;
  • 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 recovery

PostgreSQL 不会替你管理哪些旧备份仍被新 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_modearchive_commandarchive_library

归档要求:

wal_level >= replica
archive_mode = on
archive_command or archive_library configured

shell 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 copy

logical 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 应如何保留,怎样防止 正确的自动过期删除仍被后代依赖的祖先,以及为什么加密不等于不可变。


上一节:从恢复场景设计备份 · 返回本章目录 · 下一节:备份仓库与保留策略 · 查看全书目录 · 查看索引中心

最后更新于