故障复盘模板:关注系统改进,不追责个人

0阅读14分钟

复盘文档写了,然后呢

我见过不少团队在故障处理完之后,花两三个小时写一份复盘文档。内容很完整,时间线、根因、改进项都有。然后这份文档就被归档到 Wiki 的某个角落,再也没有人打开过。三个月后,类似的故障再次发生。

这不是个别现象。很多团队的复盘停留在「记录故障」这个阶段,没有走到「改进系统」。问题不在态度——大家都想做好——而在方法和结构。一份好的复盘文档,从模板设计开始就要引导写作者把注意力放在系统缺陷上,而不是个人失误上。

这篇文章给出一个可以直接用的复盘模板,同时解释每个字段为什么要这么设计。内容来自 Google SRE 体系、美团工程团队的 5 Whys 实践,以及我自己在多个团队推行复盘文化时踩过的坑。

复盘的理论基础:为什么必须「无责」

Google 在 2014 年出版的《Site Reliability Engineering》一书中用整章篇幅讨论了 Postmortem Culture。核心观点很清晰:人在生产环境中一定会犯错,试图通过追责改变人的行为,不如通过系统设计来容纳错误

Google 内部的复盘有几条硬规则:

  1. Blameless(无责):复盘文档不能出现对个人的指责。如果你发现某人在事件中做了不当操作,要问的是「系统为什么允许这个操作发生」,而不是「他为什么这么做」。
  2. Shared(共享):复盘文档在组织内部广泛公开,不局限于当事团队。其他团队遇到类似系统结构时,能从中受益。
  3. Actionable(可执行):每个改进项必须有明确的负责人和截止日期。没有行动闭环的复盘只是一篇故障记录。
  4. Thorough(彻底):根因要追到系统层面。停在三「某人误操作」或「代码写错了」不够,要继续问为什么流程允许误操作、为什么测试没发现、为什么监控没告警。

这套理念的底层逻辑是:每次故障都是系统设计的一面镜子。如果你因为追责而让当事人隐瞒细节,你丢掉的不仅是一次学习机会,还保留了那个会导致下一次故障的系统缺陷。

5 Whys 分析法:从症状到根因

5 Whys 是丰田生产系统发明的根因分析方法,在软件行业被广泛采用。它不是真的只问 5 次——有些问题 3 次就到根因,有些需要 7、8 次甚至更多。Google SRE 内部有时会做「250 Whys」,意思就是不断追问直到触达系统设计层面的根本原因。

方法论的核心是建立「因果链」:每一个 Why 的答案成为下一个 Why 的问题,直到你找到一个可以通过系统改造来解决的原因。

追问层次典型方向举例
第 1 层直接触发因素数据库主库宕机
第 2 层技术原因慢查询耗尽系统资源
第 3 层流程缺陷慢查询没有在执行前被拦截
第 4 层工具/平台缺失缺少 SQL 审核和性能卡点
第 5 层管理机制发布流程没有强制性能回归环节

美团工程团队在实践中总结了一条经验:如果追问到「人」就停了,说明分析还没到位。 真正的根因应该是一个系统设计或流程上的缺陷,而非某个人的疏忽。

完整案例:三个真实场景的复盘推演

案例一:数据库主库宕机

场景:某电商平台的 MySQL 主库在下午 3 点突然宕机,订单系统全面不可用,持续 47 分钟。

问题还原

维度内容
故障时间15:02 - 15:49,共 47 分钟
影响范围全部下单、支付、库存操作不可用
用户影响约 12000 笔订单失败,客服工单激增 340%
发现方式用户投诉触发人工排查,监控告警滞后 8 分钟
恢复方式主从切换,从库提升为新主库

5 Whys 追问

  1. 为什么主库宕机? ——一条全表扫描 SQL 执行超过 40 分钟,耗尽 CPU 和 IO 资源,导致主库进程 OOM 被系统 Kill。
  2. 为什么全表扫描 SQL 能上线? ——这条 SQL 在测试环境数据量小(2000 行),执行只要 200ms,性能测试通过。
  3. 为什么测试环境没有发现? ——测试环境数据量与生产环境差距超过 100 倍,且没有 SQL 审核卡点。
  4. 为什么没有 SQL 审核卡点? ——半年前评估过引入 SQL 审核工具,但因为「影响发布效率」被搁置。
  5. 为什么「发布效率」优先级高于「数据安全」? ——缺少故障定级标准和对应的流程卡点决策机制。

根因:不是「有人写了一条慢 SQL」,而是发布流程缺少对数据库变更的性能约束机制,且缺少数据量接近生产的测试环境。

修复方案

# 反面做法 ❌
# 在复盘文档里写:「要求张三加强 SQL 性能意识」
# 这种改进项无法验证、无法追踪,等于没写
 
# 正确做法 ✅
# 改进项要落到系统改造上:
action_items:
  - id: INC-2026-0412-01
    action: 引入 SQL 审核工具,PR 提交后自动执行 EXPLAIN 分析
    owner: 基础设施组-李四
    due_date: 2026-07-15
    priority: P0
    type: preventive
 
  - id: INC-2026-0412-02
    action: 搭建数据量与生产等比的 Shadow 测试库
    owner: DBA 组-王五
    due_date: 2026-08-01
    priority: P1
    type: preventive
 
  - id: INC-2026-0412-03
    action: 数据库操作增加慢查询自动熔断(执行超 30s 自动 Kill)
    owner: DBA 组-王五
    due_date: 2026-07-20
    priority: P0
    type: mitigative

案例二:iOS 客户端大面积闪退

场景:某公司内部 OA 应用的 iOS 端在某次发版后大面积闪退,影响 800+ 员工无法正常打卡和审批。

问题还原

维度内容
故障时间09:15 - 11:30,共 2 小时 15 分钟
影响范围iOS 端全量用户(800+ 人)启动即闪退
业务影响当日打卡记录缺失,审批流程阻塞
发现方式发版后 15 分钟,多个用户反馈
恢复方式紧急回滚到上一版本

5 Whys 追问

  1. 为什么闪退? ——服务端接口返回值中一个字段从 string 类型改为 int,iOS 客户端用旧类型解析时触发异常。
  2. 为什么接口字段类型变了没人通知客户端? ——后端开发者认为这是「内部优化」,没有走跨团队变更通知流程。
  3. 为什么没有跨团队变更通知流程? ——有流程文档,但只写了邮件通知,没有人执行。
  4. 为什么邮件通知没人执行? ——邮件通知是「建议」而非「必须」,且没有自动化卡点。
  5. 为什么没有自动化卡点? ——API 管理平台没有和发布流程打通,接口变更无法自动触发下游通知。

根因:API 变更管理缺少自动化约束机制,跨团队协作依赖人工邮件通知,且这个「建议」没有执行保障。

修复方案

# 反面做法 ❌
# 「后端同学下次改接口记得通知客户端」
# 依赖人的记忆和自觉,一定还会再犯
 
# 正确做法 ✅
# 在工具链层面解决:
action_items:
  - id: INC-2026-0318-01
    action: API 管理平台增加 Breaking Change 标记,自动通知下游消费方
    owner: 平台组-赵六
    due_date: 2026-07-30
    priority: P0
    type: preventive
 
  - id: INC-2026-0318-02
    action: iOS 客户端增加接口返回值容错解析(类型不匹配时 fallback 而非 crash)
    owner: 移动端组-孙七
    due_date: 2026-07-15
    priority: P0
    type: mitigative
 
  - id: INC-2026-0318-03
    action: 建立 API 契约测试,发布前自动验证上下游兼容性
    owner: 质量组-周八
    due_date: 2026-08-15
    priority: P1
    type: preventive

案例三:全球卫星节点被批量清空

这个案例来自 Google SRE 的实际故障记录,是「无责复盘」的经典教材。

场景:一次常规的服务器机架退役操作,因为一个 API 的输入验证缺陷,导致全球所有卫星节点(边缘代理服务器)的硬盘被清空。

问题还原

维度内容
故障时间持续约 2 天
影响范围全球所有边缘代理节点
用户影响用户请求延迟大幅上升,部分查询丢失
发现方式全球延迟监控异常
恢复方式将流量切回核心数据中心,再逐步重装边缘节点

事件经过:工程师执行机架退役流程时,第一次运行失败了。他手动重试,但因为流程已经部分执行,系统返回了一个空的机器列表。这个空的列表被送入下游 API,而该 API 的输入验证逻辑有一个缺陷——把空列表解读为「没有筛选条件」,于是把清空操作应用到了所有卫星节点。

5 Whys 追问

  1. 为什么全球节点被清空? ——退役操作的 wipe 指令应用到了所有节点,而非目标机架。
  2. 为什么 wipe 指令应用到了所有节点? ——下游 API 收到空的机器列表,将其解读为「无约束条件」。
  3. 为什么空列表没有被拦截? ——API 缺少输入验证,没有拒绝空的约束列表。
  4. 为什么工程师重试了操作? ——第一次执行失败时,没有其他检查机制来阻止盲目重试。
  5. 为什么单个操作可以影响全部节点? ——系统没有对单次操作的影响范围设置上限。

根因:API 设计缺陷(空列表语义错误)+ 缺少操作范围限制 + 缺少破坏性操作的安全审批机制。

改进方向(Google 的实际处理):

  • API 层拒绝接受空约束列表
  • 限制单次操作影响的最大节点数
  • 破坏性操作增加速率限制
  • 高风险操作增加自动化安全检查审批

注意:整个复盘中,没有一处提到「工程师不应该重试」或「工程师应该更小心」。所有改进都指向系统设计。这就是 blameless 文化的实际体现。

复盘流程:从故障发生到闭环改进

流程图画布 · 115%
Mermaid 流程图加载中...

这个流程有几个容易忽略的关键点:

1. 复盘初稿要异步写,不要在会议里从零开始

很多人的习惯是拉一个小时的会,大家对着白板从头开始写复盘。这效率很低,而且容易变成讨论会而非分析会。正确做法是指定一个 Owner(通常是故障的 Incident Commander),他在会前先收集好数据、整理好时间线、写好初稿。会议的作用是深化分析和对齐改进项,不是写文档。

2. 改进项必须在会后立即录入任务系统

改进项如果只留在复盘文档里,就会被人遗忘。Google 的经验是:复盘会议结束后,所有改进项要立即录入 Jira、Linear 或你们用的任何任务管理系统,和正常的开发任务一样被跟踪。

3. 设定完成率的底线

incident.io 的实践建议:如果改进项完成率低于 50%,说明复盘只是走形式。健康的团队应该保持在 80% 以上。

指标健康值警戒值说明
改进项完成率≥ 80%< 50%低于 50% 说明复盘是形式
故障复发率< 5%> 20%同类故障重复出现说明根因没解决
复盘完成时间< 48 小时> 1 周拖得越久,细节越模糊
跨团队阅读量持续增长仅当事团队看复盘的价值在于组织级学习

复盘模板:每个字段的设计理由

下面是一份完整的复盘模板结构。我按模块拆解,并说明每个字段为什么存在。

模块一:元信息

# 反面做法 ❌
# 复盘文档没有元信息,三个月后无法检索和关联
title: 数据库故障复盘
 
# 正确做法 ✅
# 结构化元信息,支持检索、关联和趋势分析
metadata:
  incident_id: INC-2026-0412
  severity: P1
  incident_date: 2026-04-12
  authors:
    - 李四(基础设施组)
  reviewers:
    - 王五(DBA 组)
    - 赵六(SRE 组)
  status: draft  # draft → in_review → published → closed
  related_incidents:
    - INC-2025-1103  # 关联历史类似故障

模块二:摘要与影响

# 反面做法 ❌
# 摘要模糊,无法快速评估严重程度
summary: 数据库出了问题,影响了业务,后来修好了。
 
# 正确做法 ✅
# 量化影响,非本团队的人也能快速理解
summary: |
  2026-04-12 15:02 - 15:49,MySQL 主库因慢查询导致 OOM 宕机。
  订单系统全面不可用 47 分钟,约 12000 笔订单失败。
  根因是发布流程缺少 SQL 性能卡点,全表扫描 SQL 未被拦截。
  通过主从切换恢复,已完成 3 项系统改进。
 
impact:
  duration_minutes: 47
  affected_users: ~12000
  affected_orders: ~12000
  slo_budget_consumed: 38%
  customer_tickets_increase: 340%

模块三:时间线

时间线要用 UTC 时间戳,精确到分钟。每个条目要标注「谁做了什么」和「系统状态变化」。

# 反面做法 ❌
# 时间线粗粒度,丢失关键决策点
timeline:
  - 数据库挂了
  - 排查了一会儿
  - 切换了主从
  - 恢复了
 
# 正确做法 ✅
# 细粒度时间线,包含检测、决策、执行各环节
timeline:
  - time: "2026-04-12T15:02:00Z"
    event: 慢查询开始执行(事后从 slow query log 回溯)
    phase: trigger
 
  - time: "2026-04-12T15:10:00Z"
    event: 数据库 CPU 使用率超过 95%,但告警阈值为 98%,未触发
    phase: detection_gap
 
  - time: "2026-04-12T15:18:00Z"
    event: 客服接到第一个用户投诉「无法下单」
    phase: detection
 
  - time: "2026-04-12T15:25:00Z"
    event: On-call 工程师确认为数据库问题,发起 P1 事件
    phase: triage
 
  - time: "2026-04-12T15:32:00Z"
    event: 决定执行主从切换
    phase: decision
 
  - time: "2026-04-12T15:41:00Z"
    event: 主从切换完成,从库提升为新主库
    phase: recovery
 
  - time: "2026-04-12T15:49:00Z"
    event: 订单系统恢复,监控确认流量正常
    phase: resolved

模块四:根因分析

根因分析要区分「直接原因」「促成因素」和「根本原因」。直接原因是触发故障的最后一个环节,促成因素是让故障影响扩大的条件,根本原因是系统设计层面的缺陷。

# 反面做法 ❌
# 根因停留在个人层面
root_cause: 张三写了一条性能很差的 SQL
 
# 正确做法 ✅
# 分层分析,指向系统设计
root_cause_analysis:
  direct_cause: |
    一条全表扫描 SQL 执行超过 40 分钟,导致 MySQL 主库 CPU 和 IO 耗尽,
    进程被 OOM Killer 终止。
 
  contributing_factors:
    - 测试环境数据量(2000 行)与生产环境(2 亿行)差距 100 倍
    - 数据库 CPU 告警阈值设为 98%,慢查询在 95% 时已经影响服务但未能触发告警
    - 发布流程没有 SQL 审核环节
 
  root_cause: |
    发布流程缺少对数据库变更的性能约束机制。
    团队在半年前评估过 SQL 审核工具,但因「影响发布效率」搁置,
    且缺少故障定级标准来辅助这类优先级决策。

模块五:检测缺口

检测缺口是容易被忽略但极其重要的部分。它回答的是:为什么故障发生了,我们没有更早发现?

# 反面做法 ❌
# 不分析检测缺口,只关注「怎么修」
# (没有这个模块)
 
# 正确做法 ✅
# 明确分析检测和告警的盲区
detection_gaps:
  - gap: 数据库 CPU 告警阈值过高(98%),慢查询导致 CPU 95% 时未告警
    fix: 将告警阈值调整为 85%,并增加基于趋势的预测告警
    owner: SRE 组-钱九
    due_date: "2026-07-10"
 
  - gap: 缺少慢查询上线前的自动检测机制
    fix: 在 CI/CD 流水线中集成 EXPLAIN 分析
    owner: 基础设施组-李四
    due_date: "2026-07-15"

模块六:改进项

改进项必须遵循 SMART 原则(具体、可衡量、可达成、相关性、有时限),并且分为「预防型」和「缓解型」。

# 反面做法 ❌
# 改进项模糊、无负责人、无截止日期
action_items:
  - 加强 SQL 性能优化
  - 完善测试环境
  - 提高团队意识
 
# 正确做法 ✅
# 每个改进项都具体、可追踪、有归属
action_items:
  - id: INC-2026-0412-01
    action: 引入 SQL 审核工具,PR 提交后自动执行 EXPLAIN 分析
    owner: 基础设施组-李四
    due_date: "2026-07-15"
    priority: P0
    type: preventive  # 防止同类故障再次发生
    status: todo
 
  - id: INC-2026-0412-02
    action: 数据库 CPU 告警阈值从 98% 调整为 85%
    owner: SRE 组-钱九
    due_date: "2026-07-10"
    priority: P0
    type: mitigative  # 加快下次类似问题的检测速度
    status: todo

好/坏做法对比汇总

下面把全文涉及的关键对比集中展示,方便快速参考。

维度反面做法正确做法
根因描述「某人误操作」「系统允许误操作发生,缺少防护机制」
改进项「加强代码审查」「在 CI 中集成自动化检查,阻断不合格代码合入」
时间线「数据库挂了,后来修好了」按分钟粒度记录,含检测、决策、执行各环节
影响评估「影响了业务」「12000 笔订单失败,SLO 预算消耗 38%」
改进项归属「团队负责」具体到某一个人
改进项截止「尽快」具体日期
告警规则固定阈值 98%结合趋势预测 + 分层阈值
复盘频率出了事才复盘定期回顾历史复盘的改进项完成情况
复盘阶段常见问题改进方向
数据收集只收集技术日志,忽略人工决策过程同时记录「谁在什么时候做了什么决策」
根因分析追到「人」就停了继续追问系统为什么允许这个行为
改进项制定写「加强培训」「提高意识」写具体的系统改造或流程卡点
文档撰写开会时从零开始写会前异步完成初稿,会议用于深化分析
跟踪闭环改进项留在 Wiki 里录入任务管理系统,和开发任务同等对待
改进项类型定义举例
Preventive(预防型)防止同类故障再次发生引入 SQL 审核工具,阻断不合规 SQL 上线
Mitigative(缓解型)加快下次类似问题的发现和恢复速度调整告警阈值,缩短故障检测时间
Process(流程型)改进团队协作和决策流程建立 API 变更的跨团队通知机制
Testing(测试型)增加覆盖故障场景的测试增加数据量接近生产的集成测试环境
复盘成熟度特征典型表现
L1 记录级只记录故障经过,不分析根因「数据库挂了,换了台机器好了」
L2 归因级找到直接原因,但停在表面「慢 SQL 导致数据库宕机」
L3 系统级追到系统设计缺陷,提出系统改进「发布流程缺少 SQL 性能卡点」
L4 文化级复盘文档跨团队共享,改进项闭环率 > 80%复盘成为组织学习的基础设施

复盘执行检查清单

以下清单按复盘流程的时间阶段分组,每次复盘时可以直接对照使用。

阶段一:故障发生后 24 小时内

  • 指定唯一的复盘 Owner(通常是 Incident Commander)
  • 收集所有相关日志、监控数据、变更记录
  • 整理完整时间线,精确到分钟,包含人工决策节点
  • 量化影响范围(用户数、订单数、SLO 预算消耗、客服工单量)
  • 确认故障严重程度等级(P0/P1/P2/P3)

阶段二:初稿撰写(48 小时内完成)

  • 按照模板结构填写所有模块,不留空白
  • 根因分析至少追问 5 层,确保触达系统设计层面
  • 区分直接原因、促成因素和根本原因
  • 明确列出检测缺口:为什么没有更早发现
  • 改进项分类为预防型/缓解型/流程型/测试型
  • 每个改进项有具体的负责人(一个人,不是「团队」)
  • 每个改进项有具体的截止日期(不是「尽快」)
  • 改进项优先级排序:P0 项不超过总项数的 30%

阶段三:评审会议

  • 会议前至少 24 小时发出初稿,让参与者提前阅读
  • 会议聚焦于深化根因分析和对齐改进项,不是从头写文档
  • 检查文档语言:消除所有指向个人的指责性表述
  • 邀请相关团队参与(上下游依赖方、SRE、安全)
  • 会议时长控制在 60 分钟内

阶段四:发布与闭环

  • 改进项录入任务管理系统(Jira/Linear),获得任务 ID
  • 复盘文档状态标记为 published,通知相关团队
  • 每周跟踪改进项完成进度,在周会中同步
  • 所有 P0 改进项在 30 天内完成或明确调整计划
  • 全部改进项完成后,复盘文档状态标记为 closed
  • 跨团队分享:在内部技术博客或周报中摘要关键教训

推动复盘文化的几个实操建议

模板只是工具,文化才是基础。如果你在一个还没有复盘习惯的团队推行这件事,以下是我验证过有效的做法。

从小的开始。不要等 P0 故障才复盘。P3、P4 的小问题更适合练手——影响小,压力低,团队更容易接受。

领导者先做示范。当团队负责人公开承认自己在某个故障中的决策失误,并且把改进指向自己负责的流程时,其他人会跟进。反过来,如果领导者总是问「这是谁的错」,再好的模板也推行不下去。

让复盘文档可见。Google 的做法是让所有复盘文档在公司内部公开可见。你不需要做到这个程度,但至少要让相关团队能方便地检索和阅读。

定期回顾历史复盘。每季度花一个小时,回顾过去三个月的复盘文档,检查改进项完成率,看看有没有同类故障重复出现。这个动作本身就是对复盘文化的强化。

不要追求完美。第一份复盘文档可能只有 60 分,但它比没有复盘强 100 分。先跑起来,再迭代改进。

参考资料

评论 0

0 / 1000