
数据库备份最容易出的事故,不是没做备份,是做了三年,第一次真要恢复时才发现恢复不了。备份的价值不由备份日志决定,只由一次成功的恢复决定。
下面这套东西在 Ubuntu 24.04 + PostgreSQL 16 上跑通,备份工具是 pgBackRest 2.53,存储落在一块单独的磁盘上。目标不是「有个备份」,是能在指定的一秒钟之前把库拉回来。区别在于:前者给你一个文件,后者给你一个时间点。
1. 为什么不用 pg_dump 加一个 cron
pg_dump 做的是逻辑导出,导出的是一份「当时的快照」。它能回到导出那一刻,回不到两点十七分那条误删语句之前。逻辑备份的时间粒度是「一次」,物理备份的粒度是「一秒」。
物理备份加 WAL 归档,才能做到时间点恢复(PITR)。原理不复杂:先复制一份数据目录的基线,再把基线之后产生的每一条 WAL 都留下来,恢复时按顺序重放,重放到你指定的时间就停。WAL 是数据库自己写的日记,备份工具要做的只是别让日记断页。
pgBackRest 的价值在于它把「别断页」这件事做成了可校验的:每次 full/incremental 之后可以 check,每次归档可以 verify,仓库有校验和。它不让你靠日志里没有 error 来判断成功。
2. 目录与用户,先定下来
先给备份仓库一个独立位置。不要和数据目录放同一块盘,也不要和 WAL 归档放同一个文件系统。盘坏的时候,三样东西同时消失是常态,不是意外。
/var/lib/pgbackrest/ # 备份仓库(独立磁盘,挂载点 /backup)
├── backup/main/ # full / diff / incr 备份集
├── archive/main/16-1/ # WAL 归档,按集群版本与时间线分目录
└── backup.info # 仓库元数据
/etc/pgbackrest/
└── pgbackrest.conf # 全局配置,postgres 用户需可读
/var/lib/postgresql/16/main/ # 数据目录
└── pg_wal/ # 待归档 WAL,堆积说明归档失败
安装只用到两个包:pgbackrest 本体必须装在数据库主机上,因为 archive_command 要在本机执行。远端仓库则需要在对端也装一份。
apt install pgbackrest postgresql-16
pgbackrest version
psql --version
mkdir -p /var/lib/pgbackrest
chown postgres:postgres /var/lib/pgbackrest
chmod 750 /var/lib/pgbackrest
3. 四份配置,值给全
第一份是仓库与集群配置。这里最容易埋雷的一行是加密口令——它是明文躺在文件里的,文件权限就是你的全部安全性。
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
repo1-retention-full-type=count
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=替换成32位以上随机串
compress-type=lz4
process-max=2
start-fast=y
delta=y
log-level-console=info
log-level-file=detail
log-path=/var/log/pgbackrest
[main]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
pg1-user=postgres
第二份是 postgresql.conf 里与归档相关的四行。archive_mode 改了必须重启,这一条没有例外。
wal_level = replica
archive_mode = on
archive_command = '/usr/bin/pgbackrest --stanza=main --config=/etc/pgbackrest/pgbackrest.conf archive-push %p'
archive_timeout = 60
max_wal_senders = 3
第三份是权限。archive_command 是以 postgres 身份执行的,它读不到 root 的 640 文件,这是第一次部署最常见的失败点。
chown root:postgres /etc/pgbackrest/pgbackrest.conf
chmod 640 /etc/pgbackrest/pgbackrest.conf
chown postgres:postgres /var/log/pgbackrest
systemctl restart postgresql@16-main
第四份是定时任务。全量放周末凌晨,增量放每天凌晨,两次之间靠 WAL 兜底,这个组合的恢复窗口最短。用 systemd timer 或 root 的 crontab 都可以,注意任务里的用户必须是 postgres。
# /etc/cron.d/pgbackrest
30 2 * * 1-6 postgres /usr/bin/pgbackrest --stanza=main --type=incr backup
30 2 * * 0 postgres /usr/bin/pgbackrest --stanza=main --type=full backup
0 6 * * * postgres /usr/bin/pgbackrest --stanza=main check
4. 验收:四步,一步都不能跳
stanza-create 建的是仓库侧的目录结构与元数据,它不会替你检查归档是否通。真正的验收是 check,它会在主库上做一次模拟归档。
- 建 stanza:
sudo -u postgres pgbackrest --stanza=main stanza-create - 连通性检查:
sudo -u postgres pgbackrest --stanza=main check,输出必须出现 archive-push 与 archive-get 两条 ok。 - 首次全量:
sudo -u postgres pgbackrest --stanza=main --type=full backup,结束后立刻再跑一次 check。 - 看清单:
sudo -u postgres pgbackrest --stanza=main info,确认 status 为 ok,且 WAL 归档区间是连续的。
如果第 2 步报「unable to load config」,回到第三份配置查权限。如果报「archive-push timeout」,先看 pg_wal 里堆了多少文件。pg_wal 里超过 20 个待归档文件,说明归档已经失败了一段时间。
5. 恢复到一台新机器
恢复的目标路径必须为空或者是一个已停库的旧目录。下面这条命令把副本还原到指定时刻并直接 promote,参数顺序不能乱。带时区的时间是这里唯一的硬要求,漏了时区就等于换了个时刻。
systemctl stop postgresql@16-main
sudo -u postgres pgbackrest --stanza=main \
--delta \
--type=time "--target=2026-11-11 14:32:10+08" \
--target-action=promote \
--pg1-path=/var/lib/postgresql/16/main \
restore
systemctl start postgresql@16-main
sudo -u postgres psql -c "select pg_is_in_recovery();"
返回 f 表示已经脱离恢复状态,可以读写。返回 t 说明还在重放,要么时间点没到,要么 WAL 缺了。恢复失败的九成原因是 WAL 不连续,不是备份集坏了。
恢复到新机器时,把 --pg1-path 指到新目录,并在恢复完成后单独改 port 与 data_directory,不要两台机器同时用 5432 起在一个网段里。恢复演练最忌讳「在原机上试」,一次失误就把证据也一起覆盖了。
6. 本机磁盘坏了怎么办
上面那套配置把仓库放在本机第二块盘上,它能扛住数据盘坏,扛不住整机丢失。要扛这一层,得把 repo1 挪出去。备份的地域冗余不是加分项,是备份这个词的组成部分。
pgBackRest 支持把仓库放在 S3 兼容对象存储上,核心是四个参数:repo1-type=s3、repo1-s3-bucket、repo1-s3-endpoint,以及走环境变量传入的密钥对。也可以在本机保留 repo1,再挂一个 repo2 到远端,做双仓库。双仓库的意义不是更快,是任何一边坏了另一边还能单独恢复。
代价是恢复时间。走公网的对象存储,恢复一个 200 GB 的库可能要按小时算,而不是按分钟。异地仓库救的是「数据还在」,不是「马上能用」。所以本地 repo1 留着,远端只做第二份,顺序别反。
7. 五个必然踩到的坑
- 配置文件权限。root 建的 640 文件,postgres 读不到,archive_command 每条都失败,但数据库本身照常运行,日志里只有一行 warning。等到要恢复才发现三个月没有 WAL。
- archive_command 用了相对路径。systemd 起的 PostgreSQL 环境变量很干净,PATH 里未必有 /usr/bin。写绝对路径,别赌。
- 先开 archive_mode 再建 stanza。顺序反了会报 archive_mode 未启用;而且改 archive_mode 必须重启,重载配置不生效。
- target 时间不带时区。服务器时区是 UTC、业务时区是 +08 时,你写的「14:32」会被当成 UTC 14:32,实际多恢复了八小时的数据,把误删的表一起恢复回来了。
- 只设了 retention-full,没管归档。WAL 归档不设保留策略就会一直长,仓库从几十 G 涨到几 T,最后磁盘满、归档断、备份链从中间裂开。
这五个坑的共同点是:它们都不会让数据库当场挂掉,只会让备份在某一刻静默失效。所以 check 要放进定时任务,而不是等人想起来。
8. 每月该跑一次的清单
- 跑一次 check,确认 archive-push / archive-get 都 ok。
- 看 info 输出,确认最近一次备份的 timestamp-stop 在 26 小时以内。
- 确认 pg_wal 目录内待归档文件数小于 5。
- 在测试机上做一次 –type=time 恢复,恢复到昨天某个整点。
- 恢复后核对三张核心表的行数与最大 id,与主库比对。
- 确认 repo 大小增长曲线没有跳变,跳变通常意味着增量链断了在重做全量。
这份清单里最贵的是第四条。它要花半小时,还要占一台机器。但这半小时是唯一能证明「备份存在」的实验。
写在最后
备份这件事有一个反直觉的地方:做得越好,越没有人记得它。系统三年不出事,这套配置就一直躺在 /etc 里,没人看,也没人改。它的成功表现是「什么都没发生」。
所以真正要维护的不是配置文件,是那条每月跑一次的恢复演练。人一旦停止验证,就会开始相信日志。而日志只证明工具跑过了,不证明数据还在。
你上一次真正从备份恢复出一个能用的库,是什么时候?如果答案想不起来,那台机器现在其实没有备份。

评论 (0)