Skip to content

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

脚本执行以下门禁:

  1. 查询当前 XHIM Schema 和非敏感数据库身份指纹;
  2. pg_dump --format=custom --no-owner --no-privileges 生成逻辑备份;
  3. 再次查询 Schema,迁移在备份期间发生则失败;
  4. 生成 canonical backup-metadata.jsonSHA256SUMS
  5. 将已打开的 dump inode 复制为匿名、仅当前进程持有的 FD 快照;
  6. 对该同一快照计算 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.sql

Hash 相同但 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 都在未过期窗口内。通过后按顺序执行:

  1. 停止发布写入,确认备份已离线校验并在隔离库完成恢复演练;
  2. 摘除一个 Server 副本,将它作为 Migration Leader 启动 N;
  3. XHIM 的 advisory lock 保证只有一个实例执行前向 Migration;
  4. Leader Ready 后验证 N-1 副本仍能在新 Schema 上读写;
  5. 每次只摘除、升级、恢复一个副本,Ready/WSS/消息幂等正常后再继续;
  6. 全部到 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,应:

  1. 暂停写流量;
  2. 把迁移前备份恢复到一个新的隔离数据库;
  3. 完成 Hash、Schema、租户隔离和业务抽样验证;
  4. 由变更审批决定受控切换连接或前向修复。

逻辑 pg_dump 备份是迁移保险和演练输入,不替代生产 PostgreSQL 的加密物理 备份、连续 WAL/PITR、跨故障域复制和定期保留策略。

XHIM 客户端 SDK 与服务端文档