PostgreSQL 17 增量备份与 pg_verifybackup 实测:集中改动增量 35MB、随机改动接近全量,verified 也不等于数据页没坏
在 PostgreSQL 17.11 测试实例、pgbench -s 40(约 606MB,含 400 万行 pgbench_accounts)上实测 pg_basebackup --incremental 与 pg_verifybackup。集中更新时增量 35MB,随机更新时 642MB/640MB 接近全量;备份校验能发现备份副本被篡改、缺文件、缺 WAL,但查不出源库已经坏掉的数据页。附只读巡检 SQL 和测试脚本。
PostgreSQL 17.11 实测(pgbench 自造约 606MB):全量备份 637MB;只改 1% 账户再追加 1 万行流水后,pg_basebackup --incremental 只有 35MB、113–114ms,合成后启动比对数据一致;但随机跑 20 秒 pgbench 后增量是 640–642MB,和全量差不多,因为一个文件要发的块超过 90% 就整文件复制。pg_verifybackup 能查出备份被改、少文件、缺 WAL,可源库页面本来就坏、又加了 --no-verify-checksums 时,它照样输出 backup successfully verified。
适用对象
- 增量省不省空间看改动分布:集中改动(4 万行更新 + 1 万行追加)增量 35MB;随机 20 秒 pgbench 后 640–642MB,接近全量 637MB——单个文件要发的块超过 90% 就整文件复制。先量一次再定全量周期。
- pg_verifybackup 验证的是「备份目录和 backup_manifest 一致、所需 WAL 能解析」,能查出备份被改写、缺文件、缺 WAL,但它不是数据页健康检查。源库页面已经坏了,要靠 pg_basebackup 默认的页校验和检查、pg_checksums、pg_amcheck 才能发现。
- 备份任务要看 pg_basebackup 的退出码,不能只看目录大小:本次校验和出错、退出码 1 后,磁盘上照样留着一个 644MB 的目录,有 backup_label、没有 backup_manifest。
- 增量链要全量 + 增量一起保留,恢复先 pg_combinebackup 再 pg_verifybackup;每周至少一次把备份真正启动读数据验证,合成目录可直接改端口启动。
集中改动:增量 35MB,合成 644MB,恢复比对全对上
先跑全量:pg_basebackup -Fp -c fast 得到 637MB,四轮 1235–1788ms;pg_verifybackup 全量 190–218ms,打印 backup successfully verified。
然后做 A 类集中改动:UPDATE pgbench_accounts 前 1% 账户(aid <= 40000,4 万行)+ 往 pgbench_history 追加 1 万行 + 更新一个 branch,CHECKPOINT 后跑 --incremental=full/backup_manifest。增量 35MB,113–114ms,里面有 671 个 INCREMENTAL.* 文件(合计约 15MB)。pg_verifybackup 增量 15–16ms 通过。
pg_combinebackup full incr1 -o combined 用时 918–1148ms,合成目录 644MB,pg_verifybackup 通过。把合成目录改端口 5441 直接启动(约 112–124ms 起来,日志里 redo done、database system is ready to accept connections),然后比对 pgbench_accounts count/sum、history count/sum、branches sum,和源库在增量备份那一刻完全一致:accounts 4000000 / 40000,history 10000 / 10000,branches 50000(后面随机改动的两轮也一样一致)。
本节判断
- 下载版演示脚本复跑一次结果相同:全量 637MB 1890ms,增量 35MB 138ms,合成 644MB 1099ms,三次 verify 通过,fingerprint 一致。
- 35MB 里 INCREMENTAL.* 文件 671 个、合计约 15MB,其余是小文件整份复制;恢复时它必须和 637MB 的全量一起交给 pg_combinebackup,单独一份增量启动不了。
| 阶段 | 大小 | 第 1 轮 | 第 2 轮 | 结果 |
|---|---|---|---|---|
| 全量 pg_basebackup | 637MB | 1788ms | 1543ms | pg_verifybackup 194 / 190ms 通过 |
| 集中改动增量 | 35MB | 113ms | 114ms | pg_verifybackup 15–16ms 通过 |
| pg_combinebackup 合成 | 644MB | 918ms | 1148ms | pg_verifybackup 通过 |
| 合成目录启动+比对 | - | 124ms | 112ms | count/sum 与源库一致 |
随机改动为什么和全量一样大?90% 整文件判断
B 类改动直接跑 pgbench -T 20 -c 4,两轮分别处理 251309 / 217158 笔交易,tps 约 12568 / 10860。再跑增量备份,结果 642MB / 640MB,1477 / 1451ms——和全量差不多大。
第二轮我统计了一下:INCREMENTAL.* 文件 663 个,合计只有 2.8MB,剩下的都是整文件复制。原因在 PostgreSQL 17 源码 src/backend/backup/basebackup_incremental.c(REL_17_STABLE,我核对过):一个文件需要发送的块超过 90%(nblocks * BLCKSZ > size * 0.9),就直接整文件备份。
pgbench_accounts 数据文件 542556160 字节,20 秒随机更新后几乎每个块都被改过,所以整文件进了增量。再合成 757–1225ms,657–658MB,校验通过。
本节判断
- 随机写多的库,增量备份未必省空间。先拿本文脚本在测试副本上量一次,再决定全量周期。
- 90% 是源码里写死的阈值,源码注释也说还不确定最合适的值;按文件判断,所以大表被随机写满后整文件进增量。
| 改动类型 | 增量大小 | 第 1 轮 | 第 2 轮 | INCREMENTAL.* 文件 |
|---|---|---|---|---|
| 集中改动 | 35MB | 113ms | 114ms | 671 个 / 约 15MB |
| 随机改动 | 642MB / 640MB | 1477ms | 1451ms | 663 个 / 2.8MB(其余整文件) |
pg_verifybackup 能查出:改文件、缺文件、缺 WAL
我在备份副本上做了三种破坏。A:改写备份里 pgbench_accounts 数据文件(base/5/16468)中间 16 字节,pg_verifybackup 直接报 checksum mismatch for file "base/5/16468",退出码 1。
B:删掉一个表文件,报 "base/5/16469" is present in the manifest but not on disk,退出码 1。C:删掉备份 pg_wal 里的 WAL 段,pg_waldump 先报 could not find any WAL file,pg_verifybackup 报 WAL parsing failed for timeline 1,退出码 1。
它按 backup_manifest 里的文件清单、大小和文件校验和(默认 CRC32C)逐个比对,再用 pg_waldump 解析恢复需要的 WAL。所以备份拷完以后被改、少文件、缺 WAL,都能抓到。
本节判断
- 做备份任务时,每份备份做完立刻跑 pg_verifybackup,退出码非 0 就告警。这比靠人眼检查目录可靠。
- manifest 里的 CRC32C 是备份时对整个文件算的,和数据页上的 data_checksums 是两回事:它只能证明「文件和拷贝那一刻一样」。
| 破坏 | 报错原文 | 退出码 |
|---|---|---|
| 改写数据文件 16 字节 | checksum mismatch for file "base/5/16468" | 1 |
| 删除表文件 | "base/5/16469" is present in the manifest but not on disk | 1 |
| 删除 WAL 段 | WAL parsing failed for timeline 1 | 1 |
源库数据页坏了再备份:pg_verifybackup 说通过,pg_amcheck 说坏页
这组只在实验集群上做:建 t_corrupt 表 5000 行,停库,用 dd 改写它数据文件第 2 个块里的 16 字节,再启动。我跑了两遍(第二遍补查失败目录和 pg_checksums,表重建后文件号从 16478 变成 16483,下面两个文件号都会出现)。D1 用默认 pg_basebackup,输出 WARNING: checksum verification failed in file "./base/5/16478", block 2: calculated 491C but expected 8EFB,最后 pg_basebackup: error: checksum error occurred,退出码 1。
坑在哪?失败后备份目录还留在磁盘上(第二遍看的),644MB,有 backup_label,但没有 backup_manifest。如果定时任务只看目录在不在、大小对不对,会以为备份成功。对它跑 pg_verifybackup 直接 could not open file "full_d1/backup_manifest": No such file or directory。
D2 加 --no-verify-checksums 再备份,退出码 0,成功。D3 对这份备份跑 pg_verifybackup,打印 backup successfully verified,退出码 0——坏页被当成正常内容记进清单。也就是说 pg_verifybackup 验证的是备份和备份时拷到的内容一致,不是数据页健康。
要抓坏页得换工具。D4 我先直接对备份目录跑 pg_checksums --check,被拒:pg_checksums: error: cluster must be shut down。把备份目录在 5442 端口启动一次、正常关闭后再跑,输出 checksum verification failed in file "full_d/base/5/16483", block 2 …;Files scanned: 965,Blocks scanned: 80322,Bad checksums: 1,退出码 1。
D5 源库 SELECT count(*) FROM t_corrupt 也报 WARNING: page verification failed, calculated checksum 18716 but expected 36603 / ERROR: invalid page in block 2 of relation base/5/16478。D6 先装 amcheck 扩展,再 pg_amcheck -t t_corrupt,报 heap table "postgres.public.t_corrupt": ERROR: invalid page in block 2,退出码 2。
实验做完后用本文只读 SQL 查 pg_stat_database:postgres 库 checksum_failures = 2,checksum_last_failure 2026-10-20 10:37:07(正是 pg_amcheck 那一刻)。这个计数只在某个进程真的读到坏页时才涨,所以 0 也不代表没有坏页。
本节判断
- data_checksums 在 PG 17 initdb 默认是关的,要 -k;已有集群开启要停库跑 pg_checksums --enable,本次未实测,只陈述。
- pg_amcheck 第一次跑报 amcheck is not installed,要先 CREATE EXTENSION amcheck;这一步是写操作(建扩展),生产上要走变更。
| 动作 | 工具 | 结果 | 退出码 |
|---|---|---|---|
| D1 默认备份 | pg_basebackup | checksum verification failed … error: checksum error occurred;目录残留无 backup_manifest | 1 |
| D2 --no-verify-checksums | pg_basebackup | 成功,坏页进入备份 | 0 |
| D3 校验 D2 | pg_verifybackup | backup successfully verified | 0 |
| D4 先启动再正常关闭 D2 备份,再检查 | pg_checksums --check | checksum verification failed … Bad checksums: 1 | 1 |
| D6 检查源表 | pg_amcheck -t t_corrupt | ERROR: invalid page in block 2 | 2 |
增量备份和校验,我按这四步卡
第一步,每份备份做完立刻跑 pg_verifybackup,退出码非 0 就告警;备份任务看 pg_basebackup 的退出码,不看目录大小。D1 的教训就是目录还在 644MB 但 manifest 没了。
第二步,用本文只读 SQL 检查关键参数和 WAL 汇总状态:data_checksums、summarize_wal、wal_level;pg_get_wal_summarizer_state() 和 pg_available_wal_summaries() 看汇总在不在跑。实验集群 summary_files 30、summarized_lsn 1/58000028。
第三步,增量链要全量 + 增量一起保留,恢复先 pg_combinebackup 再 pg_verifybackup;随机写多的库,增量未必省空间,先用演示脚本量一次再定全量周期。wal_summary_keep_time 默认 14400 分钟(10 天),全量太旧、汇总被清掉时增量会失败——官方文档写,本次未实测。
第四步,每周至少一次把备份真正启动起来读数据,本次用 count/sum 比对;正式演练用我们之前的备份恢复演练脚本。
我的备份校验闭环
- 01
1
跑完立刻 verify
pg_basebackup 退出码非 0 直接告警;pg_verifybackup 每一份都跑,不看目录大小。
- 02
2
检查增量前置条件
用只读 SQL 查 data_checksums / summarize_wal,确认 WAL summary 在生成。
- 03
3
先 combine 再 verify,保留全量 + 增量
恢复链缺一环都不行;随机写多先评估体积。
- 04
4
定期真恢复读数据
每周至少一次把备份启动并 count/sum 比对,或跑恢复演练脚本。
参考依据
以下来源用于确认市场趋势、政策背景和术语边界;具体落地方案仍以客户的数据范围、权限和交付目标为准。
常见问题
为什么 pg_verifybackup 说 backup successfully verified,恢复还是可能失败?
它只证明备份目录和 backup_manifest 一致、需要的 WAL 能解析,不检查数据页健康。本次源库页面已坏、用 --no-verify-checksums 备份,verify 照样通过;坏页要靠 pg_checksums / pg_amcheck,最终还得真恢复读一遍。
增量备份什么情况下不省空间?
随机写多的时候。PG 17 源码里一个文件要发的块超过 90% 就整文件复制;本次 pgbench 随机更新 20 秒后增量 642MB / 640MB,全量是 637MB。
pg_basebackup 失败为什么目录还在?
本次实测里,pg_basebackup 遇到页校验和错误后以退出码 1 结束,已经拷好的 644MB 目录没有删掉,有 backup_label、没有 backup_manifest。所以定时任务要看退出码,不能只看目录在不在。
怎么知道线上集群开了 data_checksums?
跑本文只读 SQL 第 1 段看 data_checksums 是 on 还是 off。PG 17 的 initdb 默认不开,要加 -k;已有集群要开得停库跑 pg_checksums --enable(本次未实测)。
增量备份要保留哪些文件?
全量 + 所有增量都要保留。恢复先 pg_combinebackup 合成,再 pg_verifybackup,然后启动读数据验证。增量链缺一段就恢复不了。