on-call 的"隐性知识债务":那些只存在于你脑子里的"为什么",人一走就断档
帖子正文与楼层回复。
先讲一个我亲历的场景,大家先看是不是眼熟。
去年 8 月,我们团队一个处理订单履约的核心服务(Java 17 + K8s,3 副本)出了 P2:凌晨 2 点,3 个 Pod 全部 OOMKilled,服务不可用 23 分钟。on-call 是我,恢复过程其实不复杂——重启、调大 memory limit、观察。但恢复之后我脑子里多了一堆"为什么":
- 为什么这个服务的 memory limit 是 2G 而不是 4G?(答:2 年前压测时 4G 会触发 GC 停顿 > 8s,P99 爆炸,所以锁在 2G,但那个压测报告我找不到链接了,只在脑子里。)
- 为什么不能直接 kubectl scale 扩容?(答:这个服务用了本地磁盘做临时缓存,Pod 重建后缓存丢失,下游会触发一次全量拉取,QPS 瞬时 ×3,把 DB 打挂。所以扩容必须走"先扩后缩 + 限流"流程。)
- 为什么 memory limit 调大的时候要同时调
MaxDirectMemorySize?(答:这个服务用了 Netty 的 direct buffer,JVM heap 之外还有一块,不调的话 direct memory OOM 比 heap OOM 更隐蔽。)
这三条知识,不在任何文档里。不在 wiki、不在 runbook、不在 ADR、不在代码注释里。它们只存在于我那个凌晨 2 点的脑子里。
三个月后,团队来了个新人 on-call。同样的故障(OOMKilled),他按 runbook 操作:重启、调大 memory limit、观察。结果——他不知道"不能直接 scale",直接 kubectl scale 到 5 副本,下游 DB 被全量拉取打挂,P2 升级成 P1。事后复盘,他说的第一句话是:"runbook 上没写这个限制啊。"
我说:"runbook 上确实没写,因为我当时觉得这是'常识'。"
这就是"隐性知识债务":每次故障恢复后,on-call 的脑子里多了一批"为什么"——为什么这个参数是这个值、为什么那个操作不能做、为什么这个服务不能随便重启。这些知识有明确的"利息":人不在了,或者换人了,这些知识就断了。而且它不像技术债那样"系统会提醒你"——它只在下次出同样的故障时才暴露,而暴露的代价通常是一次 P1。
我观察到几种常见做法,各有问题:
- 靠"口口相传" + 新人跟岗。最自然,也最脆弱。老人觉得"我说了三遍了他怎么还记不住",新人觉得"他当时说的时候我在想别的"。而且"跟岗"不可规模化——一个老人带不了 5 个新人。
- 事后补文档(post-incident writeup 里带一段"运维注意事项")。听起来对,但 post-incident writeup 的格式是"发生了什么 → 根因 → 改进项","运维注意事项"通常被塞进"改进项"里,然后变成一条 Jira ticket,排期……然后排期……(参见 #184 的讨论,改进项烂尾率很高。)
- 把"为什么"写进代码注释或配置文件注释。技术上最"原子化",但问题是:这些知识经常是跨服务的("这个服务扩容会影响下游 DB"),你写在本服务的注释里,看下游的人看不到;写在下游的注释里,看本服务的人看不到。
- 建一个"运维知识库"(wiki / 内部 Confluence)。最"正规",但也是最容易过期的。我见过一个团队的运维 wiki,200 篇文档,我抽查了 10 篇,6 篇引用的服务名已经改了,2 篇描述的架构已经重构了,还有 2 篇的内容和代码完全矛盾。过期的知识比没有更危险——因为它会引导人走错路径。
- 用 LLM 在 on-call 时"现场问":把代码仓库 + 配置 + 历史 incident 喂给 LLM,让它回答"为什么这个 memory limit 是 2G"。很诱人,但 LLM 会"合理地编造"一个答案,而且你没法验证它是不是对的。我试过,它给我编了一个"因为 JVM 的 G1 GC 在 2G heap 下 mixed GC 效率最高"的说法,听起来很专业,但完全是编的。
我个人的倾向,供拍砖:"为什么"类知识应该被"锚定"到它最不容易过期的那个位置,而不是集中在一个"知识库"里。 具体来说:
- 参数级的"为什么"(为什么 memory limit 是 2G)→ 锚定到配置文件或 Helm values 的注释里,并且必须带一个可验证的引用(压测报告链接、Jira ticket、或一段可复现的 benchmark 脚本)。没有引用的"为什么"视为"未验证假设",on-call 遇到时应该重新验证而不是直接信。
- 操作级的"为什么"(为什么不能直接 scale)→ 锚定到 runbook 的"禁止操作"section,并且必须带一个"如果违反了会怎样"的具体描述(不是"会影响下游",而是"会触发下游全量拉取,QPS 瞬时 ×3,DB 连接池打满,P2 升级 P1")。
- 跨服务的"为什么"(这个服务的扩容会影响那个服务)→ 锚定到一个服务依赖图(不是架构文档,是活的依赖图),每个边上标注"耦合类型 + 影响方向 + 触发条件"。
核心原则:知识的"锚点"越靠近它被使用的上下文,过期概率越低。"为什么 memory limit 是 2G"写在 memory limit 旁边,比写在 wiki 的"运维注意事项"第 47 页,过期概率低一个量级——因为改 memory limit 的人一定会看到旁边的注释,而改 wiki 的人不一定。
但我也真的不确定这个方案能不能规模化。因为"锚定"的前提是"有人写",而"有人写"的前提是"有人在乎"——这就回到了 #184 讨论的"改进项落地"问题:on-call 在凌晨 2 点恢复了故障,他脑子里那 10 条"为什么",有多少会被写下来?我猜不到 3 条。
想听听大家:
- 你们团队有没有"隐性知识债务"的典型案例?——就是"老人知道但新人不知道,然后出了事"的那种。
- 你们用什么方式沉淀"为什么"类知识?代码注释、wiki、runbook、口口相传,还是干脆不沉淀?
- 你们有没有"知识过期"的机制?——比如定期 review 文档、或者"上次验证时间"超过 N 天就标记为"待验证"?
- 如果让你设计一个"on-call 知识沉淀"的流程(不是 wiki,是一个具体的动作序列),你会怎么设计?
- 你们有没有遇到过"知识沉淀了但没人看"的情况?怎么解决的?
想把这个话题聊透一点,因为我觉得"隐性知识债务"是 SRE 工程里最容易被低估、也最容易被"我觉得这是常识"一句话带过的那一块。它不像技术债那样有代码可以重构,也不像 SLO 那样有数字可以汇报,但它决定了凌晨 2 点那个新人,是按 runbook 走 8 分钟恢复,还是走错路变成 P1。