记录(08-23-isolated-backup-restore-drill): 固化Attempt 4失败复盘与修订提案
这个提交包含在:
@@ -112,6 +112,22 @@ Attempt 3 只留下 `database_restore_failed` 聚合类别,失败清理同时
|
|||||||
- 权限 `0600` 的私有原始日志只允许初始化时截断一次;容器启动、dump list、目标确认、restore apply 和附件恢复各写固定阶段标记并使用追加模式。这样后续阶段失败时仍能在本轮 runtime 内交叉核对之前阶段,最终清理和脱敏输出边界不变。
|
- 权限 `0600` 的私有原始日志只允许初始化时截断一次;容器启动、dump list、目标确认、restore apply 和附件恢复各写固定阶段标记并使用追加模式。这样后续阶段失败时仍能在本轮 runtime 内交叉核对之前阶段,最终清理和脱敏输出边界不变。
|
||||||
- 上传前由合同脚本对实际候选文件生成/校验 SHA-256;ARM 安装前再次校验同一摘要。证据记录合同脚本提交、候选摘要和 PASS,不记录地址、路径或业务数据。
|
- 上传前由合同脚本对实际候选文件生成/校验 SHA-256;ARM 安装前再次校验同一摘要。证据记录合同脚本提交、候选摘要和 PASS,不记录地址、路径或业务数据。
|
||||||
|
|
||||||
|
## Attempt 4 后的 PostgreSQL 稳定就绪修订提案
|
||||||
|
|
||||||
|
Attempt 4 在 `dump_list=PASS/0` 后、实际 restore apply 前以 `restore_target_confirmation_failed` 失败关闭。只读环境差异调查确认:runner 以第一次 `pg_isready` 成功作为容器就绪,而合同夹具因曾观察到 PostgreSQL 首次初始化临时服务关闭窗口,已改为连续两次真实查询成功;两者没有复用同一就绪实现。该问题按 `local_contract_vs_arm_environment_difference_investigation` 登记,高置信度根因为 `postgres_initialization_readiness_race`,不能归因于备份不可恢复。
|
||||||
|
|
||||||
|
修订只允许改变跟踪 runner 与其可复跑合同,不修改 `scripts/restore-db.sh`,不放宽目标身份、归档、shadow、无端口、恢复非零失败关闭或清理门禁:
|
||||||
|
|
||||||
|
1. 删除 runner 的“首次 `pg_isready` 成功即通过”判定。`pg_isready` 最多可作为诊断,不能成为允许 restore 的充分条件。
|
||||||
|
2. `dump_list` 保持不连接目标数据库的独立阶段;其成功只证明 custom dump 目录可解析,不计入数据库稳定就绪次数。
|
||||||
|
3. `target_confirmation` 在最多 60 次、每次间隔 1 秒的有界窗口内执行真实 `SELECT current_database()`。每次查询退出码非零时把连续成功计数重置为 `0`;查询成功且数据库名精确等于本轮 shadow 目标时累计,只有连续两次成功才 `PASS`;查询成功但名称不匹配立即失败,不继续重试。
|
||||||
|
4. 脱敏输出增加 `target_confirmation_exit_code`、`target_name_match=yes|no|NOT_RUN`、`target_confirmation_attempt_count` 和 `target_confirmation_success_streak`。退出码取最后一次实际查询的非负整数;未执行为 `NOT_RUN`。错误类别区分 `TARGET_QUERY_NONZERO` 与 `TARGET_NAME_MISMATCH`,不得输出查询文本、数据库名、路径、凭据或原始 stderr。
|
||||||
|
5. 稳定就绪函数必须定义在被渲染 runner 中;合同从 rendered runner 提取或直接驱动该同一实现,不得在 fixture 外围另写一套更宽松或更稳定的等待逻辑。fixture 自身如需等待,也必须通过同一实现进入后续真实脚本双路径回归。
|
||||||
|
6. 回归至少覆盖:首次查询失败后连续两次成功可继续;一次成功后失败会重置 streak;数据库名错配立即失败;查询持续非零在第 60 次后失败;每条失败路径的 apply 保持 `NOT_RUN`、诊断先于清理且残留为 `0`;原有 dump、restore 双路径、身份、migration、附件和敏感输出合同继续全绿。
|
||||||
|
7. 禁止用固定长 sleep、增加 ARM 特例、降低连续成功次数、跳过名称精确比较或只修改合同夹具来修复。若相同实现的本地合同通过而 ARM 仍在该阶段失败,保留环境差异 finding,依据新退出码和尝试计数继续调查,不现场放宽。
|
||||||
|
|
||||||
|
本节只是待审设计,不构成修改 runner、连接 ARM 或执行 Attempt 5 的授权。修复通过后仍须提交可追溯变更、重新选择当时最新批次、重跑只读身份探针、重新渲染并核对候选 SHA-256,再单独申请 Attempt 5 实际恢复。
|
||||||
|
|
||||||
## 清理与回滚
|
## 清理与回滚
|
||||||
|
|
||||||
- 生产无变更,无生产回滚步骤。
|
- 生产无变更,无生产回滚步骤。
|
||||||
|
|||||||
@@ -107,7 +107,21 @@
|
|||||||
- [x] 私有原始日志保留唯一初始化截断,五个固定阶段均以标记分隔并追加写;静态合同拒绝后续阶段再次截断。
|
- [x] 私有原始日志保留唯一初始化截断,五个固定阶段均以标记分隔并追加写;静态合同拒绝后续阶段再次截断。
|
||||||
- [x] 完整复跑 runner 合同、既有隔离备份/恢复演练和自动异机备份 74 项;task、Trellis、process、默认/定向敏感扫描及空白门禁通过,测试容器和本地私有路径残留均为 `0`。
|
- [x] 完整复跑 runner 合同、既有隔离备份/恢复演练和自动异机备份 74 项;task、Trellis、process、默认/定向敏感扫描及空白门禁通过,测试容器和本地私有路径残留均为 `0`。
|
||||||
- [x] Attempt 4 最新批次只读探针重新执行并通过;候选已由提交 `5ec7db1` 的跟踪模板确定性渲染,完整合同、依赖钉住和 SHA-256 复核通过。
|
- [x] Attempt 4 最新批次只读探针重新执行并通过;候选已由提交 `5ec7db1` 的跟踪模板确定性渲染,完整合同、依赖钉住和 SHA-256 复核通过。
|
||||||
- [ ] Attempt 4 候选尚未上传或安装;实际恢复须在本次探针和候选证据审查后另行取得明确批准。
|
- [x] 用户批准 Attempt 4 后,候选在 ARM 安装前再次通过 SHA 清单与语法校验并执行;`dump_list=PASS/0`,随后在 `target_confirmation` 阶段失败,实际 restore apply、migration、关键表和附件均未运行。
|
||||||
|
- [x] Attempt 4 runtime、临时 root 入口和普通用户上传暂存均清理为 `PASS/0`;按预先裁决规则转入本地合同路径与 ARM 实际环境的只读差异调查,不直接修补 runner 或创建 Attempt 5。
|
||||||
|
- [x] 只读差异矩阵与本地精确复现收敛到 PostgreSQL 初始化就绪竞态:合同夹具等待连续两次真实查询,runner 仅等待首次 `pg_isready`;本地稳定数据库上的同一 target query 为 `PASS/0`,ARM 所有临时对象复核残留 `0`。
|
||||||
|
- [x] Attempt 4 后正式归档只读探针与执行前为同一构建和同一 manifest 脱敏摘要,run id 配对、产物 SHA-256、24 小时新鲜度均通过;探针入口与暂存残留 `0`,正式归档内容级零修改已补证。
|
||||||
|
- [ ] 演练后腾讯云生产 `/ready` 尚未独立补证;须记录正常状态和 12 位构建前缀,不能由 ARM timer、归档探针或清理 PASS 代替。
|
||||||
|
- [ ] 修复与任何 Attempt 5 尚未批准;先审阅 `design.md` 的“PostgreSQL 稳定就绪修订提案”,仅批准后才开始以下本地工作。
|
||||||
|
|
||||||
|
### Attempt 5 前仅本地修复计划(待批准)
|
||||||
|
|
||||||
|
1. 在跟踪 runner 中以最多 60 次、间隔 1 秒、连续两次真实 `SELECT current_database()` 成功且名称精确匹配,替换首次 `pg_isready` 即通过逻辑;名称错配立即失败,查询非零重置连续成功计数。
|
||||||
|
2. 增加 target query 退出码、名称匹配、尝试次数和连续成功次数的 allowlist 诊断,区分查询非零与名称错配;私有原始日志、失败先诊断后清理和零残留边界不变。
|
||||||
|
3. 让可复跑合同直接驱动 rendered runner 中同一稳定就绪实现;删除 fixture 外围与 runner 不同源的稳定化旁路。
|
||||||
|
4. 补齐首次失败后恢复、streak 重置、名称错配立即失败、永久查询失败有界退出四类回归,并复跑真实 `restore-db.sh` Docker stdin/host-file、源/副本身份、dynamic migration、附件安全、敏感输出和清理合同。
|
||||||
|
5. 确认 `scripts/restore-db.sh` 字节未改,运行 task、Trellis、process、docs、敏感扫描和空白门禁;通过后以中文提交引用本 task。
|
||||||
|
6. 本地修复阶段不连接 ARM、不生成或安装 Attempt 5 现场入口。提交后重新选择最新批次并执行只读探针、重新渲染候选和核对 SHA-256;实际恢复须再次单独批准。
|
||||||
|
|
||||||
## 验证命令类别
|
## 验证命令类别
|
||||||
|
|
||||||
|
|||||||
@@ -325,3 +325,78 @@ Attempt 4 的 runner 产生过程已从个人 workspace 人工产物改为“跟
|
|||||||
完整 runner 合同再次全部 `PASS`:源/副本身份、三条身份负例、dynamic migration、附件安全、五阶段追加日志、失败先诊断后清理、真实 `restore-db.sh` Docker stdin/host-file 双路径、退出码和敏感输出边界均通过。私有候选包共 6 个文件(runner、附件校验器、两份现有恢复脚本、sudoers、SHA 清单),包内 SHA 清单复核 `PASS`;runner 钉住的三份依赖摘要全部匹配,sudoers 固定命令与 runner 自身路径一致,`scripts/restore-db.sh` 相对生产构建仍无字节漂移,本地测试容器残留 `0`。
|
完整 runner 合同再次全部 `PASS`:源/副本身份、三条身份负例、dynamic migration、附件安全、五阶段追加日志、失败先诊断后清理、真实 `restore-db.sh` Docker stdin/host-file 双路径、退出码和敏感输出边界均通过。私有候选包共 6 个文件(runner、附件校验器、两份现有恢复脚本、sudoers、SHA 清单),包内 SHA 清单复核 `PASS`;runner 钉住的三份依赖摘要全部匹配,sudoers 固定命令与 runner 自身路径一致,`scripts/restore-db.sh` 相对生产构建仍无字节漂移,本地测试容器残留 `0`。
|
||||||
|
|
||||||
准备过程有一次安全失败:首次来源 migration 枚举误把 `migration_lock.toml` 当作 migration 名称,现场显示 `66/65`。该错误发生在候选渲染前,没有产生候选;随后改为只读取来源提交 `server/prisma/migrations` 树下的目录对象,并与当前工作树目录集合独立比较,结果为 `65/65`、差异 `0` 后才生成私有配置。Attempt 4 若获批准,安装前仍须在 ARM 复核上述候选包 SHA 清单;批次年龄和最新批次身份也须以实际执行开始时为准,超过 24 小时或最新批次变化时重新探针,不沿用本节结论。
|
准备过程有一次安全失败:首次来源 migration 枚举误把 `migration_lock.toml` 当作 migration 名称,现场显示 `66/65`。该错误发生在候选渲染前,没有产生候选;随后改为只读取来源提交 `server/prisma/migrations` 树下的目录对象,并与当前工作树目录集合独立比较,结果为 `65/65`、差异 `0` 后才生成私有配置。Attempt 4 若获批准,安装前仍须在 ARM 复核上述候选包 SHA 清单;批次年龄和最新批次身份也须以实际执行开始时为准,超过 24 小时或最新批次变化时重新探针,不沿用本节结论。
|
||||||
|
|
||||||
|
## Attempt 4:目标数据库确认阶段失败关闭
|
||||||
|
|
||||||
|
日期:2026-08-25
|
||||||
|
|
||||||
|
本节仅追加 Attempt 4 原始脱敏结果,不改写前置探针、候选合同或环境差异预先裁决规则。
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| ARM 安装前候选包 SHA 清单 | `PASS` |
|
||||||
|
| 正式批次与私有副本前置门禁 | 已通过,否则不会进入 `restore_database` |
|
||||||
|
| dump list | `PASS`,退出码 `0` |
|
||||||
|
| target confirmation | `FAIL` |
|
||||||
|
| 失败阶段/类别 | `restore_database` / `restore_target_confirmation_failed` |
|
||||||
|
| 执行路径 | `DOCKER_STDIN` |
|
||||||
|
| restore apply | `NOT_RUN` |
|
||||||
|
| restore 脚本/真实 pg_restore 退出码 | `NOT_RUN/NOT_RUN` |
|
||||||
|
| pg_restore 错误计数 | `NOT_RUN` |
|
||||||
|
| 稳定错误类别 | `TARGET_CONFIRMATION_FAILED` |
|
||||||
|
| migration/12 类关键表 | `NOT_RUN/NOT_RUN` |
|
||||||
|
| 附件恢复与业务关联 | `NOT_RUN` |
|
||||||
|
| runtime/临时 root 入口清理 | `PASS/PASS` |
|
||||||
|
| 普通用户候选暂存 | `PASS`,残留 `0` |
|
||||||
|
| 结论 | `NO_GO` |
|
||||||
|
|
||||||
|
`dump_list=PASS` 只证明最新 custom dump 可由 ARM 的 PostgreSQL 16 工具读取目录;`target_confirmation=FAIL` 发生在实际恢复调用之前,因此本次没有数据库数据写入、migration 查询、关键表读取、附件解包或附件关联校验。数据恢复 RTO 未形成,实际执行时的精确 RPO 秒数未被失败输出保留;只能确认 runner 已在创建 runtime 前通过不超过 24 小时的硬门禁,不能用前置探针年龄冒充执行时精确 RPO。
|
||||||
|
|
||||||
|
本轮输出没有分别保留 target query 的退出码和“查询成功但数据库名不等”布尔值,现有证据尚不能区分连接/认证/工具调用失败与目标名称比较失败。正式归档在 runner 中只有读取和复制路径,未观察到归档写入;但失败发生在末尾源批次复校验之前,因此“正式归档零修改”仍需后续只读摘要复核,不以代码静态结论替代现场证据。演练后生产 `/ready` 也须另行补充,不能从 ARM timer 或清理 PASS 推断。
|
||||||
|
|
||||||
|
按 Attempt 4 开始前已提交的裁决规则,本次先登记为 `local_contract_vs_arm_environment_difference_investigation`,不是新的孤立 runner 缺陷,也不是备份不可恢复证据。下一步仅允许建立脱敏环境差异矩阵和补齐生产/归档只读复核;在差异证据、修订设计和新批准之前,不修改 runner、不创建 Attempt 5、不复用本批次盲重试。
|
||||||
|
|
||||||
|
## Attempt 4 失败后的只读环境差异调查
|
||||||
|
|
||||||
|
本节仅追加失败后的只读 ARM 信息与本地合成复现;未在 ARM 启动调查容器、未读取业务数据、未修改 runner 或正式归档。
|
||||||
|
|
||||||
|
| 维度 | 本地合同环境 | ARM 实际环境 | 判定 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| CPU/容器架构 | `x86_64` / `linux/amd64` | `aarch64` / `linux/arm64` | 不同 |
|
||||||
|
| Docker client/server | `25.0.4/25.0.4` | `28.0.2/28.0.2` | 不同 |
|
||||||
|
| PostgreSQL 16 镜像实例摘要前缀 | `fff3594bf464` | `c05eced0bdb4` | 多架构实例不同 |
|
||||||
|
| 存储/cgroup/rootless | `overlay2/systemd/no` | `overlay2/systemd/no` | 相同 |
|
||||||
|
| Bash/Python/OpenSSL/coreutils | `5.1.4/3.9.2/1.1.1w/8.32` | `5.1.4/3.9.2/1.1.1w/8.32` | 相同 |
|
||||||
|
| AppArmor/SELinux | `Y/unavailable` | `Y/unavailable` | 相同 |
|
||||||
|
| 工作文件系统 | `ext2/ext3` 类 | `ext2/ext3` 类 | 相同 |
|
||||||
|
| 必需命令 | 合同执行通过 | `PASS` | 无缺失证据 |
|
||||||
|
|
||||||
|
差异矩阵本身不能证明 Docker 28 或 arm64 有缺陷。真正收敛根因的是启动时序覆盖不一致:tracked runner 在启动 PostgreSQL 后以第一次 `pg_isready` 成功结束等待;本地合同夹具曾真实出现“初始化临时服务正在关闭”的失败,之后夹具改成连续两次真实 `SELECT 1` 成功才继续,但 runner 的等待实现没有同步修改。于是合同环境稳定化后绕过了 runner 的竞态,而 ARM 仍执行首次 `pg_isready` 路径。
|
||||||
|
|
||||||
|
失败序列与该机制一致:dump list 不连接目标数据库,因此可在 PostgreSQL 初始化服务切换窗口中 `PASS/0`;紧随其后的 target query 需要真实连接,命中关闭/重启窗口即进入 `TARGET_CONFIRMATION_FAILED`,actual restore 尚未调用。本地使用相同镜像配置、相同随机凭据和 runner 中完全相同的 target query,在连续两次真实查询确认稳定后得到退出码 `0`、数据库名精确匹配、stderr 为空;本地测试容器随后清理为残留 `0`。
|
||||||
|
|
||||||
|
当前高置信度根因类别为 `postgres_initialization_readiness_race`,上位 finding 仍为开跑前定义的 `local_contract_vs_arm_environment_difference_investigation`。系统性差异具体表现为:合同夹具和 runner 没有复用同一稳定就绪路径,架构/容器版本造成的时序差异把该覆盖缺口暴露在 ARM。修复方向应是 runner 与合同共同执行“连续成功的真实目标数据库查询”并保留 target query 数字退出码与名称匹配布尔值,而不是为 ARM 增加一次固定 sleep、放宽目标确认或再次只修测试夹具。该修复和任何 Attempt 5 均须先更新设计、补可复跑负例并重新批准。
|
||||||
|
|
||||||
|
失败后只读残留复核:一次性容器、runtime、root 入口、恢复库目录、sudoers 和普通用户暂存均为 `0`;ARM dead-man 与公网健康 timer 均为 `active`。演练后生产 `/ready` 与正式归档摘要在此时仍待独立只读补证。
|
||||||
|
|
||||||
|
## Attempt 4 后正式归档只读复核
|
||||||
|
|
||||||
|
日期:2026-08-25
|
||||||
|
|
||||||
|
本节追加 Attempt 4 后正式归档只读探针结果,不回写前置探针或 Attempt 4 原始失败记录。探针不复制、不解包、不恢复、不启动数据库容器,也不修改正式归档。
|
||||||
|
|
||||||
|
| 检查项 | 结果 |
|
||||||
|
| --- | --- |
|
||||||
|
| 只读探针 | `PASS`,退出码 `0` |
|
||||||
|
| 最新批次构建短标识 | `7b86ffb3`,与当前生产构建相同 |
|
||||||
|
| 探针时批次年龄 | `42007` 秒,约 11 小时 40 分,`within_24h=yes` |
|
||||||
|
| run id 与目录配对 | `PASS` |
|
||||||
|
| DB/附件产物完整性 | 大小与 SHA-256 聚合 `PASS` |
|
||||||
|
| source image digest | 存在 |
|
||||||
|
| manifest 脱敏摘要前缀 | `ee1f97a5b42d` |
|
||||||
|
| 探针临时访问清理 | `PASS` |
|
||||||
|
| 探针入口/暂存残留 | `0/0` |
|
||||||
|
|
||||||
|
Attempt 4 前后探针的最新构建短标识、run id 配对、产物完整性和 manifest 脱敏摘要前缀一致,后置探针仍在 24 小时内通过。由此正式归档的内容级零修改已取得现场只读补证;该结论仅覆盖正式归档,不替代恢复成功证明,也不回答最新备份是否可恢复。
|
||||||
|
|
||||||
|
腾讯云生产演练后 `/ready` 仍未独立补证。ARM 的 timer active、清理 PASS 和归档探针均不能推导生产在线状态;在取得 `/ready` 正常状态及 12 位构建前缀前,AC2 保持未满足,证据不得表述为生产前后均正常。
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户