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

LLMS 索引： [llms.txt](/llms.txt)

---

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

## 36.1.1 恢复用户影响、数据正确性与运行余量 {#item-36-1-1}

### 用状态向量代替单点绿灯

事件恢复状态可以写成：

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

其中：

```text
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 前基线、影响窗口和恢复窗口：

```yaml
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

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

```text
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 就可能
再次触发事故。对每个主要资源记录：

$$
\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 清理临时降级、应急权限和旁路配置 {#item-36-1-2}

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

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

```yaml
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”。临时限流可能仍在保护低余量系统，立即
撤掉会重启事故。先确认它是：

```text
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。事故后至少做三方对照：

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

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

## 36.1.3 通知、观察窗口与正式结案条件 {#item-36-1-3}

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

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

```text
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。但交接
不能抹掉风险。

### 结案门

```yaml
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` 不等于“复盘已经写完”。达到上面条件后，事件可以从实时
响应转入学习与控制工作；后续三种状态分别追踪：

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

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

---

[返回本章目录](../) · [下一节：从时间线建立因果链](../02/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
