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 保证恢复要么完整成功要么完全回滚,不会出现恢复了半套表的状态。
恢复之后的断言才是关键,脚本逐项检查下面这些。
- 迁移记录是否完整(drizzle 的迁移日志行数是否符合预期)
- 关键约束是否还在,比如余额非负的 check 约束、兑换码和邀请码的唯一索引、购买表的外键
- 账务一致性,每个用户的余额是否等于其最新一条流水的
balance_after - 购买价格快照没有负数
这些检查抓的是”数据恢复了但语义坏了”的情况。dump 文件能导入不代表业务数据是对的,约束丢了、快照错了,表面上都看不出来。
脚本成功返回 0,失败保留错误日志并返回非零,可以直接挂进 cron 定期跑。
演练频率
恢复检查脚本配合一个临时数据库,每周跑一次的成本几乎为零。真正的数据丢失场景里,你没时间现场研究 pg_restore 的参数。