主题
XHIM PostgreSQL 备份、恢复演练与滚动升级
这些工具只负责 XHIM PostgreSQL 逻辑备份、离线校验、向显式目标库恢复以及 发布前置门禁。它们不会创建数据库、删除数据库、执行 DROP、自动清空目标库, 也不会回退已执行的 Schema Migration。
所有连接 URL 必须由 Secret Manager、受保护 CI Variable 或当前 shell 环境提供。脚本不接收 URL 命令行参数,不把 URL、用户名或密码写入元数据和日志。
1. 创建迁移前备份
输出目录必须是绝对路径、父目录已存在且目标本身不存在:
bash
export XHIM_POSTGRES_SOURCE_URL='<从 Secret Store 注入>'
server/scripts/postgres/backup.sh \
--output-dir /secure/xhim-backups/1.2.3-before-migration \
--server-version 1.2.3脚本执行以下门禁:
- 查询当前 XHIM Schema 和非敏感数据库身份指纹;
- 用
pg_dump --format=custom --no-owner --no-privileges生成逻辑备份; - 再次查询 Schema,迁移在备份期间发生则失败;
- 生成 canonical
backup-metadata.json和SHA256SUMS; - 将已打开的 dump inode 复制为匿名、仅当前进程持有的 FD 快照;
- 对该同一快照计算 Hash,并调用
pg_restore --list验证 custom archive TOC。
备份包只包含:
text
xhim-postgres.dump
backup-metadata.json
SHA256SUMS元数据绑定 product=XHIM、Server SemVer、数据库 Migration 文件版本、UTC 时间、文件大小/SHA-256 和数据库身份的 SHA-256 指纹。身份原文、连接 URL 和 密码不会进入备份包。
输出目录、备份目录及其中的文件拒绝符号链接、特殊文件、大小写冲突和覆盖。 /、/tmp、/var、用户主目录等危险根路径不能直接作为输出目录。 脚本失败时不会删除或覆盖已有文件;失败产生的新目录应由值班人员保留调查或换 一个新目录重试。
2. 离线校验
校验机只需要备份包、Python、pg_restore,不需要数据库密码:
bash
server/scripts/postgres/verify_backup.sh \
--bundle-dir /secure/xhim-backups/1.2.3-before-migration \
--expected-server-version 1.2.3 \
--expected-schema-version 0011_push_device_admin.sqlHash 相同但 archive TOC 已损坏仍会失败。Hash 和 TOC 检查读取同一个匿名 FD 快照;校验过程中原子替换磁盘上的 dump 只会影响后续新操作,不能改变本次 校验输入。把“文件存在”当成“备份可恢复”是不允许的。
3. 恢复到显式隔离空库
先由 DBA 在隔离 PostgreSQL 集群显式创建空数据库和最小权限账号。脚本不会代为 创建、删除或选择默认数据库:
bash
export XHIM_POSTGRES_RESTORE_URL='<隔离空库 URL,从 Secret Store 注入>'
server/scripts/postgres/restore.sh \
--bundle-dir /secure/xhim-backups/1.2.3-before-migration \
--expected-server-version 1.2.3 \
--expected-schema-version 0011_push_device_admin.sql恢复前会再次完整校验备份,拒绝以下情况:
- 目标身份等于备份源身份,包括使用另一账号连接同一个源数据库;
- 目标存在用户表、序列、视图或其他用户对象;
- 备份 Product、Server/Schema Version、Hash 或 custom TOC 不匹配。
恢复进程先从一个以 O_NOFOLLOW 固定打开的源 inode 制作 0600 匿名 FD 快照。 元数据 Hash、pg_restore --list 和实际 pg_restore 均读取这一个 inode; 目标身份/空库检查期间即使有人用 rename 原子替换原始 xhim-postgres.dump,也无法改变已批准的恢复输入。快照不暴露可复用文件名, 进程退出即释放。
极少数受控环境会预建扩展或管理对象。只有同时传入下面两个参数才允许继续:
text
--allow-nonempty
--nonempty-confirmation XHIM_CONTROLLED_NONEMPTY_RESTORE_NO_DROP即使受控放行,恢复仍不使用 --clean 或 --create,不会隐式 drop;对象冲突 会让单事务恢复失败。该开关不能用于生产原库。
4. 自动恢复演练
每季度、每次数据库 Migration 前后至少执行一次:
bash
server/scripts/postgres/restore_drill.sh \
--bundle-dir /secure/xhim-backups/1.2.3-before-migration \
--report-file /secure/xhim-drills/1.2.3-restore-drill.json \
--expected-server-version 1.2.3 \
--expected-schema-version 0011_push_device_admin.sql演练只接受空目标库,恢复后核对 Migration 版本和核心表集合,并以 canonical JSON 写入不覆盖的报告。报告只保存目标数据库身份指纹,不保存连接信息。
5. N/N-1 滚动升级
复制并填写 server/deploy/postgres-compatibility.template.json。正式声明必须同时列出本次 版本 N 和上一个版本 N-1、目标 Schema,以及经过测试的兼容窗口截止 UTC。 模板中的 REPLACE_... 不能通过门禁。
迁移前执行:
bash
server/scripts/postgres/rolling_upgrade_preflight.sh \
--bundle-dir /secure/xhim-backups/1.1.0-before-migration \
--compatibility-file /secure/releases/1.2.0-postgres-compatibility.json \
--from-server-version 1.1.0 \
--to-server-version 1.2.0门禁验证备份确属当前在线库、在线 Schema 未在备份后改变、兼容声明对应当前 Release 的最新 Migration,且 N/N-1 都在未过期窗口内。通过后按顺序执行:
- 停止发布写入,确认备份已离线校验并在隔离库完成恢复演练;
- 摘除一个 Server 副本,将它作为 Migration Leader 启动 N;
- XHIM 的 advisory lock 保证只有一个实例执行前向 Migration;
- Leader Ready 后验证 N-1 副本仍能在新 Schema 上读写;
- 每次只摘除、升级、恢复一个副本,Ready/WSS/消息幂等正常后再继续;
- 全部到 N 后保留 N/N-1 兼容窗口并持续观察错误率、延迟和数据库锁。
Migration 必须遵守 expand-and-contract:N 阶段只新增可空字段/表/索引或提供 兼容默认值;删除/改名等 contract 操作至少推迟到 N-1 退出兼容窗口后的后续 版本。
6. 应用回滚,不盲回数据库
发生问题时先检查旧应用是否仍声明兼容当前在线 Schema:
bash
server/scripts/postgres/rollback_preflight.sh \
--compatibility-file /secure/releases/1.2.0-postgres-compatibility.json \
--rollback-server-version 1.1.0通过后只能逐副本回滚应用镜像,数据库保留已经前向执行的 Schema。仓库没有 down migration,禁止手工反向执行 SQL 或用旧备份覆盖在线库。
如果旧应用不兼容当前 Schema,应:
- 暂停写流量;
- 把迁移前备份恢复到一个新的隔离数据库;
- 完成 Hash、Schema、租户隔离和业务抽样验证;
- 由变更审批决定受控切换连接或前向修复。
逻辑 pg_dump 备份是迁移保险和演练输入,不替代生产 PostgreSQL 的加密物理 备份、连续 WAL/PITR、跨故障域复制和定期保留策略。