跳至内容
35.7 实战:在克隆环境分类并恢复

35.7 实战:在克隆环境分类并恢复

前六节建立了抢救原则,本节把它们压进一次可复验的盲测。演练不是“制造一个错误, 然后按预先知道的答案修掉”,而是同时维护三条相互独立的证据链:

现场链:managed before/after + original case digest + known-good digest
判断链:blind packet -> classifier predicate -> route
恢复链:selected source -> transformation -> physical/business validation

如果分类器偷看答案、恢复动作改写原始 case,或验收只看进程启动,这三条链中至少有 一条会断。实验的价值正在于让这些捷径变成机器可拒绝的反例。

35.7.1 仅在启用 checksum 的专用镜像注入可控页异常

先读合同,再允许 mutation

实验入口不是脚本参数,而是六份可评审合同:

先做不接触 PostgreSQL 的静态检查:

static/labs/ch35/task.sh lint

预期输出至少包含:

status=lint-ok
declared_counterexamples=35-schema-valid
source_files_hash_bound=13
managed_reset_host_executed=false
production_ch35_gate=pending

如需核对当前 Pigsty sandbox,capture 只通过 SSH 读取目标身份、拓扑和服务投影,不改 数据库:

evidence_dir="$(mktemp -d /tmp/pg36-ch35-capture.XXXXXX)"
PG36_EVIDENCE_DIR="$evidence_dir" \
  static/labs/ch35/task.sh capture

完整演练则是 L3:会在 pg-test-3 创建私有临时 PostgreSQL 集群、停止并复制它, 再修改操作副本。因此 runner 要求五项精确 guard,缺一项就在创建远端目录之前失败:

evidence_dir="$(mktemp -d /tmp/pg36-ch35-evidence.XXXXXX)"

PG36_EVIDENCE_DIR="$evidence_dir" \
PG36_CH35_TARGET=pg36-l2-vagrant/pg-test \
PG36_CH35_NONPRODUCTION=true \
PG36_CH35_PRODUCTION_DATA=false \
PG36_CH35_PRODUCTION_TRAFFIC=false \
PG36_CH35_CONFIRM=CLONE_CLASSIFY_RECOVER_CH35 \
  static/labs/ch35/task.sh drill:forensics

这些值不是“我愿意承担风险”的通用开关。它们共同声明:目标正是本书的无数据、 无流量 sandbox,且授权范围只到 disposable clone。换成生产主机、生产数据或生产 流量后,本命令没有任何授权意义。

隔离布局

runner 在 pg-test-3 上创建:

/tmp/pg36-ch35-forensics-<UUID>/
  source/          checksum-enabled fixture
  known-good/      clean shutdown 后冻结的可信快照
  cases/           每个场景独立的 original evidence copy
  working/         修复或恢复所使用的新副本
  sockets/         mode 0700 的私有 Unix socket

临时实例设置 listen_addresses='',不进入 Patroni、DCS、HAProxy、PgBouncer、备份仓库 或业务路由。managed pg-test 只做 before/after 只读投影。cleanup 还必须同时满足:

  1. 路径符合 exact UUID root;
  2. root 中的 marker 与本次 run identity 相符;
  3. 所有临时 postmaster 已停止;
  4. original case 和 known-good 的保留性已经验证。

只有四项都成立,runner 才删除整个 exact root;它不会根据模糊 glob 删除目录。

物理页场景

fixture 是启用 data checksum 的 PostgreSQL 18.4 集群,包含 12,000 行确定性数据和 至少八个 heap block。源实例干净停止并冻结 known-good 后,runner 才为物理场景创建 独立 clone,并在 postmaster 已停止 时对目标 heap relation 执行:

block             2
offset in block   512
operation         byte XOR 0x01
bytes changed     1

这个动作的目的不是模拟真实磁盘故障机理,而是生成一个可重复的、checksum 能检测到的 坏页。实验同时保存 relation mutation 前后的 SHA-256;运行中的 relation file、 managed PGDATA 和唯一恢复来源永远不被改写。

随后取得两类互补证据:

offline: pg_checksums --check -> bad checksum = 1, exit non-zero
online:  sequential heap scan -> SQLSTATE XX001

离线检查回答“磁盘上的 checksum 是否匹配”,在线扫描回答“服务实际读取该页时发生 什么”。两者都不是修复动作。实验没有启用 ignore_checksum_failurezero_damaged_pages,也没有运行 pg_resetwal 或删除任何 pg_wal 文件。

35.7.2 另设索引或 collation 异常,随机隐藏故障类型

第二种故障故意不破坏 page

collation 场景从同一 known-good snapshot 创建另一份 case。它只在 disposable catalog 中临时允许 allow_system_table_mods,把实验专用 ICU collation 的 stored version 改成带 run identity 的假值:

actual version   153.121
stored version   0.pg36-ch35-278fcd34

这个构造只模拟“catalog 记录的版本与当前 provider version 不同”。它没有安装另一版 ICU,也没有证明比较函数或索引顺序真的变化。相应证据是:

offline bad checksums       0
stored != actual         true
bt_index_check passes    true
relation kind            index-derived

因此,“checksum 全绿”和“B-tree 结构检查通过”不能否定 collation-derived state 已经过期;反过来,version mismatch 也不能被夸大成 heap page 已损坏。

隐藏答案,分类证据

runner 随机安排两个场景,并给每个场景生成无语义的 case_id。classifier 只能读取 blind-packets.json 中七组字段:

case_id
observed_at
checksum
relation
collation
amcheck
business

场景标签与注入细节保存在独立的 hidden-answers.json,不作为 classifier 输入。合法 route 是显式、可评审的合取谓词:

RESTORE_FROM_KNOWN_GOOD_COPY
  checksum.enabled = true
  AND checksum.offline_bad_checksums >= 1
  AND relation.kind = heap
  AND collation.version_mismatch = false

REINDEX_AND_REFRESH_COLLATION
  checksum.offline_bad_checksums = 0
  AND collation.version_mismatch = true
  AND amcheck.structural_check_passed = true
  AND relation.kind = index-derived

若两个谓词同时成立,或两个都不成立,分类器必须返回:

STOP_AND_ESCALATE

未知状态不是“选一个最像的”。正确动作是保留原件、停止写入和重复实验,列出缺少的 事实与恢复来源,并在危险参数之前升级。

正式 run 278fcd34-48af-49e0-97c9-c5e5e4161c78 的随机顺序是:

顺序隐藏场景关键盲证据分类 route
1COLLATION_METADATAchecksum 0、version mismatch、amcheck passREINDEX_AND_REFRESH_COLLATION
2PHYSICAL_HEAP_PAGEchecksum 1、heap、scan XX001RESTORE_FROM_KNOWN_GOOD_COPY

这里公布 hidden truth 是实验完成后的教学复盘;分类发生时,答案并未进入算法输入。

35.7.3 用备份、重建或抽取恢复并验证不变量

物理异常:恢复新副本,不改现场

物理场景命中 RESTORE_FROM_KNOWN_GOOD_COPY 后,runner 不在坏页上写回正确 byte, 也不复制单个 block 覆盖现场。它从停止状态的 known-good snapshot 创建一份新的 recovery copy,再在新副本上完成:

pg_checksums --check       bad checksums = 0
amcheck                    pass
row_count                  12,000
sum_id                     72,006,000
sum_balance                588,702,000
ordered_content_digest     5c04d26539b8fa55f52e13e6a78f8bdd

四个业务不变量同时匹配才算实验恢复成功。row_count 能发现大块缺失,却发现不了值 被替换;两项求和能捕获部分数值漂移,却可能相互抵消;确定顺序的内容 digest 再覆盖 每行关键字段。真实系统还应加入账务平衡、对象存在性、外部事件和幂等副作用等业务 事实,不能照抄这四项就宣称可信。

原始 damaged case 在验证期间保持逐文件 digest 不变,直到最终统一清理。这样即使 恢复结果后来被推翻,调查者仍能从同一原件重新分叉。

collation 异常:先重建派生对象,后承认新版本

collation 场景也不直接操作 original case。runner 创建单独 working copy,并严格按 以下语义顺序执行:

REINDEX INDEX public.pg36_ch35_code_idx;
ALTER COLLATION public.pg36_ch35_icu REFRESH VERSION;

第一步让 exact dependent index 按当前 provider 规则重建,第二步才把 catalog 中 stored version 刷新为 actual version。若颠倒顺序,warning 会消失,但旧派生对象 可能仍由旧规则构造;那只是消音,不是修复。

正式结果:

repair order               REINDEX -> REFRESH VERSION
elapsed                    267.719 ms
version mismatch after     false
offline bad checksums      0
amcheck after              pass
four invariants            all match

实际事故不能仅凭 collation 名称猜依赖关系。应先枚举所有依赖对象,确认 index、 partition、materialized view、constraint 与应用排序语义,规划锁和容量,再对 exact 对象重建。若 heap、checksum 或业务证据同时异常,应回到扩大调查范围,而不是继续套用 这条单因果 route。

验证证据包,而不是相信终端输出

一次完整 run 的关键产物是:

evidence/
  before.json
  after.json
  classification.json
  negative-report.json
  public-summary.json
  validation-report.json
  review.txt
  exercise/
    blind-packets.json
    hidden-answers.json
    exercise-evidence.json
    cleanup.json
    run-manifest.json
    source-manifest.json

before.jsonafter.json 证明 managed system identifier、timeline 和 topology 没有变化;source-manifest.json 把 13 份 runner/contract 源文件绑定到 hash; run-manifest.json 绑定本次输入与输出;cleanup.json 证明 exact root 已删除且没有 临时 postmaster 遗留。raw evidence 保留在受控位置,对外只发布去敏摘要 rescue-run.json

对一个已有完整 bundle,可在不再连接或修改 PostgreSQL 的情况下复验:

PG36_EVIDENCE_DIR=/path/to/ch35-evidence \
  static/labs/ch35/task.sh all

all 只消费现有证据。正式 bundle 的 validator 不仅检查 happy path,还把合同的 35 个反例逐一变成 live mutant 并确认全部拒绝,覆盖:

  • 把生产数据或 managed PGDATA 伪装成实验目标;
  • 弱化 checksum 阈值或泄露 hidden answer;
  • 在 original case 上原地修复;
  • REFRESH VERSION、后 REINDEX
  • 业务 digest 漂移却宣称恢复成功;
  • 临时 postmaster 未停或 root 未删却宣称 cleanup 成功。

这类负向验证回答的是“哪些错误证据绝不能通过”,比再打印一次 status=completed 更能约束未来脚本回归。

35.7.4 用 reset:host 重建 Pigsty L3,不复用损坏现场

这是风险类别,不是复制粘贴命令

本书把 reset:host 用作一个内部风险标签:当操作系统、存储、package 或 PostgreSQL 基线不再可信时,从声明式 inventory 和可信数据源重建整台 L3。它不是 Pigsty 中已经 替读者填好目标的普通命令,也不授权删除任何 host。机器可读计划见 l3-rebuild-plan.json

宿主机重建前至少要具备:

  1. incident commander 明确 exact host、故障域与数据库权威所在;
  2. 原始证据和 storage snapshot 已保存在目标之外;
  3. 流量已排空,且健康节点或恢复环境拥有数据库权威;
  4. 已验证的 backup、健康 cluster source 或重建路径被明确命名;
  5. inventory、Pigsty release、package repository 与 secret source 全部固定版本;
  6. 替代容量、失败回退和生产 destructive approval 已就绪。

安全状态机是:

preserve + hash evidence
  -> fence suspect host from authority and routes
  -> provision clean host or verified clean storage
  -> apply pinned Pigsty node/PostgreSQL baseline
  -> restore trusted backup OR join as a fresh replica
  -> validate lineage/checksum/replication/route/backup/monitoring/business
  -> observe
  -> return traffic

关键字是 cleanfresh。不要把可疑 PGDATA 当作新实例恢复源,不要在未保存 唯一证据时格式化磁盘,也不要因为 service 成功启动就跳过 lineage 与业务验收。原 PGDATA 只有在证据保留期、合规义务和 incident owner 共同批准后,才进入单独的清理 流程。

本实验刻意停在生产门外

正式演练只验证了 L3 rebuild decision contract,没有执行 managed host 的移除、 重装或恢复:

managed_reset_host_executed = false
managed_pgdata_mutated      = false
managed_service_changed     = false
managed_route_changed       = false
production_ch35_gate        = pending

因此本节可以支持团队评审“何时应重建、重建应满足什么”,不能提供生产重建时长、 数据可恢复比例或变更批准。真实生产执行要重新解析 exact inventory、当前 Pigsty 版本、故障域、备份恢复演练结果和业务 RTO/RPO,并走独立破坏性门禁。

到这里,本章形成了一条完整原则:

保留原件,用证据分类;从可信来源生成新状态,用物理与业务不变量共同验收;只有在 基础设施可信度也恢复后,才让流量回归。


上一节:工程取证与业务验证 · 返回本章目录 · 下一章:事故复盘、控制固化与平台演进——举一反三 · 查看全书目录 · 查看索引中心

最后更新于