PostgreSQL 备份与恢复演练

备份这件事,写了脚本只完成一半。没恢复验证过的备份,和没有备份的差别只在心理安慰上。这套站点用的方案是两个脚本分工,一个负责产出备份,一个负责把备份恢复到临时库并逐项检查。

备份脚本的四个细节

脚本核心是 pg_dump 的 custom 格式。

pg_dump --dbname="$DATABASE_URL" --format=custom --no-owner > "$TMP"
mv "$TMP" "$TARGET"

围绕这两行有几个容易忽略的点。

先写临时文件再原子改名。pg_dump 跑到一半被中断(磁盘满、进程被杀)会留下一个不完整的 dump,恢复时才发现就晚了。写到 .partial 文件再 mv,目录里出现的每个 .dump 文件都是完整的。

本机没有 pg_dump 时回退到容器。

docker run --rm --network=host -e DATABASE_URL postgres:17-alpine \
  sh -c 'exec pg_dump --dbname="$DATABASE_URL" --format=custom --no-owner'

很多精简系统不装 PostgreSQL 客户端,而站点本身就跑着 postgres 镜像,直接借它的客户端用,不用额外装东西。

连接串只走环境变量,不出现在命令行参数里。ps aux 能看到所有进程的命令行,密钥写在那里等于公开。

保留策略内置在脚本里,默认留最近 14 份,删除前先打印待删文件清单。清理逻辑和备份逻辑放在同一个脚本里,才不会出现”只备份不清理,半年后磁盘满了”的结局。

恢复检查脚本

恢复脚本做两件事,把指定 dump 恢复到 RESTORE_DATABASE_URL 指向的库,然后跑一组断言。

pg_restore --dbname="$RESTORE_DATABASE_URL" --clean --if-exists --no-owner \
  --single-transaction --exit-on-error < backup.dump

--single-transaction 加 --exit-on-error 保证恢复要么完整成功要么完全回滚,不会出现恢复了半套表的状态。

恢复之后的断言才是关键,脚本逐项检查下面这些。

这些检查抓的是”数据恢复了但语义坏了”的情况。dump 文件能导入不代表业务数据是对的,约束丢了、快照错了,表面上都看不出来。

脚本成功返回 0,失败保留错误日志并返回非零,可以直接挂进 cron 定期跑。

演练频率

恢复检查脚本配合一个临时数据库,每周跑一次的成本几乎为零。真正的数据丢失场景里,你没时间现场研究 pg_restore 的参数。