跳转到主要内容

36 事故复盘、控制固化与平台演进——举一反三

服务恢复解决的是“现在还能不能用”,事故复盘要解决的是“为什么系统允许这条失效链 成立,以及下一次什么会不同”。如果复盘止于一份文档,系统没有发生任何变化;如果 行动止于“代码已经合并”,控制也未必真的有效。

本章把全书最后一个闭环写成:

restore user outcome
  -> stabilize correctness and headroom
  -> preserve what was known when
  -> explain trigger, amplification and failed defenses
  -> convert findings into owned controls
  -> verify effectiveness and expiry
  -> make the safer path a platform default

“无责”不是“无因”或“无责任”。它要求不把复杂系统失败压缩成人格评价,同时仍精确 记录谁以什么 role 对哪项控制、截止时间和验证证据负责。

学习完成标准

完成本章后,读者应能:

  1. 分开用户影响恢复、数据正确性、运行余量与 incident closure;
  2. 清点并回收临时降级、应急权限、路由旁路和被暂停的自动化;
  3. 为通知、观察窗口、证据保留和正式结案定义可验证条件;
  4. 用事件时间、采集时间和知识时间重建 append-only timeline;
  5. 区分 trigger、放大机制、failed defense 与潜在失效条件;
  6. 从技术、流程、组织和认知四个层面寻找 contributing factors;
  7. 写出不归罪个人、又不回避具体动作与决策缺陷的复盘;
  8. 区分当时可见事实、当时假设、后来证据与事后解释;
  9. 判断某个正确结果来自受控机制,还是仅仅来自运气;
  10. 评估日志、指标、trace、审计、时间同步和 retention 的证据质量;
  11. 把行动项写成 owner role、期限、控制类型、验证、失效与重验合同;
  12. 区分 prevent、detect、mitigate、recover 四类控制;
  13. 拒绝“加强意识”“以后注意”和“部署即关闭”等不可验收行动;
  14. 把教训回写到 SLI/SLO、runbook、恢复目标、安全假设和 ADR;
  15. 用 Pigsty inventory、模板、监控规则、safeguard 与演练承载平台控制;
  16. 将局部补丁升级为默认护栏,同时保留 exception、版本和退出路径;
  17. 从多个事故识别重复控制主题,但不把教学实验冒充生产缺陷;
  18. 产出 90 天路线,并用独立证据而不是 ticket 状态关闭控制。

三种关闭不能混为一谈

层次 关闭条件 不能替代它的信号
incident 用户影响、正确性、容量余量与运行路径稳定 endpoint 偶尔返回 200
postmortem 影响、时间线、因果、决策、未知项经相关方评审 文档创建成功
control action 指定验证通过,关闭证据被独立接受,重验时间已登记 PR merged / ticket done

行动关闭后,控制还可能因版本、流量、拓扑、人员边界或依赖变化而失效。因此控制注册表 需要 last_verified_atevidencevalid_untilrevalidation_days,而不是 永久绿色的 checkbox。

从四类演练提取控制主题

第 32~35 章分别保留了四条恢复路线:

章节 场景 主要决策 不能外推
ch32 误写与 PITR 排除错误 history,合并审计后的合法增量 sandbox timing 不是生产 RTO
ch33 主库失效 围栏、接纳新权威、对账 unknown、修复 lineage 受控停进程不是硬件/网络分区
ch34 flow 与 retention pressure 先分类,再限流或修复 owner fixture 阈值不是生产容量线
ch35 物理与派生损坏 从可信源恢复或重建 derived state 单字节/元数据注入不是真实介质故障

它们反复提示 observation contract、生产门禁、业务验收、精确作用域、unknown outcome、 未知分类停止线和 lineage/authority 七个主题。这里的措辞必须严格:

exercise exposed a control question
!=
production lacks this control

只有生产 inventory、流程、配置和演练证据完成评估后,某个主题才能被确认成实际缺口。

正式实验

本章的实验是完全离线的 postmortem compiler。它读取四份已冻结、去敏的公开摘要:

每个 observed fact 都绑定 source JSON Pointer、expected value、actual value 和 knowledge stage。编译器产出四份事故记录、七个跨事故控制主题、十二项 0~90 天参考 backlog 和覆盖 ch01~ch36 的能力评估合同。

正式 run 91c4464b-89f7-4145-9708-f07256d747ce

input evidence files hash-bound     4
incident records                    4
cross-incident themes               7
proposed control actions           12
roadmap phases                      3
capability chapters covered        36
live mutants rejected              36 / 36
production gaps confirmed           0
database / SSH connections          0 / 0
production mutations                0

36 个 live mutant 包括篡改源事实、把 sandbox 影响冒充真实用户、删除 action owner、 期限、验证或失效条件、自动批准生产动作、漏掉路线阶段,以及仅因能力地图完整就自动 认证读者。公开结果见 closure-run.json

本章边界

本章没有:

  • 分析任何真实生产事故、个人或客户数据;
  • 证明读者所在组织存在七个缺口;
  • 创建工单、发送通知、修改 Pigsty inventory 或执行 backlog;
  • 把第 32~35 章一次实验计时提升成 SLO/RTO;
  • 因为读者完成阅读而自动授予能力认证。

正式结论保持:

production_ch36_gate = pending
roadmap_status        = reference-proposal-requires-local-approval
learner_assessment    = not-assessed

阅读前后关系

本章目录

36.1 服务恢复不等于事件结束

36.2 从时间线建立因果链

36.3 证据质量与决策复盘

36.4 把行动项变成控制

36.5 回写 SLO、SOP 与架构 ADR

36.6 将控制固化到平台

36.7 实战:复盘四类事故并完成全书结业

权威参考

复盘与 SRE:

PostgreSQL:

Pigsty:


上一章:数据抢救与工程取证——起死回生 · 返回下卷导读 · 返回全书导读 · 查看全书目录 · 查看索引中心

36.1 服务恢复不等于事件结束

SELECT 1 成功、主端点重新可连或 Grafana 曲线回落,只能说明某个观察面在某一时刻 恢复。事件是否结束,还取决于用户结果、数据正确性、恢复能力、容量余量和临时控制 是否都回到可接受状态。

36.1.1 恢复用户影响、数据正确性与运行余量

用状态向量代替单点绿灯

事件恢复状态可以写成:

R=(U,D,H,P,O) R = (U, D, H, P, O)

其中:

U  user outcome:成功率、延迟、功能和影响人群
D  data:完整性、一致性、unknown outcome 与外部副作用
H  headroom:连接、CPU、内存、I/O、WAL、XID、容量余量
P  protection:HA、backup/archive、权限、围栏和回退能力
O  operations:监控、告警、自动化、值班和变更路径

只有各维都达到预先定义的 acceptance,才能从 active incident 进入观察。典型的反例:

表面恢复 尚未回答
应用成功率回升 超时请求究竟提交还是回滚
新主库可写 旧主是否已围栏、replica 是否同 lineage
磁盘空间释放 slot/XID owner 是否恢复、WAL archive 是否连续
PostgreSQL 启动 checksum、索引、业务不变量是否可信
PITR candidate 可查询 target 是否正确、合法 post-target 写是否对账

用户影响要从用户路径测量。数据库连接成功不是订单可提交,readiness probe 成功也不是 支付状态正确。至少比较 incident 前基线、影响窗口和恢复窗口:

user_journey: checkout_commit
sli_revision: checkout-v4
window:
  impact_start: ...
  mitigation_start: ...
  observation_end: ...
segments:
  region: [...]
  client_version: [...]
  operation: [...]
result:
  attempts: ...
  good: ...
  unknown: ...
  duplicate: ...
source_query_hash: ...

unknown 不能并入 success 或 failure;它需要 stable request token、业务 ledger、 outbox/inbox 和外部系统对账。

正确性恢复要写明 cutoff

“数据已经恢复”至少需要:

accepted source and system identifier/timeline
restore or reconciliation cutoff
confirmed affected object and row/event scope
versioned business invariant
external side-effect reconciliation
unrecoverable and still-unknown register
owner acceptance

验证查询本身也可能因 snapshot、时区、collation、replica lag 或遗漏 partition 而说谎。 保存 SQL/程序 hash、参数、执行角色、目标 endpoint 和 snapshot/cutoff。抽样可用于 早期判断,不能自动替代最终全量或风险加权验收。

运行余量是恢复的一部分

若服务只在当前流量下勉强稳定,下一次重试、checkpoint、autovacuum 或 backup 就可能 再次触发事故。对每个主要资源记录:

headroom=safe capacitycurrent demandsafe capacity \text{headroom} = \frac{\text{safe capacity} - \text{current demand}} {\text{safe capacity}}

safe capacity 来自压测与安全边界,不等同于理论最大值。观察:

  • active/queued connection 与 pool wait;
  • CPU run queue、memory pressure、swap/OOM 和 I/O latency;
  • WAL generation/archive/retention 与 filesystem free;
  • replica replay lag、slot restart LSN 与 backup freshness;
  • oldest xmin、freeze age、dead tuples 与 maintenance debt;
  • error budget burn、retry amplification 和降级队列积压。

事故后的补偿、缓存回暖、索引重建和备份会制造第二波负载;应纳入容量计划。

36.1.2 清理临时降级、应急权限和旁路配置

为每个临时动作建债务账本

incident commander 批准临时动作时就应同步登记回收条件:

temporary_control_id: TC-...
target_identity: ...
change:
  desired_before: ...
  emergency_value: ...
reason: ...
owner_role: ...
approved_at: ...
expected_effect: ...
stop_condition: ...
rollback_or_supersede: ...
expires_at: ...
verification_after_removal: ...

常见临时债务:

  • 只读、限流、功能开关、缩短队列或拒绝非关键工作;
  • 精确暂停 failover、backup、vacuum、发布或调度器;
  • 临时路由、旁路 endpoint、DNS/HAProxy 权重;
  • break-glass role、临时证书、放宽的网络来源;
  • 提高日志、采样或 trace 密度;
  • 临时增加资源、保留 replication slot 或 forensic clone;
  • 为抽取损坏数据而只在 clone 使用的危险参数。

不要在压力刚回落时机械执行“全部 revert”。临时限流可能仍在保护低余量系统,立即 撤掉会重启事故。先确认它是:

remove now
  原风险消失,移除不会突破余量

replace with permanent control
  临时动作有效,但实现、权限或可观测性不适合长期保留

retain with dated exception
  当前不能移除,有 owner、风险、补偿控制和到期日

从外向内、逐项回收

推荐次序随事故调整,但每次只改变一个可解释变量:

  1. 确认稳定基线与 rollback;
  2. 回收过期的 break-glass 权限、token 和会话;
  3. 恢复被暂停的监控、归档、备份、vacuum 与调度;
  4. 校正路由和服务发现,移除旁路;
  5. 分阶段撤销限流或降级,观察 user SLI 与资源余量;
  6. 恢复常规变更窗口;
  7. 验证 inventory、runtime 和 secrets source 没有漂移。

安全相关临时措施按“先建立替代保护,再移除旧保护”处理。不要为关闭 incident 而 先删证据 clone、storage snapshot、audit log 或 recovery backup;它们按证据保留 策略单独到期。

Pigsty 中比较 desired 与 observed

Pigsty inventory 描述期望集群、实例、服务、用户、数据库和参数;运行中的 PostgreSQL、 Patroni、HAProxy、PgBouncer 与监控则提供 observed state。事故后至少做三方对照:

version-controlled inventory
vs rendered configuration
vs runtime/catalog/topology observation

差异要么回写声明式配置并评审,要么从 runtime 清除。不要只修改现场,留下下一次 playbook 重跑会覆盖的“幽灵修复”;也不要未经 diff 把 inventory 全量重放到刚恢复的 系统。危险 playbook 使用 exact -l 目标、safeguard、preview 和独立批准。

36.1.3 通知、观察窗口与正式结案条件

恢复通知要说已知、未知和下一步

一次可信的恢复更新包含:

current user impact and affected segments
confirmed impact window
mitigation/recovery performed
data correctness and unknown-outcome status
temporary controls still active
what remains unverified
observation window and next update
owner/contact and escalation path

不要使用“完全恢复”“无数据丢失”这类超出证据的表述。如果结论只覆盖 fixture、区域、 时间 cutoff 或某类业务对象,就明确写出量词。安全、隐私、法律和客户通知由相应 owner 决策,工程团队提供事实、范围与置信度,不自行淡化或扩大。

观察窗口由失效周期决定

“观察 30 分钟”不是通用规则。窗口至少覆盖相关周期:

  • 高峰流量、重试和 backlog 排空;
  • checkpoint、WAL switch、archive 与 backup;
  • autovacuum/freeze 或维护任务;
  • replica catch-up、connection recycle、DNS/TTL;
  • cache warm-up、batch、settlement 或账务周期;
  • 临时控制撤销后的再暴露。

有些验证必须经过一个完整 backup + restore 或下一次业务结算,不能让 active incident 无限挂起;可以将事件关闭,同时把长期验证转成有 owner 的 control action。但交接 不能抹掉风险。

结案门

incident_closure:
  user_sli_accepted: true
  business_invariants_accepted: true
  unknown_outcomes_reconciled_or_owned: true
  capacity_headroom_accepted: true
  ha_backup_archive_monitoring_restored: true
  temporary_controls_accounted_for: true
  evidence_retention_recorded: true
  stakeholder_update_sent: true
  observation_window_passed: true
  residual_risks_owned: true
  postmortem_trigger_decided: true

postmortem_trigger_decided 不等于“复盘已经写完”。达到上面条件后,事件可以从实时 响应转入学习与控制工作;后续三种状态分别追踪:

incident: closed
postmortem: draft -> reviewed -> published
actions: proposed -> implemented -> effectiveness-verified -> expired/revalidated

这能避免为了让 dashboard 上的 incident 数量归零,而提前把未验证行动标成完成。


返回本章目录 · 下一节:从时间线建立因果链 · 查看全书目录 · 查看索引中心

36.2 从时间线建立因果链

时间线回答“先后发生了什么”,因果分析回答“哪些条件共同使结果成为可能”。二者不能 互相替代:相关事件排在前面,不代表它导致后果;一个关键条件没有出现在日志里,也 不代表它不存在。

36.2.1 触发条件、放大机制与失效防线

先建立三种时间

每条 timeline event 至少区分:

event_time       被观察对象声称事件发生的时间
observed_at      collector 或人看到它的时间
recorded_at      它进入证据库的时间

再加:

source_clock / timezone / clock_offset
sequence or monotonic marker
source identity and hash
actor or automation
action / observation / decision
knowledge available at that moment
confidence and later correction

跨 PostgreSQL、Patroni、HAProxy、应用、主机和外部依赖时,wall clock 可能偏移;事务 XID、LSN、timeline、request token、日志 sequence 和 trace span 能建立局部 happens- XID、LSN、timeline、request token、日志 sequence 和 trace span 能建立局部的先于 关系(happens-before),但它们也不是一个全局时钟。不要为了画出整齐图表而把不确定 的秒级顺序伪造成毫秒精度。

因果链的五个位置

latent condition
  平时存在但尚未造成可见影响的条件

trigger
  让系统进入失效路径的事件

amplifier
  扩大范围、持续时间或恢复难度的反馈

failed / absent defense
  本应阻断、发现或减轻路径却没有生效的控制

impact
  用户、数据、安全、恢复能力或运营负担的结果

以一个假设性 PostgreSQL 连接事故为例:

latent:  pool 没有 admission budget,应用重试无 jitter
trigger: downstream latency 突升
amplifier: 请求超时 -> 立即重试 -> session/lock queue 继续增长
failed defense: user SLI 未触发值班告警,只有 node connection alert
impact: checkout timeout;后台任务挤占交互式容量

“连接数过多”只是中间状态;“调大 max_connections”可能增强放大器。好的分析会继续 问工作为何被允许无界进入、为何重试不知 commit outcome、为何隔离和降级没有生效。

用反事实检验边

对每条 A -> B 写出可反驳命题:

evidence for temporal ordering
mechanism connecting A to B
independent observation
counterfactual: if A were absent, would B still occur?
alternative explanations
confidence

反事实不是要求在线复现生产事故。可以用 trace、query plan、WAL/lock graph、隔离 fixture、历史对照或模型验证。若证据只支持相关性,就写“contributing hypothesis”, 不要升级成 root cause。

防线为什么失效

逐层检查:

防线 应回答
prevent 为什么错误配置、无界请求或危险变更能进入系统
detect 为什么没有在用户影响前/同时发现
mitigate 为什么隔离、限流、降级或围栏没缩小影响
recover 为什么 restore、failover、rebuild 或对账不够快/不够可信

“告警触发了”不等于 detect control 有效。若它晚于客户投诉、没有 route、缺少 owner 或每次都误报,它只生成噪声。

36.2.2 技术、流程、组织与认知因素

把 contributing factors 只写成技术缺陷,常常会在另一条路径重演同类事故。四个视角 互相约束:

技术

software defect / query plan / lock behavior
capacity and queue design
replication, WAL, backup and storage
topology, routing and failure domain
configuration/default/version interaction
observability and identity

例如 Patroni 正确 promotion 仍可能遇到应用没有幂等 token;backup 完整仍可能因目标 选择或业务 delta 缺失而恢复错误 history。

流程

review and approval
change staging and rollback
incident command and handoff
runbook decision points
backup/restore/failover exercise
action verification and expiry

不要把“runbook 没写”当终点。继续问:这个判断是否适合 runbook?信息能否自动采集? 当证据冲突时是否有 stop line?runbook 是否随当前版本演练过?

组织

service and data ownership
on-call authority
dependency contract
priority and staffing
incentive and delivery pressure
cross-team escalation

如果 database team 能看到 replication lag,却不知道哪个 slot 属于谁,问题不是 pg_replication_slots 文档不足,而是 retention owner 没有进入服务目录与升级路径。

认知

mental model available at the time
ambiguous names or dashboards
confirmation bias and anchoring
alert framing
hidden automation
training and experience

认知因素不是把责任还给个人。平台要让正确 mental model 更容易形成:明确 endpoint 语义、显示 target identity、把 evidence 与 inference 分栏、对危险动作展示前置谓词, 并让未知状态进入 STOP_AND_ESCALATE

建一张因素矩阵

失效环节 技术 流程 组织 认知
进入 无 admission limit 发布门未测压力 owner 不明 把 timeout 当失败
扩大 即时重试 无降级触发线 app/DB 各自优化 把 session 当容量
检测 缺 user SLI 告警未演练 告警 owner 空缺 dashboard 名称误导
恢复 无 token 对账 runbook 缺 stop 权威不明确 先重启再取证

矩阵不要求每格都填内容;它防止团队只在自己熟悉的层面找答案。

36.2.3 避免单一根因和个人归罪

“谁执行”仍是事实,“谁粗心”不是机制

可审计表述:

At T, the release role applied revision X to target Y.
The preview did not include generated lock acquisition.
The approval UI showed cluster name but not system identifier.
Rollback criterion was not defined before execution.

归罪表述:

某某不够谨慎。
值班同学经验不足。
操作失误导致事故。

前者保留动作、上下文和控制缺口,后者用人格标签替代可改变的系统条件。真正无责的分析 假设参与者在当时信息、目标、工具和压力下有局部合理性,然后追问系统怎样让危险动作 显得合理。

accountability 与 blame

无责不取消 accountability:

  • incident commander 对响应目标与决策节奏负责;
  • service owner 接受用户影响和 residual risk;
  • action owner role 按期交付验证证据;
  • reviewer 对结论的证据强度提出异议;
  • management 为优先级、资源和到期 exception 作决定。

故意违规、欺诈、骚扰或安全事件可以进入独立的人事、法律或合规程序;不要把该程序 塞进技术复盘,也不要用“blameless”掩盖它。技术复盘仍分析系统如何预防、检测和限制 后果。

不要寻找一个可删除的“根”

复杂系统通常有多条必要/充分关系:

trigger existed
AND guard absent
AND amplification active
AND detection late
AND recovery path unverified
-> observed impact

“五个为什么”可帮助继续追问,但线性链容易忽略并发分支。用 causal graph 表示:

flowchart LR
  A["配置变更"] --> C["查询成本上升"]
  B["统计信息过期"] --> C
  C --> D["请求超时"]
  E["立即重试"] --> F["队列放大"]
  D --> F
  G["无 admission gate"] --> F
  F --> H["用户错误率上升"]
  I["缺少 user-SLI page"] --> J["检测延迟"]
  J --> H

行动优先选能切断多条边、覆盖多类事故且可验证的控制,而不是只修最后一次 trigger。 例如目标身份 + exact scope 的发布门,可能同时降低误恢复、误切换、误删 slot 和误改 PGDATA 的风险。

复盘结论的证据等级

observed       source-bound direct evidence
corroborated   two independent observations agree
inferred       mechanism fits evidence, alternatives remain
hypothesized   plausible and testable, not yet verified
unknown        material fact unavailable

写作时保留等级。评审者要能指出哪条新证据会推翻结论;无法被反驳的“根因”通常也无法 指导一个可验证控制。


上一节:服务恢复不等于事件结束 · 返回本章目录 · 下一节:证据质量与决策复盘 · 查看全书目录 · 查看索引中心

36.3 证据质量与决策复盘

复盘拥有响应时没有的时间、权限和上下文,因此最容易犯 hindsight bias:把后来才知道 的答案投射给当时的人。决策质量必须按当时可用信息评估,结果质量则按后来完整 证据评估。

36.3.1 哪些事实当时可见,哪些后来才知道

建 knowledge timeline

普通 timeline 记录系统事件;knowledge timeline 记录响应者何时获得什么信息:

knowledge_id: K-...
observed_at: ...
available_to_roles: [...]
statement: ...
kind: observation | inference | report
source:
  system: ...
  object_identity: ...
  query_or_artifact_hash: ...
quality:
  directness: direct | derived | hearsay
  completeness: ...
  clock_uncertainty_ms: ...
interpretation_at_the_time: ...
decision_ids_informed: [...]
later_evidence: ...
correction: ...

后来发现原告警是 replica、不是 primary,不要覆盖旧记录。追加:

T1 observed: endpoint E reports recovery=true
T1 inference: E is believed to be current primary
T2 correction: inventory and DCS show E was a replica

这样才能问:“在 T1,接受写流量是否合理?”而不是用 T2 的答案责怪 T1。

四栏复盘表

当时可见事实 当时假设 后来证据 当前结论
client timeout 上升 primary 过载 proxy backend errors + DB 正常 故障在 route
WAL 目录增长 WAL 生成太快 slot restart LSN 不动 retention owner 阻塞
新节点可写 failover 成功 旧主仍可接受直连 authority 尚未安全
checksum clean 数据无损坏 业务 digest 漂移 page 完整性与语义不同

要求每个结论能回指源证据,并明确 source identity。截图可帮助人理解,但通常缺查询、 时间窗、变量、完整返回和 hash;关键结论保留机器可读原始投影。

决策日志与复盘文档分离

响应期间的 decision record 应 append-only:

decision_id: D-...
at: ...
objective: ...
known_evidence: [K-...]
hypotheses_considered: [...]
chosen_action:
expected_observation:
stop_condition:
rollback:
authority:

复盘可以评价该决定,却不能重写当时输入。若事故中没有决策日志,这本身就是一个证据 缺口;不要靠会后记忆补成精确逐分钟事实。访谈内容标记为 recollection,并与日志、 审计、metric 和 trace 交叉验证。

证据保真与最小披露

raw evidence 可能包含 query text、角色、IP、token、客户标识或 payload。采用两层:

restricted original
  immutable/retained/access-audited

review projection
  redacted, source-bound, sufficient for the claim

去敏不应破坏关联键;可用 stable pseudonymous token、区间、计数与 hash。复盘仓库只放 projection 和原件位置/权限,不复制 secret 或个人数据。

36.3.2 哪些假设被验证,哪些动作靠运气

好结果不能证明好决策

四种组合都值得复盘:

决策过程 结果 判断
证据充分、边界清楚 成功 机制候选,仍需复验
证据充分、边界清楚 失败 模型或实现有缺口
猜测、无 stop/rollback 成功 near miss / luck
猜测、无 stop/rollback 失败 显性事故

“重启后好了”只证明重启与恢复同时发生。它可能清掉等待队列、终止事务、触发 failover、 刷新 cache 或碰巧等到下游恢复;没有前后证据就无法选出机制,也无法知道丢了什么。

为每个关键动作做机制审计

preconditions
  执行前必须为真的事实是否被验证?

scope
  作用到 exact object/session/node/cluster 吗?

mechanism
  为什么它应改变目标症状?

expected
  多久、在哪个观察面看到什么?

stop / rollback
  哪个信号表明应停止或撤回?

result
  预期与实际是否一致?

repeatability
  在隔离环境重演后仍成立吗?

例如取消 exact application_name 会话后 lock waiters 清零,可以支持“这些 fixture session 构成 flow pressure”;它不能证明任意高连接事故都应取消会话。又如从 known-good snapshot 恢复后业务 digest 匹配,支持该 fixture 恢复路线;它不能证明 生产 backup 覆盖同一范围。

验证假设,而不是验证故事

每个 hypothesis 写:

hypothesis: H-...
prediction:
  if_true: ...
  if_false: ...
test:
  isolation: ...
  changed_variable: ...
  independent_observations: [...]
result:
alternative_explanations:
status: supported | weakened | rejected | unresolved

如果测试同时改变配置、版本、流量和拓扑,即使问题消失也无法归因。生产不能安全复现 时,在 clone、replay、模型或历史数据上验证,并把外推边界写清。

near miss 也进入控制系统

这些情况值得和事故一样记录:

  • 错命令被 safeguard 阻止;
  • 误切换前发现 system identifier 不符;
  • dangerous parameter 在唯一副本执行前被 review 拒绝;
  • restore candidate 偶然正确,但 target 没有审计来源;
  • unknown request outcome 恰好没有重复副作用。

near miss 提供了低损失的失效路径证据。若只统计造成用户影响的事件,平台会忽略已经 穿透多层防线、仅靠最后运气没有出事的路径。

36.3.3 告警、日志和时间同步缺口

从问题反推 observation contract

不要以“多收日志”为默认行动。先列事故中无法及时回答的问题:

问题 所需信号 identity/维度 retention
谁受影响 user SLI / request outcome service、operation、segment 至少覆盖 SLO 窗口
查询为何慢 wait、plan、query ID、I/O cluster、db、role、query 覆盖发布与周期负载
谁保留 WAL slot/subscriber/archive system id、timeline、slot owner 覆盖恢复窗口
谁拥有写权威 DCS、Patroni、timeline、route cluster/member/endpoint 覆盖 failover 前后
数据何时变化 audit/WAL/business event transaction/token/object 由 RPO、合规决定
恢复是否可信 backup/restore/business manifest source/cutoff/candidate 覆盖证据保留期

每个信号写 producer、collector、query、labels、刷新周期、缺失语义、owner、retention、 权限与成本。missing value 不能默认解释成 zero/healthy。

PostgreSQL 证据的时效与局限

PostgreSQL cumulative statistics、pg_stat_activitypg_stat_replicationpg_stat_replication_slotspg_stat_walpg_stat_io 等提供不同观察面,但要记录:

  • counter 是累计值还是当前 gauge;
  • stats reset、server restart 和 failover 是否改变基线;
  • 读取 snapshot、事务和刷新延迟;
  • query text 是否截断或因权限不可见;
  • standby 与 primary 的语义差异;
  • extension/版本是否改变列和统计;
  • NULL、空行与零值分别意味着什么。

日志应使用可解析格式(如 csvlog/jsonlog)和稳定关联字段,但 log_statement=all 可能暴露敏感数据并产生高开销。优先记录必要 identity、duration、SQLSTATE、query ID、 application/client context;query 参数和 payload 按数据分类处理。

指标、日志与告警要能互相落点

一条值班告警(page)应先表达 user impact 或 error-budget threat,再链接诊断上下文:

alert
  exact service + SLI window + burn/severity
  -> dashboard
     user outcome + dependency + PostgreSQL/host
  -> runbook
     evidence requests + route predicates + stop line
  -> raw source
     reproducible query/log projection

按每个 instance 发 30 条 alert 通常不如按 service impact 聚合一次,再保留 instance 分解。告警规则也需要单元/合成 time series 测试:正常、阈值边界、缺失数据、抖动和 长期低速 burn 都要覆盖。

时间同步是证据基础设施

至少监控:

clock source and synchronization state
offset / frequency error
last successful sync
host suspend/resume or VM migration
timezone configuration
collector ingestion delay

数据库 now() 是事务开始时间;statement_timestamp()clock_timestamp() 语义不同。应用、PostgreSQL log、systemd journal、proxy 和 monitoring timestamp 还可能分别来自 event time 与 ingest time。复盘合并前先统一 UTC 展示、保留原时区,并标注误差;不能靠肉眼把相近 timestamp 当因果顺序。

缺口行动要可验收

不要写:

增加更多监控。
完善日志。
保证时间准确。

写成:

owner: observability-platform
artifact: pg36_service user-SLI rule revision 3
test: synthetic 5% failure over declared windows
pass: fast window pages; slow burn records ticket; zero/missing remain distinguishable
clock test: inject collector offset fixture and reject order claims below uncertainty
evidence: rule test output + notification trace + dashboard link
revalidate: 30 days

观察能力只有在问题发生前存在、事件中可访问、事件后可重放时,才是一项控制。


上一节:从时间线建立因果链 · 返回本章目录 · 下一节:把行动项变成控制 · 查看全书目录 · 查看索引中心

36.4 把行动项变成控制

复盘行动项的目标不是让团队“做过一些事”,而是改变下一次失效路径的概率、可见性、 影响或恢复质量。能否关闭一项 action,取决于控制效果证据,而不取决于 ticket 状态。

36.4.1 所有者、截止时间、验证方法与失效条件

合格 action contract

id: ACT-...
finding_or_theme: ...
control_objective: ...
control_type: prevent | detect | mitigate | recover
owner_role: ...
decision_owner: ...
priority: P0 | P1 | P2
due_at: ...
scope:
  services: [...]
  versions: [...]
  environments: [...]
artifact: ...
verification:
  procedure: ...
  pass_condition: ...
  evidence_to_close: ...
failure_condition: ...
rollback_or_disable: ...
dependencies: [...]
status: proposed
last_verified_at: null
revalidate_at: ...
exception: null

owner_role 保证组织结构变化后仍能路由;具体 assignee 可在工单中绑定。decision_owner 对资源、延期和 residual risk 作决定,不能把所有压力留给实现者。

关闭有四个阶段

implemented
  artifact 已创建

deployed
  control 已进入目标环境

operating
  运行数据证明它持续执行

effective
  反例或演练证明它在失效路径上产生预期效果

例如新增一个 replication lag alert:

  • rule 合并:implemented;
  • VMAlert 已加载:deployed;
  • evaluation 和 notification 正常:operating;
  • 注入受控 lag 后按 SLI/route 触发且未误触其他场景:effective。

只有最后一步及其证据满足 closure contract,才标 effectiveness-verified。若控制只在 部分 service/version 上部署,状态也只能按该 scope 关闭。

写失效条件

没有 failure condition 的控制无法监控自身:

控制 失效条件示例
backup 最近可恢复点超 RPO,或隔离 restore 失败
failover 旧 writer 未围栏,unknown outcome 无法对账
capacity gate 未知 workload 绕过 budget,queue 无上限
release gate exact target/system identifier 缺失仍可执行
checksum review 扫描未覆盖全部声明对象或结果不可追溯
runbook 当前版本/拓扑不适用,参与者只能靠 hidden answer

同时规定 control telemetry 的 owner 和 retention。否则控制悄悄失效,直到下一次事故 才被发现。

到期、例外和重验

配置、版本、工作负载和依赖持续变化。每项控制应有:

revalidation interval
events that invalidate prior evidence
exception owner and expiry
replacement or retirement condition

升级 PostgreSQL/Pigsty、改变 DCS/backup repository、迁移 region、重写事务边界、 修改 pool 或流量翻倍,都可能使旧演练证据失效。exception 不是永久豁免;到期时必须 重新接受风险、补充控制或完成修复。

36.4.2 自动检查、发布门、容量线与恢复演练

先选控制位置

越靠近错误进入点,通常越便宜:

design/static
  schema、inventory、policy、compatibility check

release
  target identity、preview、migration lock、canary、rollback gate

runtime
  admission、timeout、quota、least privilege、safeguard

detect
  user SLI、database/host evidence、integrity and backup checks

recover
  PITR、failover、rebuild、reconciliation drill

不能自动 prevent 的风险,用 detect + mitigate;无法可靠恢复的数据,必须更早 prevent。 不要为追求“全自动”让一个弱分类器直接执行不可逆动作。

自动检查同时测试反例

正向测试只证明一个正确样本通过。控制至少覆盖:

missing identity
wrong cluster / database / role
unsupported version
stale inventory
conflicting evidence
empty or partial result
timeout / collector unavailable
unsafe production flag
rollback unavailable

对 migration,不只验证 SQL syntax;还验证 lock path、rewrite、replica lag、old/new application compatibility 和 rollback。对 restore,不只验证 PostgreSQL 启动;还验证 source lineage、target cutoff、business manifest、archive/backup 恢复和 route 未误切。

发布门要有明确 deny

ALLOW
  required evidence complete
  exact target resolved
  risk/authority/rollback valid

DENY
  predicate false

STOP_AND_ESCALATE
  evidence missing, conflicting or outside classifier domain

未知状态默认 allow,会把 collector 故障变成生产变更。默认 deny 也不是全部答案: 紧急 break-glass 需要独立身份、范围、期限、记录和事后 review。

容量线是多资源 envelope

不要把压测 TPS 单值写进 gate。容量合同至少包含:

workload_revision: ...
hardware_and_topology: ...
dataset_and_cache_state: ...
concurrency_and_arrival: ...
service_sli:
  latency: ...
  error: ...
resource_limits:
  connection_and_queue: ...
  cpu_memory_io: ...
  wal_archive_replication: ...
  xid_vacuum_storage: ...
safety_margin: ...
valid_until_or_invalidation: ...

流量、数据倾斜、query mix、checkpoint、vacuum 和 backup 会改变 envelope。容量 gate 既可阻止超预算发布,也要在运行中检测 headroom 消耗;指标缺失时不能宣称有余量。

恢复演练验证整条链

周期演练不是每季度运行同一 happy-path 命令:

能力 至少验证
backup/PITR source、target、candidate、业务 delta、archive continuity
failover authority、fence、client unknown、timeline、rejoin
overload blind classification、scope、stop route、post-cleanup
integrity preserve、checksum/amcheck、source selection、business invariant
secrets/access break-glass、审计、到期回收

轮换 seed、故障点、参与者和 hidden truth;保留可比较指标,但不把单次练习排名成个人 绩效,否则参与者会优化剧本而不是暴露控制缺口。

36.4.3 不能验证的“加强意识”不是合格行动项

把愿望改写成系统行为

不合格 可验证改写
加强备份意识 每 90 天从随机 retained backup 隔离恢复;业务 manifest 全过
以后谨慎执行 target 缺 system identifier、scope 或 rollback 时 gate 必须拒绝
完善监控 synthetic user failure 在规定窗口触发 SLI 值班告警并附诊断链接
培训故障切换 blind scenario 中先证明 fence/authority,再 reconcile unknown
优化性能 固定 workload 下达到声明 SLI 且所有资源保留安全余量
更新文档 当前版本新值班者仅凭 runbook 完成演练,错误分支被 stop line 阻止

培训和文档可以是控制组件,但不能单独证明系统更安全。人会遗忘、轮岗并在压力下使用 默认路径;应同时改进工具、权限、界面、自动采集、guard 与平台默认值。

拒绝 solution-first action

从“升级版本”“增加节点”“换存储”“重写服务”开始,容易跳过控制目标。先写:

which failure edge must be cut?
how much risk reduction is expected?
what evidence would falsify the proposal?
what new failure modes does it introduce?
how will it be rolled back or retired?

升级 PostgreSQL 或 Pigsty 可能修复已知 bug,也会改变扩展、配置、监控、backup 和 playbook 语义。它需要 ADR 与验证矩阵,不能作为通用复盘结论。

排优先级看风险,不看措辞力度

一种简单排序:

priority score=P(recurrence)×impact×control coverage×confidencedelivery cost+operational cost \text{priority score} = \frac{ P(\text{recurrence}) \times \text{impact} \times \text{control coverage} \times \text{confidence} }{ \text{delivery cost} + \text{operational cost} }

数字不是客观真理,而是迫使团队公开假设。优先:

  • 能切断多条 causal edge;
  • 覆盖多个 service/incident theme;
  • 在影响前 prevent/detect;
  • 具备明确、廉价的 effectiveness test;
  • 降低 on-call 认知负担;
  • 不制造更大单点或不可逆自动化。

低成本并不自动优先。“再加一条 alert”容易交付,却可能增加 noise;一次 target- identity gate 可能需要更多工程投入,但能同时阻断多类破坏性误操作。

action review 的停止线

以下任一成立,不应进入“完成”:

owner or decision owner absent
scope/version unspecified
verification cannot fail
closure evidence unavailable
failure condition unobservable
production rollout lacks rollback/approval
action only changes wording or awareness
exception has no expiry

复盘质量最终体现在控制 registry 里有多少 action 被有效验证,而不是文档里列了 多少 bullet。


上一节:证据质量与决策复盘 · 返回本章目录 · 下一节:回写 SLO、SOP 与架构 ADR · 查看全书目录 · 查看索引中心

36.5 回写 SLO、SOP 与架构 ADR

事故揭示的新事实如果只留在 postmortem,日常开发、发布和值班仍会按旧假设运行。 复盘的下游消费者是 SLO、runbook、service catalog、inventory、ADR、测试与版本路线; 每个消费者都需要明确 revision 和 owner。

36.5.1 更新观察契约、告警规则与 runbook

observation contract 是接口

应用、PostgreSQL、Pigsty 组件和 incident process 对同一 identity 达成约定:

service: pg36_shop
revision: obs-v...
identity:
  service: ...
  environment: ...
  pg_cluster: ...
  system_identifier_projection: ...
  instance: ...
  database: ...
  role: ...
  application_name: ...
user_slis:
  availability: ...
  latency: ...
  correctness: ...
diagnostic_sources:
  postgresql_views: [...]
  logs: [...]
  patroni_dcs: [...]
  proxy_pool: [...]
  host_storage: [...]
missing_semantics: ...
retention_and_access: ...
owners: ...

新增 label 不是免费:高基数会增加监控成本,敏感 identity 会扩大数据暴露。每个维度 回答“哪项决策需要它”,没有消费者的 telemetry 不应无限保留。

从 SLI 到诊断,不从组件告警猜影响

推荐两层告警:

page
  user SLI / error-budget burn / imminent data or recovery risk

ticket or context
  component symptom, capacity trend, maintenance debt

例如 replica lag 可能威胁 RPO 或 read-only 用户路径,也可能只是一个无业务 route 的 重建节点。告警需要 topology、route 和 objective 才能分级。Pigsty 的 PostgreSQL、 Patroni、PgBouncer、HAProxy、host、backup 指标可作为诊断层,业务 good-event 仍要由 应用定义。

规则发布前测试:

normal
threshold boundary
fast burn
slow burn
missing series
one instance vs whole service
maintenance and failover
notification routing

每条值班告警链接一个能在当前版本执行的 runbook,而不是 dashboard 首页。

runbook 写判断,不堆命令

symptom_or_page: ...
objective: ...
required_identity: ...
first_evidence:
  - source: ...
    why: ...
routes:
  - predicate: ...
    action: ...
    expected: ...
    stop: ...
    rollback: ...
unknown_route:
  action: STOP_AND_ESCALATE
authority: ...
version_scope: ...
last_exercised_at: ...
evidence_example: ...

命令输出随 PostgreSQL/Pigsty 版本漂移;runbook 应说明指标和 SQL 的语义,并记录版本 适用范围。危险操作不要留一个通用变量空槽让值班者临时填目标,应从已验证 identity 生成 exact plan 并再次确认。

验证回写完成

“文档更新”需要:

  1. alert fixture 能进入对应 runbook;
  2. 新参与者在 blind tabletop 中请求到正确证据;
  3. 错误/缺失 evidence 进入 stop line;
  4. 文档链接、query 和权限在当前环境有效;
  5. postmortem action 反向链接 observation/runbook revision。

文档过期检测也可自动化:版本、owner、链接、最近演练时间和依赖对象进入 lint。

36.5.2 修正 RPO/RTO、容量和安全假设

objective、capability 与 observation 分栏

objective
  业务愿意承诺什么

designed capability
  架构和控制预计支持什么

observed result
  某次真实事件或受控演练测到什么

一次 sandbox failover 的 6,211.692334 ms acknowledgement gap 是 observation,不是 生产 RTO;一次 fixture PITR 的恢复时间也不包含审批、下载、容量申请、DNS、外部对账 和用户切换。反过来,一个写在文档里的 15 分钟 RTO 若从未演练,也不是 capability。

RPO 不是一个数据库数字

按结果类型拆分:

database transaction durability
replica / WAL archive lag
backup recovery point
business event and outbox state
external side effect
audit/reconciliation source

异步复制、archive 与 backup 各有不同 loss window。PITR 到正确 target 仍可能丢弃 target 后的合法写;数据库恢复也不会自动撤销已发出的支付、邮件或消息。因此 RPO 合同写数据类别、cutoff、source、对账方法和 exception。

RTO 分解而不是报一个 stopwatch

RTO=Tdetect+Tdecide+Tprovision+Trestore+Tvalidate+Troute+Tobserve \mathrm{RTO} = T_{\mathrm{detect}} + T_{\mathrm{decide}} + T_{\mathrm{provision}} + T_{\mathrm{restore}} + T_{\mathrm{validate}} + T_{\mathrm{route}} + T_{\mathrm{observe}}

不同路线各自分布:

  • automatic/controlled failover;
  • PITR candidate + reconciliation;
  • replica rebuild;
  • backup restore;
  • data extraction/forensic recovery;
  • host/region rebuild。

记录 p50/p95 并不代表数据足够;小样本保留 raw runs 和条件。把最慢、最不确定且可改变 的阶段转成行动,而不是只优化 PostgreSQL copy speed。

容量与安全假设也要版本化

事故后检查 ADR 中的隐含假设:

peak and retry multiplier
pool/session/work_mem concurrency
WAL/archive/slot growth rate
vacuum/freeze window
backup repository and restore bandwidth
DCS/failure-domain independence
credential and network trust boundary
break-glass availability and audit
monitoring dependency during control-plane failure

每项写 evidence、margin、owner、invalidation event。将“单节点也够用”“replica 一定 最新”“内网可信”“备份每天成功”这种自然语言替换为可测谓词。

不因事故随意降低目标

若观测证明 objective 不可实现,有三个诚实选择:

  1. 投资能力达到业务目标;
  2. 调整产品/降级设计,缩小承诺范围;
  3. 与业务共同接受并批准新目标和 residual risk。

不能为了让报表变绿而单方面降低 SLO/RPO/RTO,也不能保留不可能实现的目标让值班者 承担结构性失败。

36.5.3 将必要变更纳入服务目录与版本路线

service catalog 是事故路由表

数据库服务条目至少包含:

service_and_tier:
business_owner:
technical_owner:
oncall_and_escalation:
data_classification:
postgresql:
  cluster:
  major_and_extensions:
  databases:
  writer_and_reader_services:
pigsty:
  inventory_revision:
  release:
dependencies:
  dcs:
  backup_repository:
  object_storage:
  identity_and_secrets:
objectives:
  sli_slo:
  rpo_rto:
controls:
  backup_restore:
  ha_fencing:
  capacity:
  integrity:
  security:
last_verified:
exceptions:

这不是手工 CMDB 展示页。inventory 和 runtime 能自动投影的字段不重复录入;业务 owner、data class、objective、外部 dependency 与例外仍需显式治理。

用 ADR 记录为什么

配置告诉系统“是什么”,ADR 解释“为什么这样、在什么条件下仍正确”:

# ADR-...: ...

Status / Date / Owners
Context and incident evidence
Decision
Alternatives considered
Assumptions and version scope
Consequences and new failure modes
Migration / rollback
Verification and observability
Invalidation / review trigger
Related controls and postmortems

例如把 DCS 从单故障域扩为多成员,不能只写节点数;ADR 要讨论 failure domain、quorum、 latency、failsafe/watchdog、maintenance、network partition 和运营复杂度。

版本路线按风险依赖排序

行动可能需要:

  • PostgreSQL minor/major fix;
  • Pigsty/Patroni/pgBackRest/PgBouncer/HAProxy 版本更新;
  • extension compatibility 与 shared_preload_libraries
  • OS、kernel、filesystem、ICU/libc 或 hardware firmware;
  • dashboard/alert/runbook schema;
  • 应用 driver、retry、transaction 和 idempotency contract。

建立 compatibility matrix:

current -> candidate
known incident relevance
support/security window
configuration semantic diff
extension and backup compatibility
rollback boundary
staged evidence
production approval

不要把多个必要升级捆成一次无法归因的大爆炸。先解决阻塞链和可观测性,再 canary/ batch;每阶段都有 before/after、stop 和 rollback。

Pigsty 映射

Pigsty 的声明式 inventory 与幂等 playbook 适合承载 desired state;监控栈适合承载 control telemetry;pgBackRest、Patroni、PgBouncer 和 HAProxy 分别承载恢复、权威、 连接与路由机制。但平台不会替业务定义:

good user event
transaction and idempotency boundary
acceptable data loss
business invariant
external side-effect reconciliation

这些应用合同必须回写 service catalog,并与 Pigsty/PostgreSQL 证据在同一次演练中 共同验收。

路线变更也需要退出条件

每个 roadmap item 记录:

why now
dependency and owner
target version/revision
evidence before rollout
success and stop criteria
rollback support window
when old path is retired

只有当旧例外、旧 runbook、旧 dashboard 和旧配置都被清点,版本演进才真正关闭; 否则值班时仍可能按过时入口执行。


上一节:把行动项变成控制 · 返回本章目录 · 下一节:将控制固化到平台 · 查看全书目录 · 查看索引中心

36.6 将控制固化到平台

平台化不是把所有决定自动化,而是让安全默认、身份、证据、审批和复位在每次操作中 一致出现。自动化适合执行已解析的意图;当 target、authority 或 evidence 仍有歧义时, 平台应停下来,而不是更快执行。

36.6.1 配置模板、验证脚本与策略即代码

控制从机器可读合同开始

desired state
  inventory / parameter / role / service / backup policy

precondition
  exact identity / version / topology / authority / headroom

plan
  rendered change / affected objects / lock and restart / traffic impact

gate
  risk / approval / rollback / evidence completeness

execution
  bounded target / idempotency / audit

postcondition
  native state + platform state + business invariant

模板只消除重复,不应隐藏高风险选择。默认填入:

  • stable service/cluster naming 与 environment/data class;
  • least-privilege role 与明确 HBA 来源;
  • timeout、pool、backup、monitoring、安全和容量基线;
  • version pin 与 extension compatibility;
  • safeguard、exact target 和 destructive approval;
  • owner、SLO/RPO/RTO 与验证 revision。

把业务密码、private key 或 raw token 从 inventory Git 中分离;模板引用受控 secret source,并验证存在性/权限,不输出值。

Pigsty inventory 是 desired state,不是全部事实

Pigsty 以声明式配置表达 node、cluster、instance、service、database、user 与参数, playbook 将其物化。推荐流程:

inventory PR
  -> schema/policy lint
  -> render and semantic diff
  -> sandbox/canary
  -> exact -l scope
  -> staged rollout
  -> SQL + Patroni/DCS + proxy/pool + monitoring verification

--check --diff 可帮助预览部分 Ansible 变化,但不能模拟所有 handler、运行时决策、 数据库锁、外部 repository 或 failover。preview 是证据之一,不是执行结果。

删除、重建和 PITR 等流程必须额外读取当前 identity 与 authority。Pigsty 的 pg_safeguard 可阻止危险 PGSQL 删除路径,但不能替代 exact inventory、备份验证、 流量排空和审批。安全控制应多层、互相独立。

原生证据复核平台结论

平台声称 回到原生/组件证据
primary/replica 正常 Patroni/DCS role、PostgreSQL recovery/timeline/replication
service route 正确 HAProxy backend + endpoint 实际连接 identity
pool 正常 PgBouncer pool/client/server state + PostgreSQL sessions
backup 正常 pgBackRest info/check + 隔离 restore/business manifest
参数生效 pg_settings source/pending_restart + process/runtime
监控覆盖 collector target、query result、rule test、notification

平台 UI 的绿色状态不能成为唯一证据;否则平台控制面故障时,团队失去验证路径。

策略即代码也要可解释

规则输出:

decision: deny
policy_revision: ...
target_identity: ...
failed_predicates:
  - required backup restore evidence expired
  - production destructive approval absent
evidence_links: [...]
exception_path: ...

不可解释的 deny 会诱使人绕过控制;不可审计的 allow 会隐藏风险。policy 变更本身需要 review、测试、版本和回退,并覆盖 allow/deny/unknown 三类 fixture。

36.6.2 备份、切换、容量和维护的周期演练

建 capability calendar

按风险与变更频率决定周期,而不是所有项目“一年一次”:

能力 建议触发
backup check 连续运行;失败立即路由
isolated restore 固定周期 + backup/repository/version 大变更
planned switchover 维护周期 + topology/Patroni 变更
unplanned failover tabletop/drill 固定周期 + DCS/fencing 变更
capacity benchmark workload/hardware/major config 变化
vacuum/freeze review 持续趋势 + 数据增长/事务模式变化
integrity check 风险分层周期 + storage/ICU/major version 变化
security access review 固定周期 + owner/role/network 变化
migration/upgrade rehearsal 每次 candidate revision

周期只是上限;invalidation event 应提前触发。

调度演练而不调度事故

exercise:
  capability: ...
  environment: isolated
  source_snapshot_or_fixture: ...
  hidden_scenario_seed: ...
  allowed_mutations: [...]
  forbidden_targets: [...]
  guards: [...]
  expected_evidence: [...]
  cleanup_and_preservation: ...
  production_claims_forbidden: [...]

故障注入限定 disposable clone 或明确无数据、无流量 sandbox。production chaos 需要 另一套组织授权,不能由“周期演练”四个字自动许可。

Pigsty 能承载的周期任务

  • 通过监控栈持续观察 PostgreSQL、host、Patroni、PgBouncer、HAProxy、backup;
  • 用 pgBackRest policy 与 exporter 观察 backup/archive,再在隔离目标实际 restore;
  • 用 Patroni/Pigsty 服务模型演练计划切换和受控故障切换;
  • 从 version-controlled inventory 重建 node/cluster,并验证 drift;
  • 用 playbook tag/limit 管理作用域;
  • 把 exporter query、alert rule、dashboard 与 runbook revision 共同发布。

具体 playbook、参数和输出会随 Pigsty 版本变化。运行前以已固定 release 的官方文档和 本地 source 为准;破坏性 playbook 不从书中复制到生产。

统一 evidence envelope

不同演练使用同一外壳:

contract + source hashes
environment identity
before / during / after
decision log
raw restricted evidence + redacted projection
business manifest
cleanup / retained artifacts
negative cases
review and production-claim boundary

统一 envelope 让平台能跨演练统计:哪些 control 过期、哪些 action 没有 evidence、 哪些版本从未恢复、哪些团队只跑 happy path。

演练失败不是坏成绩

演练在不伤害生产的前提下暴露:

backup 不可读
权限不足
runbook过期
candidate选错
业务不变量缺失
cleanup不完整
值班升级路径断裂

这正是它的产出。禁止为了完成率修改 pass condition 或隐藏失败;修复后从同一合同 重新验证,并保留前次失败证据。

36.6.3 从单个补丁升级为默认护栏

从 incident-specific 修复抽象不变量

单点补丁:

给 pg-prod-7 的某个脚本加一行 if

默认护栏:

任何 destructive workflow:
  必须解析 environment + cluster + system identity
  必须有 current backup/recovery evidence
  必须声明 data/traffic/authority
  必须有 exact scope、preview、rollback、approval
  缺失或冲突 -> deny/stop

抽象层次以共同机制为准,不是越通用越好。一个巨大“万能安全框架”若无法描述 PostgreSQL timeline、replication slot、PITR target 或 collation dependency,反而 会隐藏领域事实。

safer default 的五个性质

  1. 自动采用:新 service 默认获得,不靠记忆 opt-in;
  2. 显式例外:绕过需要 owner、理由、补偿控制和到期;
  3. 可观察:知道 guard 是否执行、何时失效;
  4. 可版本化:配置、policy、runbook、dashboard 共同 revision;
  5. 可验证/可退出:有反例、回退和 retirement path。

例如:

new PostgreSQL service
  default backup/archive policy
  default service endpoints and pool limits
  default SLI + PostgreSQL/host dashboards
  default safeguard and least privilege
  default restore/failover exercise registration

业务仍要填 owner、data class、RPO/RTO、good event 与不变量;模板不能替它猜。

推广前做 blast-radius 管理

平台默认变更影响面大,按:

fixture -> sandbox -> one canary service -> cohort -> default for new
-> migrate existing -> retire exception

每阶段比较 compatibility、false positive/negative、延迟与资源成本、operator burden 和 rollback。guard 误拒绝所有紧急恢复也会制造可用性风险;保留受控 break-glass,并 监控使用频率。

建 control registry

control_id: ...
objective: ...
implementation_revision: ...
default_scope: ...
exceptions: [...]
telemetry: ...
owner_role: ...
last_verified:
  at: ...
  environment: ...
  evidence: ...
valid_until: ...
related_incidents: [...]
replacement: ...

跨 postmortem 查询重复 theme,而不是逐篇人工回忆。平台 backlog 优先合并能覆盖多个 incident/service 的控制,仍保留每个 incident 的特有 action。

平台完成的定义

一个控制成为默认护栏后,仍要证明:

new service receives it
existing intended scope converged
exception inventory complete
runtime telemetry healthy
negative fixture is blocked
positive fixture is not blocked
break-glass is audited and expires
revalidation is scheduled

“已经写进 Pigsty 模板”只完成第一步。最终效果必须在 PostgreSQL、组件、用户路径和 业务不变量上共同可见。


上一节:回写 SLO、SOP 与架构 ADR · 返回本章目录 · 下一节:实战:复盘四类事故并完成全书结业 · 查看全书目录 · 查看索引中心

36.7 实战:复盘四类事故并完成全书结业

最后一次实验不再连接数据库。它把第 32~35 章公开证据作为不可改写的输入,验证我们 能否在不夸大结论的前提下形成跨事故控制路线,并把全书能力映射为可答辩的证据。

36.7.1 汇总 ch32–ch35 的证据、决策与用户影响

实验合同

先读:

静态检查会创建临时输出,编译、验证后删除;它没有网络/SSH/数据库入口:

static/labs/ch36/task.sh lint

创建一份自己的 closure bundle:

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

PG36_EVIDENCE_DIR="$evidence_dir" \
  static/labs/ch36/task.sh compile

PG36_EVIDENCE_DIR="$evidence_dir" \
  static/labs/ch36/task.sh all

compile 拒绝覆盖非空目录。all 只消费已有 bundle,重新验证 source hash、JSON Pointer、控制合同、路线和 36 个 mutant。它不会执行 backlog。

输出结构

closure-report.json
postmortem-portfolio.json
roadmap-90d.json
capability-assessment.json
input-manifest.json
source-manifest.json
validation-report.json
negative-report.json
public-summary.json
review.txt

四份输入分别绑定:

../ch32/pitr-run.json
../ch33/failover-run.json
../ch34/overload-run.json
../ch35/rescue-run.json

每条 fact 保存:

{
  "id": "F35-CHECKSUM",
  "source_pointer": "/physical_page/offline_bad_checksums",
  "expected": 1,
  "actual": 1,
  "matches_source": true,
  "knowledge_stage": "during-response"
}

编译器重新从源文件解析 actual。修改报告里的数字、pointer、source schema 或 hash 都会失败;事后解释也不能改写 knowledge_stage

四份事故记录

记录 影响证据 决策 验收/边界
ch32 误写 fixture 1,000 victims、1,000 wrong outbox、外发 0 exclusive PITR + audited delta 恢复 1,000,保留 100 条合法后写,fixture loss 0
ch33 主库失效 160 attempts、130 ack、最大 ack gap 6,211.692334 ms fence → promote → reconcile → rejoin ack missing 0、unknown unresolved 0;不是生产 RTO
ch34 资源压力 flow 30/21 admitted/9 rejected;retained WAL 42,611,296 B flow 与 retention 分开 route exact fixture 清理;managed mutations 0
ch35 完整性 1 byte、1 bad checksum、XX001;另一 case checksum 0 + version mismatch trusted copy;REINDEX 后 REFRESH checksum/amcheck/业务不变量通过,原件保留

impact.kind 只能是 simulatedobserved-sandbox。报告中没有真实用户、生产数据或 生产影响。

事实与叙事分离

catalog 为每个 incident 保留:

impact statement -> source fact IDs
decision route -> basis fact IDs
ordered timeline
control theme IDs
claims not made

这使读者可以质疑叙事而不改写证据。例如 ch33 的 observed gap 确实是 6,211.692334 ms,但 claims_not_made 明确拒绝“这就是生产 RTO”;ch35 确实翻转 一个 byte,但不声称模拟真实控制器或内存故障。

36.7.2 找出跨事故重复出现的控制缺口

先找共同问题,不先宣布生产有缺陷

四次实验可以支持:

these mechanisms repeatedly matter

不能直接支持:

your production platform lacks them

所以七个结果都标为:

status = production-assessment-required
basis  = exercise-exposed-risk-not-confirmed-production-deficiency

正式报告中 production_gaps_confirmed=0。真实采用时,要用本地 inventory、policy、 runtime、访谈和演练逐项把 status 更新为 present/effectivegapexceptionnot-applicable

七个共同主题

主题 出现章节 生产评估问题
observation contract 32/33/34/35 user impact、identity、数据库与业务证据是否可关联
production claim gate 32/33/34/35 sandbox timing/route 是否会被误当目标或批准
business validation 32/33/35 恢复是否超越 process/topology 到业务事实
reversible exact scope 32/33/34/35 mutation/cleanup 是否有目标、guard、rollback
unknown outcome 32/33 timeout/ack/commit 是否能按稳定 token 对账
classifier stop route 34/35 缺失/冲突证据是否阻止错误动作
lineage and authority 32/33/35 source、timeline、writer authority 是否可证明

出现次数不等于优先级。用 production exposure、impact、现有控制强度、验证成本和多事故 覆盖率排序。

主题必须双向一致

每个 incident 列出 theme IDs,每个 theme 又列 incident IDs。validator 检查双向 membership,并要求至少两个 incident 才能称 cross-incident。删除一个主题、把主题 缩成单事故或引用不存在的 incident 都会被拒绝。

控制不是事故类型的一一映射

同一个 target-identity gate 可以保护:

  • PITR 不恢复错 cluster;
  • failover 不接纳错 lineage;
  • overload mitigation 不取消无关会话/slot;
  • rescue 不改写 managed/unique PGDATA。

同一事故也需要多类控制:

prevent   exact target + safeguard
detect    user SLI + source-bound evidence
mitigate  admission / fence / stop route
recover   restore / rejoin / reconcile

平台 backlog 优先寻找这类多边覆盖,但仍保留专用控制,例如 collation dependency 重建或 outbox 对账。

识别“控制存在但无效”

生产评估不能只问“有没有”:

implemented?
deployed to intended scope?
operating now?
tested against negative case?
effective at cutting the causal edge?
evidence still valid?
exceptions complete?

有 backup job 但从未 restore,不算已验证 recover control;有 runbook 但 current version 无法执行,也不算;有 safeguard 但 generic token 可绕过 target identity, 只能算弱控制。

36.7.3 输出 90 天改进路线、平台 backlog 与复验计划

路线是参考 proposal,不是自动变更

十二项 action 均为:

status                        proposed
production_execution_approved false

每项包含 stable owner role、P0/P1、due day、source themes、control type、artifact、 verification procedure、pass condition、evidence to close、failure condition 与 revalidation days。采用者要把 role、scope、date 和 approval 映射到自己的组织。

Day 0~30:先让风险可见、危险动作可停

Action 控制
A36-01 版本化 incident observation/evidence schema,并让缺字段 packet 失败
A36-02 destructive workflow 强制 exact target、data/traffic/authority 与 approval
A36-03 定义 request idempotency token 与 unknown-outcome reconciliation
A36-04 分开 user-impact SLI 与 PostgreSQL/host 诊断 telemetry

这一阶段不承诺完成所有架构改造,先建立 identity、证据和 stop line。没有这些基础, 后续演练可能只是在更好地记录错误动作。

Day 31~60:证明四条恢复能力

Action 控制
A36-05 多 PITR candidate + legitimate delta + business manifest
A36-06 fence/failover/client reconciliation/rejoin
A36-07 flow/retention blind classifier + ambiguous stop
A36-08 checksum/amcheck/collation review + clone-only rescue
A36-09 Pigsty inventory、release、safeguard 与 dangerous playbook review

演练用隔离环境,生产不被当作故障注入场。失败产物被保留并转 action;不能为了按期 完成把 pass condition 改成“脚本退出 0”。

Day 61~90:把局部改进变成持续系统

Action 控制
A36-10 用生产测量/有边界演练修订 SLO、RPO、RTO、容量与安全 ADR
A36-11 每月聚合重复 theme、过期 evidence 与 exception
A36-12 blind 90-day game day + 独立 effectiveness review

第 90 天不是项目结束。control registry 根据 revalidation_days 继续检查 30/60/90/180 天周期;版本、拓扑、workload 或依赖变化会提前使证据失效。

validator 如何对抗“纸面完成”

36 个 live mutant 覆盖六组失败:

source integrity
  drop incident, change fact/pointer/schema/hash

epistemic boundary
  claim production impact, rewrite knowledge stage, remove claims-not-made

cross-incident reasoning
  single-incident theme, unknown membership, missing theme

action quality
  no owner/due/verification/failure/revalidation, vague result

governance
  mark closed, auto-approve production, incomplete roadmap

graduation
  duplicate chapter coverage, auto-certify learner

正式结果:

run_id                         91c4464b-89f7-4145-9708-f07256d747ce
input files hash-bound        4
compiler source files bound  12
live mutants rejected        36 / 36
database connections          0
SSH connections               0
external dispatch             0
production mutation           0
production_ch36_gate          pending

公开摘要 SHA-256:

c00463cfedbd4d880d60d6af2f3401d568ef7549948f4cbd062c9b500e53117b

摘要见 closure-run.json。raw formal bundle 不发布, 因为公开教材只需要去敏结论和可重跑 source。

36.7.4 回看从 SQL 到生产的能力地图

十二个可答辩能力域

capability-map.json 将 36 章恰好覆盖一次:

能力域 章节 读者应能交付
context/workflow 1–2 精确身份、对象地图和可重跑任务
model/integrity 3–4 用 schema/type/constraint 编码业务不变量
transaction/programming 5–6 解释 MVCC、事务与服务端副作用
query engineering 7–9 从 plan/stats/workload 证明优化
concurrency/release 10–12 兼容发布、锁边界、rollback 与 contract test
extension workloads 13–18 按语义、生命周期、风险和退出选择扩展
service baseline 19–22 用 Pigsty 交付 route/HA/backup/recovery
security/governance 23–25 least privilege、SLO/SOP 与 observation contract
capacity/maintenance 26–28 可复现 envelope、调优、vacuum/freeze/bloat
evolution 29–30 migration/upgrade candidate、cutover 与 rollback
incident/recovery 31–35 blind classify、保护权威与证据、正确恢复
platform learning 36 将证据转成控制并独立验证效果

地图完整只证明教材没有漏章,不证明具体读者掌握。因此正式结果必须保持:

learner_assessment.status        not-assessed
automatic_certification          false
assessment_required              true

结业不是记忆命令

对每个域,读者完成四层答辩:

explain
  用自己的话说明机制、边界与反例

execute
  在授权环境完成实验并保留机器证据

diagnose
  面对隐藏场景从 evidence 选择 route,而不是背答案

design
  把一次结果转成适合本地 service 的控制、验证和复位

只会复制 psql、playbook 或恢复命令,不构成专家能力;只会讲原理、不能从 runtime 证明状态,也不构成。PostgreSQL 原生证据、Pigsty 平台状态和业务事实要互相校验。

建个人结业 portfolio

portfolio/
  environment-and-version-contract/
  schema-and-transaction-design/
  query-and-concurrency-cases/
  release-and-service-baseline/
  backup-ha-security-observability/
  capacity-maintenance-evolution/
  incident-recovery/
  postmortem-and-control/

每份 evidence 标注:

produced_by:
environment_authority:
version_and_topology:
source_hashes:
claim:
what_it_does_not_prove:
reviewer:
verified_at:
valid_until:

真正的结业由你所在环境的 owner/reviewer 根据证据决定,而不是本书脚本替组织签字。

从哪里继续

读完全书不是把 PostgreSQL 变成“学完的知识”,而是获得一套持续更新的方法:

先确认身份与目标
再从原生证据理解 PostgreSQL
用 Pigsty 把能力组合成服务
用业务不变量决定是否真的正确
用隔离演练证明恢复
用复盘和平台默认值防止同类路径重演

下一步选择一个真实但低风险的 service,把本节 12 项参考 backlog 做本地 present/gap/not-applicable/exception 评估,只批准一项最小、可验证改进。完成它、 保存效果证据、安排重验,然后再进入下一项。这比一次性宣布“数据库平台建设完成” 更接近长期可靠性。


上一节:将控制固化到平台 · 返回本章目录 · 返回全书导读 · 查看全书目录 · 查看索引中心