备份压缩不是越高越好。更高的压缩级别可能让仓库再小一点,却会显著增加备份时间和 CPU 消耗,还可能与数据库业务争抢计算资源。对 pgBackRest 而言,低级别 Zstandard 通常落在更实用的平衡点上,而默认的 zst(3) 已经非常接近这个甜蜜点。
真正要优化的不是压缩率,而是备份链路
评估压缩参数时,只看最终文件大小很容易得出错误结论。一次 PostgreSQL 备份至少涉及四类成本:
- 从数据库数据目录读取页面的 I/O;
- 压缩进程消耗的 CPU;
- 将备份写入本地磁盘、对象存储或远程仓库的吞吐;
- 恢复时的读取、传输和解压成本。
如果备份仓库位于速度较慢的网络或按容量计费的对象存储中,多消耗一些 CPU 可能值得。反过来,如果数据库主机已经接近 CPU 饱和,或者备份窗口很紧,高压缩级别节省的少量空间通常不划算。
这也是 Zstandard 低级别有吸引力的原因:它能以相对温和的 CPU 成本获得较好的压缩效果。继续提高级别时,压缩文件仍会变小,但收益往往开始递减。来源给出的核心判断是:低级别 Zstandard 是合理的甜蜜点,pgBackRest 默认的 zst(3) 已经处于这一范围。
为什么不应该直接把压缩级别拉满
压缩级别通常不是线性旋钮。把级别从 1 调到 3,与从 3 调到更高级别,带来的收益并不相同。后者可能明显增加 CPU 时间,却只减少很小比例的仓库占用。
生产环境中还需要考虑几个容易被忽略的变量:
- 并行度:
process-max越高,备份吞吐可能越高,但瞬时 CPU 压力也会增加。 - 数据类型:文本、重复值和稀疏数据通常更容易压缩;已经压缩的图片、归档文件或加密数据收益有限。
- 备份类型:全量、差异和增量备份的数据组成不同,不能只测一次全量就推断所有任务。
- 仓库位置:本地 NVMe、跨区域网络和对象存储面对的瓶颈完全不同。
- 恢复目标:压缩设置不能只服务于备份窗口,还要验证恢复时间目标 RTO。
因此,最合适的级别不是“产生最小文件的级别”,而是“在满足备份窗口和恢复目标的前提下,让总成本最低的级别”。
可以这样配置 zst(3)
下面是一份可改造的 pgBackRest 配置。运行前需要按实际 PostgreSQL 版本、数据目录和仓库路径修改相应字段:
[global]
repo1-path=/backup/pgbackrest
repo1-retention-full=2
# Zstandard,级别 3
compress-type=zst
compress-level=3
# 根据主机 CPU 和备份窗口调整;测试时应保持不变
process-max=4
[main]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
配置文件通常可保存为 /etc/pgbackrest/pgbackrest.conf。pgBackRest 会在每次命令执行时读取配置,不需要为修改压缩参数重启 PostgreSQL。
创建 stanza 并执行一次全量备份的命令如下:
sudo -u postgres pgbackrest --stanza=main stanza-create
sudo -u postgres pgbackrest --stanza=main check
sudo -u postgres pgbackrest --stanza=main --type=full backup
sudo -u postgres pgbackrest --stanza=main info
不要在没有恢复验证的情况下直接替换生产参数。至少先在测试环境中完成一次备份和恢复演练。
用自己的数据做一轮可重复的对比
不同数据库的可压缩性差异很大。下面的脚本会分别测试 zst(1)、zst(3)、zst(6) 和 gz(6),记录墙钟时间、用户态 CPU 时间、内核态 CPU 时间、CPU 利用率及仓库字节数。
这是一个测试环境脚本。它通过 --archive-check=n 避免测试仓库与现有 WAL 归档配置冲突,因此生成的备份只能用于压缩性能比较,不能作为正式恢复副本。执行前还应确保 /var/tmp/pgbackrest-bench 有足够空间。
#!/usr/bin/env bash
set -euo pipefail
STANZA="${STANZA:-main}"
BASE="${BASE:-/var/tmp/pgbackrest-bench}"
# 算法和级别。可以删除不需要的组合,或增加更多 zst 级别。
cases=(
"zst 1"
"zst 3"
"zst 6"
"gz 6"
)
mkdir -p "$BASE"
printf 'algorithm,level,wall_seconds,user_seconds,sys_seconds,cpu,repo_bytes\n'
for item in "${cases[@]}"; do
read -r algorithm level <<< "$item"
repo="$BASE/${algorithm}-${level}"
metrics="$repo.time"
rm -rf "$repo" "$metrics"
mkdir -p "$repo"
common=(
--stanza="$STANZA"
--repo1-path="$repo"
--log-level-console=warn
)
pgbackrest "${common[@]}" stanza-create
/usr/bin/time \
-f '%e,%U,%S,%P' \
-o "$metrics" \
pgbackrest "${common[@]}" \
--type=full \
--archive-check=n \
--compress-type="$algorithm" \
--compress-level="$level" \
backup
repo_bytes=$(du -sb "$repo" | awk '{print $1}')
printf '%s,%s,%s,%s\n' \
"$algorithm" "$level" "$(cat "$metrics")" "$repo_bytes"
done
例如保存为 bench-pgbackrest.sh 后,在已经配置好 pgBackRest 的测试数据库上运行:
chmod +x bench-pgbackrest.sh
sudo -u postgres ./bench-pgbackrest.sh | tee compression-results.csv
单轮测试容易受到页面缓存、后台任务和存储抖动影响。更可靠的做法是:
- 每个组合至少运行三次;
- 轮换或随机化测试顺序,避免后运行的任务持续受益于缓存;
- 所有组合保持相同的
process-max、数据库快照和存储路径类型; - 同时观察数据库业务延迟,而不只是备份进程的完成时间;
- 用正式 WAL 归档配置再做一次真实恢复测试。
如何从结果中选择参数
把 zst(3) 作为基线,分别计算其他组合相对它节省了多少空间,又增加了多少时间和 CPU。决策时可以使用一个简单问题:
为了再减少 1 GB 仓库占用,需要额外付出多少 CPU 秒和多少备份时间?
如果 zst(6) 只让仓库略微缩小,却明显延长备份窗口,那么应保留 zst(3)。如果网络带宽或对象存储容量才是主要成本,并且主机有充足的空闲 CPU,更高级别才可能有意义。
上线前可以按下面的清单确认:
- 默认从
compress-type=zst、compress-level=3开始; - 在代表性数据上比较空间、耗时和 CPU,而不是使用空库测试;
- 调整压缩级别时保持并行度不变;
- 在业务高峰期观察数据库延迟和 CPU steal、iowait;
- 同时验证全量、差异和增量备份;
- 完成一次端到端恢复,并记录恢复耗时;
- 只有在空间或网络收益足够明确时,才提高压缩级别。
压缩参数的目标不是制造最小的备份文件,而是构建一条能长期稳定运行、可以按时恢复、并且不会拖慢数据库的备份链路。对多数环境而言,从 pgBackRest 默认的 zst(3) 开始测量,比直接追逐最高压缩率更稳妥。