如果你最近正在用 PostgreSQL 17 或更新版本尝试原生增量备份,看到这句ERROR: incremental backups cannot be taken unless WAL summarization is enabled时,第一反应多半是去翻 WAL 归档,查archive_mode,甚至怀疑备份目录权限。我那次也是这样:监控先告警,备份脚本跟着失败,日志里干净得只剩下这一行提示。环境是 PostgreSQL 17.2,数据量接近 2TB,平时负载不高,归档也一直正常,所以我最初完全没把问题往参数上想,绕了快一个小时才定位到根因。
这篇文章会围绕这句报错展开:先还原我当时的排查现场,再把 WAL summarization 到底是什么、为什么增量备份必须依赖它讲清楚,最后给出启用参数后跑通增量备份和恢复的完整步骤,以及几个替代方案和取舍。适合正在使用 PG17 原生备份能力、或者准备从全量备份切到增量备份的 DBA 和运维同学参考。
1. 报错出现时的现场:日志、环境、备份命令
1.1 我是在什么环境里遇到这个报错的
当时线上是一套 PostgreSQL 17.2 主库,跑在 Ubuntu 22.04 上,数据目录接近 2TB。原来的备份策略很简单:每天凌晨 2 点用pg_basebackup做一次全量备份,因为数据量大,全量备份窗口越来越长,所以准备改成“周日全量 + 工作日增量”。备份通过 cron 调用,用户是一个专门用来备份的超级用户角色,权限没有问题,磁盘空间也足够。
我第一次执行增量备份的命令长这样:
pg_basebackup -h /var/run/postgresql -U backup_user \ -D /data/backups/incr_20250217 \ --incremental=/data/backups/base_20250216/backup_manifest \ --wal-method=stream命令刚跑起来,甚至还没有开始传输数据,就直接吐出了错误:
pg_basebackup: error: incremental backups cannot be taken unless WAL summarization is enabled这个报错出现的位置很有意思:它没有等到备份传输中途才报,而是在初始化阶段就拒绝执行。这意味着问题不在网络、磁盘或者备份端,而是服务端在回答“你是否支持增量备份”的时候,直接给了否定答案。
1.2 第一反应最容易查错的两个方向
第一个会查的方向是 WAL 归档。因为任何人看到“WAL”两个字母,都会下意识去看archive_mode和archive_command。我当时也查了,archive_mode=on,archive_command指向 pgBackRest 的归档脚本,pg_stat_archiver里也显示最近都有成功归档,没有任何异常。继续查wal_level,也是replica,完全符合增量备份的基本要求。
第二个会查的方向是旧备份目录。毕竟--incremental指向了一份上一轮的全量备份,如果这份备份的 manifest 丢了、目录被移动过、或者备份文件不完整,也可能会报错。我检查了/data/backups/base_20250216/backup_manifest文件,存在,大小正常,目录权限也没问题。
查完这两处之后我才意识到,报错信息本身已经说得非常明确了:问题就在 WAL summarization。它不是归档,也不是备份文件,而是 PostgreSQL 17 新增的一个独立机制。这个时候再去翻归档配置,其实是典型的“被 WAL 三个字母带偏了”。
2. 增量备份和 WAL summarization 的关系:为什么报错会指向这里
2.1 PostgreSQL 17 之后“原生增量备份”走了什么路
在 PostgreSQL 17 之前,官方工具里其实没有真正意义上的“增量备份”。pg_basebackup每次都是把整个数据目录复制一遍,备份窗口和数据量成正比。那时候要做增量,基本只能依赖第三方工具,比如 pgBackRest、pg_rman,它们靠自己的 manifest 和页级比较机制来减少拷贝量。
PG17 带来了原生增量备份能力,配套的是pg_basebackup --incremental和pg_combinebackup。这套机制的设计思路和外部工具不太一样:它不是用工具自己去扫新旧文件做 diff,而是让 PostgreSQL 在运行过程中主动记录“哪些数据页被修改过”,然后把这个记录交给备份工具。
“哪些数据页被修改过”的记录,就是 WAL summary。增量备份能不能做,本质上取决于这份记录存不存在。所以当wal_summarize参数还处于默认的off状态时,服务端根本拿不出增量依据,直接拒绝是最安全的做法。
2.2 WAL summary 文件里到底写了什么
WAL summary 文件存放在数据目录的pg_wal/summaries子目录里,由名为 wal summarizer 的后台进程生成。它不包含 WAL 日志本身,只包含非常精简的元数据:比如这个 summary 覆盖的 WAL 范围,以及在这个范围内哪些表文件的哪些 block 被写入过。
用一个比较好理解的比喻:全量备份就像把整座图书馆的书全部复印一遍;增量备份则只需要复印“上次复印之后有人翻动过的书架”。WAL summary 就是那张“被翻动过的书架清单”。有了清单,增量备份就可以只读清单里点到的那部分页面,不需要重新扫全库。
输出文件是按 WAL 段范围划分的,大小通常远小于 WAL 本身。正因为它是这种“提示文件”,如果关闭了wal_summarize,summarizer 进程根本不会启动,这些文件也不会生成。那么增量备份工具就没有任何可依赖的“变化清单”,只能报错。
2.3 不开 summarization 时增量备份还能做吗
理论上,还存在一种“笨办法”:备份工具自己扫描上一份全量备份的所有数据文件,再逐个文件、逐个 block 比对当前数据文件的 LSN,找出变化的部分。外部工具在 PG17 之前就是这么做的,但代价是额外的大量 IO 和 CPU。原生接口走的是更高效的 WAL summary 路线,它要求服务端必须先把变化清单准备好。
所以 PG17 的pg_basebackup --incremental在这个问题上没有任何回旋余地:不开wal_summarize,就不让你做增量。这个设计目的很清晰,避免备份工具在缺少变化列表时误判某些文件“没有变化”,最后恢复出一个损坏的实例。宁可报错,也不给一个看似成功但不可恢复的备份。
3. 从报错到根因:四个命令锁定参数问题
3.1 先确认备份方式,别一上来就动配置
遇到这类报错,我的建议是先确认调用方真实使用的备份方式。因为同一个环境里可能同时存在多套脚本,比如一台主库既被 pgBackRest 管理,又有人手动跑pg_basebackup。如果报错来自 pgBackRest,它不会抛出 WAL summarization 相关错误,它有自己的增量机制。只有当调用方是 PG17 原生工具时,这个报错才有意义。
确认方式很简单,看两条命令:
pg_basebackup --version pg_backup_start --help 2>&1 | head -20重点是确认你确实在用pg_basebackup --incremental,而不是某个包装了pg_basebackup但内部仍然走全量逻辑的第三方脚本。确认之后,问题范围就缩小到了服务端参数。
3.2 SHOW wal_summarize 与 pg_stat_wal_summarizer
接下来要检查的参数是wal_summarize。这个参数是 PG17 引入的,默认值是off。用 psql 连接目标实例后执行:
SHOW wal_summarize;如果返回结果是off,那基本就锁定根因了。为了进一步确认 summarizer 进程状态和 summary 生成情况,可以看一眼pg_stat_wal_summarizer视图:
SELECT * FROM pg_stat_wal_summarizer;在这个视图里,如果看到 summarizer 进程有 PID,并且有不断推进的 LSN 记录,说明功能在正常工作;如果视图返回空,或者关键字段停滞,说明功能没有真正开启。不过最直接的还是先看SHOW wal_summarize。
3.3 为什么全量备份没事,增量备份就报错
这也是很多人在排查时会困惑的地方:同一套环境,全量备份跑得好好的,怎么一到增量备份就报错?
因为全量备份根本不需要变化清单。它要做的就是把整个数据目录原样复制出去,最多再加一份备份期间产生的 WAL。而增量备份要回答的问题是:自从上一份备份之后,数据文件里哪些块变了。PG17 原生增量备份依赖 WAL summary 来回答这个问题,所以当wal_summarize=off时,只有增量命令会失败,全量命令完全不受影响。
理解了这一点,回头再看当时的备份策略:周日全量成功,周二增量失败,其实不是备份脚本在“挑日子”,而是增量路径对参数有额外要求。
4. 解决:启用 wal_summarize 并跑通第一次增量备份
4.1 参数修改与重启的注意点
修改参数有两种方式,一种是直接编辑postgresql.conf,另一种是用ALTER SYSTEM。考虑到很多环境不允许直接改文件,我通常用这个方式:
ALTER SYSTEM SET wal_summarize = on;注意,wal_summarize是启动级参数,不能用pg_reload_conf()让它热生效。对,你没有看错,它不像shared_buffers那样改完必须重启,也不是像work_mem那样可以 reload,它是PGPOSTMASTER级别的参数。重启前最好确认当前没有正在执行的备份任务,如果有备库还建议评估一下是否需要切换主备。
重启命令根据部署方式不同会有差异,比如:
sudo systemctl restart postgresql-17 # 或者 pg_ctl restart -D /var/lib/postgresql/17/main重启完成后,再执行一次:
SHOW wal_summarize;看到返回on,问题就解决了一半。
4.2 第一次增量备份完整命令和结果验证
参数开启后,我重新执行了之前的全量备份,确保有一个干净的基础备份。然后再次执行增量备份:
pg_basebackup -h /var/run/postgresql -U backup_user \ -D /data/backups/incr_20250217 \ --incremental=/data/backups/base_20250216/backup_manifest \ --wal-method=stream这次不再报错,备份过程开始正常传输。需要说明的是,用pg_basebackup --incremental生成出来的备份目录,并不是一个完整的、可独立启动的实例目录,它是一个“增量片段”。最终要用的时候,需要依赖它和之前的基础全量备份合并。
为了验证生成的增量备份是否可用,我进入备份目录检查了基础文件结构,确认backup_manifest、pg_wal等关键内容存在。如果这个阶段有任何异常,问题通常会出在旧备份 manifest 与新备份之间的 WAL 连续性上,这一点在后面会展开讲。
4.3 可恢复性验证:pg_combinebackup 实测
增量备份跑通不等于恢复没问题,备份方案真正要验证的是“能不能恢复”。PG17 提供pg_combinebackup把基础备份和增量合成为一个完整备份。命令如下:
pg_combinebackup /data/backups/base_20250216 \ /data/backups/incr_20250217 \ -o /data/backups/combined_20250217这里有个顺序要求:旧的基础备份在前,新的增量备份在后,不能倒过来。合并完成后,combined_20250217就是一个可以直接启动的完整数据目录。我把它复制到测试实例上,用pg_ctl启动,并且执行了一次pg_checksums --check验证文件完整性,整个过程没有报错。
个人建议:每次启用新参数、切换备份策略后,至少要完整做一次“备份 + 合并 + 启动 + 查询”的链路测试。增量备份报错是“显性失败”,怕的是备份成功但恢复不出来,那种问题才是真正的隐患。
4.4 参数开启后的资源开销观察
wal_summarize=on不是免费的,系统里会多出一个 summarizer 后台进程。它会周期性地读取 WAL 并生成 summary 文件,因此会有少量 CPU 和 IO 开销。在写入负载较高的业务库上,这个开销能感知到,但正常情况下比全量备份的低峰期 IO 要小得多。
开启后,建议观察一段时间的pg_stat_wal_summarizer视图,确认 summarizer 的 LSN 能稳定推进。如果它的推进速度一直追不上当前 WAL 的写入速度,说明它在拖后腿,可能需要考虑降低 WAL 产生的速率,或者分担备份任务。总之第一次开启后的第一个小时,值得多看一眼监控。
5. 不想开启 wal_summarize 时的替代路线与代价
5.1 继续用 pgBackRest 做增量备份
有一部分团队并不是非要使用 PG17 原生增量能力,只是原来的全量备份窗口太长,想通过增量来解决。这种情况下,如果你的备份方案里已经有 pgBackRest,完全可以继续用它,不需要开wal_summarize。
pgBackRest 的增量备份逻辑和 PG17 原生机制不同:它通过自己的 manifest 记录每个文件的 LSN 和状态,备份时对每个文件做增量判断,并不依赖 WAL summary。所以在 PG17 上,pgBackRest 的type=incr仍然可以照常工作。代价是你得维护一套独立于官方工具的配置和恢复流程,而且未来如果打算切回官方原生增量,还需要再一次调整策略。
5.2 全量 + WAL 归档方案和原生增量备份的本质区别
还有一种更保守的做法:继续做每周全量,但每天把 WAL 归档完整保留下来,恢复时用“全量 + 回放 WAL”的方式。这个方法在技术上完全可行,而且不需要启用新参数。
但它和“增量备份”不是一回事。增量备份是在数据文件层面做差分,恢复时只需要把改过的页面覆盖回基础备份;而全量 + WAL 归档需要在全量基础上按顺序回放所有日志,日志越多恢复时间越长。对一个每天产生大量 WAL 的库来说,恢复窗口可能远超预期。
所以如果库小、WAL 量少,全量 + WAL 归档足够;如果库大、恢复时间敏感,原生增量备份的价值才真正体现出来。
5.3 方案对比:性能、恢复时间、运维复杂度
| 方案 | 备份速度 | 恢复时间 | 依赖特性 | 适合场景 |
|---|---|---|---|---|
| PG17 原生增量 | 快,只复制变化块 | 快,合并后直接启动 | 需要wal_summarize=on | PG17+,熟悉官方工具 |
| pgBackRest 增量 | 快,文件级 diff | 快,自带恢复流程 | 需要维护 pgBackRest | 已有 pgBackRest 的存量环境 |
| 全量 + WAL 归档 | 全量慢,归档持续 | 慢,取决于日志量 | 只需archive_mode=on | 小库,对恢复时间不敏感 |
从运维角度看,没有绝对最优,只有场景适配。我的选择思路是:如果实例版本已经是 PG17 以上,团队也愿意接受官方新特性,那就直接用原生增量,少维护一套工具;如果版本刚升级、业务对备份恢复的成熟度要求很高,那 pgBackRest 的稳定生态更稳妥。
6. 这组参数在真实环境里容易踩的 5 个细节
6.1 wal_summarize 是启动级参数,不是 reload 参数
很多人改完参数执行SELECT pg_reload_conf();发现SHOW wal_summarize;还是off,就怀疑自己没改对。其实不是没改对,而是这个参数只能重启生效。建议把参数修改、重启、验证三条命令写进变更记录,避免下次再踩同一个坑。
6.2 pg_wal/summaries 目录只增不减,需要纳入清理策略
开启wal_summarize后,pg_wal/summaries会持续产生新文件。正常情况下,这些文件会随 WAL 清理机制一并被回收,但如果你手动做了异常的备份目录保留策略,或者调整了wal_keep_size等参数,summary 文件可能堆积。所以监控磁盘时,别只盯着pg_wal主目录,summaries子目录也要纳入容量巡检。
6.3 增量备份依赖旧备份的 manifest,别把中间版本弄丢
原生增量备份不是凭空产生的,它需要上一份全量备份的backup_manifest作为基线。如果中间某份备份被误删或挪动,后续增量就无法继续。建议给备份目录设置明确的保留周期,并且把“上一级备份是否存在”写进备份脚本的前置检查里,别等到备份命令报错了才去翻文件。
6.4 PG17 之前的备份实例完全不认识这个报错
如果你的实例还是 PG16 或更早版本,根本不会出现这句报错,因为wal_summarize参数在那时还不存在。反过来,当你看到这句报错时,也反过来提醒你:当前实例一定是 PG17 以上。这个报错本身可以作为版本确认的一个证据,排查时候可以先SHOW server_version;确认版本边界。
6.5 备份恢复后的 checksum 与实例启动验证
我在做完增量备份合并后都会执行pg_checksums --check,并且实际启动一次实例验证。因为增量备份最容易出的问题不是“报错”,而是“看似成功但数据缺页”。养成“每次备份改动后都跑一次恢复演练”的习惯,比任何监控都管用。我后来把这条写进了团队的备份检查清单,从那之后,这类参数问题基本都能在半小时内定位完。