結論先說:incident response 的目標是先恢復使用者承諾,不是急著找根因;而支撐這個目標的,是六段流程裡每一階段清楚的決策範圍,加上一份在壓力下真的能用的 runbook——這是本篇(上)要處理的部分,postmortem 怎麼寫、Google/Netflix 怎麼做復盤,留給(下)篇。
Day 26 把訊號分流成 pager、evaluation queue 與 security review 三條路。今天要接著處理這些訊號響起之後真正發生的事:事故要怎麼走完,證據要怎麼留下來,才不會下一次又從零開始猜。
故障發生 → 診斷 → 修復 → 恢復 → ???
許多團隊在「恢復」這一刻鬆了一口氣,認為 incident 已經結束。但實際上,只是 downtime 結束了。真正的問題才要開始:
如果不回答這些問題,每一次故障都會以相同或相似的根因形式重演。生產系統的可靠性不來自「幸運沒有故障」,而來自「系統化地學習每一次故障」。
這句話聽起來像口號,但它背後有一個很具體的機制假設:故障的知識不會自動留在組織裡,它只會留在參與過的人的短期記憶裡。凌晨三點被叫醒處理 timeout 的工程師,隔天早上還記得細節;一週後記得大概方向;一個月後只記得「好像有出過什麼事」;三個月後,新加入的同事根本不知道這件事發生過。如果沒有把當時的證據——alert 內容、版本號、決策順序、卡點——寫成可檢索的文件,這些知識就會隨著記憶力衰退而蒸發,而系統本身完全沒有變得更可靠,只是「這一次剛好被修好了」。
這裡有一個常見的誤解,值得先講清楚:很多團隊把「incident 已經結束」跟「downtime 已經結束」畫上等號。這兩件事的判準完全不同。Downtime 結束的判準是「使用者現在能不能正常使用」,這通常在幾分鐘到幾小時內就能確認。但 incident 真正的結束,是「這次故障暴露的系統弱點已經被記錄、根因已經被理解到可以驗證的程度、至少有一項具體行動已經在追蹤清單裡」——這通常需要幾天到幾週。把兩者混為一談的代價,是團隊會在 downtime 結束的那一刻集體鬆懈,把本來該花在復盤上的注意力,轉移到下一個緊急任務。復盤如果沒有在故障後 24-48 小時內排進行事曆,經驗顯示它有很高的機率永遠不會發生——不是因為沒有人在乎,而是因為下一個緊急事件永遠會先來。
另一個常見誤解是把「事故管理」窄化成「事故發生時的處理流程」。但一套完整的事故管理,其實橫跨三個時間軸:
故障發生前 故障發生中 故障發生後
───────── ───────── ─────────
Runbook 是否可用? 六段流程走得快嗎? Postmortem 有沒有寫?
版本是否可追蹤? 決策依據夠不夠? Action Item 有沒有兌現?
告警是否可操作? 降級是否可逆? 下次演練有沒有變快?
這三個時間軸互相餵養:故障前準備得越充分(好的 runbook、乾淨的版本記錄),故障中的決策就越快;故障中留下的證據越完整,故障後的復盤就越有效;復盤產出的改進,又回頭餵養下一次故障前的準備。任何一段斷掉,這個迴圈就停止運作——這也是為什麼本篇要把 Detect 到 Review 這六段、加上 runbook、加上 postmortem 三件事放在同一篇文章裡談:它們是同一條鏈,拆開看每一段都合理,湊起來看才是完整的可靠性系統。
這條迴圈的存在,也回答了本節標題那句「證據應該留在哪裡」——答案不是單一一個地方,而是分散在迴圈的三個節點上:留在 runbook 裡的證據,服務的是故障中的自己;留在 postmortem 裡的證據,服務的是故障後的自己與下一個值班的人;留在 regression test 與 release gate 裡的證據,服務的是故障前的自己,讓同一個觸發條件在下一次變更時就被攔下來,根本不會走到需要 Detect 的那一步。這三個節點缺一不可,本篇接下來的段落,會依序把這三處證據該怎麼留、留成什麼形狀,一一講清楚。
Detect → Triage → Mitigate → Resolve → Recover → Review
這六個字看起來像是把「處理事故」拆成六個步驟的流程圖,但它真正想表達的是每個階段有不同的決策目標,用錯目標會讓整場事故變慢或變危險。一個常見的失敗模式,是團隊把六段流程壓縮成兩段——「發現」與「修好」——中間的細節全部憑直覺處理。這樣做在小型、單一服務的系統裡也許還撐得住,但當系統牽涉多個依賴、多個團隊、多個版本座標時,缺少中間階段的明確切分,會讓「誰該做什麼判斷」變得模糊,而模糊在高壓下最容易演變成互相等待或重複勞動。
好的告警應該在秒級到分鐘級內觸發,但這句話背後有一個常被忽略的前提:告警規則必須在事故發生前就已經定義清楚,而且定義的對象是使用者風險,不是系統內部指標(這正是 Day 25-26 討論 SLO 與告警分流時建立的基礎)。這裡有個容易忽略的區別:Detect 階段的產出不是「知道系統壞了」,而是「知道系統壞了、壞在哪個維度、影響多大」。一則只寫「API error rate 上升」的告警,跟一則寫「policy Q&A 的 P95 latency 超過 5 秒,過去 10 分鐘影響約 8% 流量」的告警,觸發的後續動作速度完全不同——後者讓值班者不需要再花時間確認「這值得叫醒別人嗎」。
「優先級判定:是停止所有新 rollout?還是只暫停該功能?」這句話背後,需要有一套所有人事先都同意的嚴重度分級,Triage 現場才不會變成「你覺得很急、我覺得還好」的主觀爭論。多數成熟團隊會用 SEV0 到 SEV3(或類似的四到五級)分級,每一級對應明確的行動門檻,而不是留給值班者臨場判斷:
SEV0:核心使用者旅程完全不可用,或有資料外洩之虞
→ 立即升級給所有相關 owner,暫停一切非緊急 rollout,
可視需要對外公告
SEV1:核心使用者旅程明顯降級,影響範圍達到既定門檻(如 >5% 流量)
→ 升級給該服務 owner 與 IC,暫停該功能相關 rollout
SEV2:邊緣功能或少數用戶群受影響,有已知的 workaround
→ 該服務 team 內部處理,不需跨團隊升級,工作時間內修復即可
SEV3:內部可觀測到的異常,使用者尚未受影響或影響極輕微
→ 記錄追蹤,排入正常工作排程,不觸發 page
這套分級真正的價值,不在於分類本身多精細,而在於它把「這件事有多急」從一個當下臨場的主觀判斷,變成一個可以事先討論、之後被拿出來檢驗的客觀標準。一個常見的誤解是把嚴重度分級當成「事後補充說明用」的標籤——只在 postmortem 文件裡填一格「SEV1」,卻沒有在 Triage 當下真的用它來決定升級與否。分級如果沒有在事故發生的第一分鐘就被拿來用,它就只是一個裝飾性欄位,起不到「讓所有人快速對齊急迫程度」的作用。
Triage 的核心問題只有三個:誰是 incident commander?哪些系統/使用者群體受影響?風險等級高到需要升級通知客戶嗎?值得強調的常見誤解是:Triage 不是「找出故障的技術原因」,而是「決定接下來由誰、用什麼優先順序處理」。把技術診斷提前塞進 Triage,會讓本來該在幾分鐘內完成的角色分配,被拖進「我們先看一下 log」的細節裡,反而延誤了真正該立刻開始的降級動作。Triage 的輸出應該具體到「incident ticket 的初始資訊」與「incident commander 的角色分配」,缺一不可——沒有指定 commander 的事故,常見的下場是三個人同時在做三件互相打架的事,卻沒有人知道彼此在做什麼。
關於 incident commander 這個角色,還有兩個常被混淆的誤解值得單獨拆開講:
IC ≠ 團隊裡最資深、最懂技術細節的人
→ IC 的工作是協調決策與溝通節奏,不是親自下去查 log;
最懂技術的人常常該去做 IC 底下的診斷角色,而不是兼任 IC 本身。
IC ≠ 造成這次故障的人
→ 把「誰改壞的」跟「誰來指揮」混在一起,等於逼當事人一邊
承受壓力一邊做判斷,這正是本系列下篇談到 Cook 觀點時會談到的
「後見之明會扭曲當事人自己對事件的判斷」的實際體現。
前者的誤解會讓 IC 一邊查資料一邊指揮,兩件事互相搶注意力,決策速度反而變慢;後者的誤解則常常在小團隊裡自然發生——「這是你負責的服務,你來當 IC」聽起來合理,但當事人此刻最需要的是專心診斷,而不是同時扮演對外溝通、協調各角色、決定升級與否的角色。一個健康的 on-call 輪值設計,會刻意讓 IC 與「造成或最熟悉故障服務的人」是兩個不同的人,即使團隊小到只有兩三個工程師,也該在演練裡明確練習這個角色分離。
Mitigate 階段最容易被誤解成「開始debug」,但它真正的目標是「用已知、可逆的手段降低使用者受到的傷害」——停止有問題的 rollout、切到既定的 fallback、暫時降級非關鍵功能,這些動作的共通點是:它們不需要完全理解根因就能執行。這是六段流程裡最違反工程師直覺的一步,因為多數工程師的訓練本能是「先弄清楚為什麼壞了,再決定怎麼修」,但在 incident 現場,弄清楚根因往往要花幾十分鐘到幾小時,而使用者的痛苦是以分鐘計算的。此階段的目標是「讓用戶 SLI 盡快回穩」,而不是「完全理解故障原因」——這句話值得刻在每一份 runbook 的第一頁。
Resolve 確認的是「故障的直接觸發因素已經解除」——例如錯誤的 rollout 已回滾、故障節點已下線、有問題的 config 已還原。這代表「問題來源已被移除」,但不代表用戶端已經完全回穩:快取裡可能還存著錯誤版本產生的回答、queue backlog 需要時間消化、連線池需要重新建立健康的連線。把 Resolve 誤當成事故結束,是本篇後面會反覆強調的一個高頻錯誤——因為觸發因素消失通常伴隨著圖表變綠、on-call 群組安靜下來,這種「表面平靜」極容易被誤讀成「已經沒事」。
Recover 要確認的是用戶 SLI 已回穩,不只看「error log 消失」,而是看「用戶實際完成的任務比例是否恢復」。這一步的重要性常被低估,團隊通常在 Resolve 後就宣告「恢復完成」,卻忽視了需要驗證「新版本真的解決了問題」——這中間的落差就是 Resolve 與 Recover 沒被明確區分所造成的代價,也是本篇整篇文章反覆回來討論的核心張力(見 ⑥ What I Learned)。
在進入 Review 之前,值得先拆穿一個貫穿整個 Triage 到 Mitigate 階段、卻很少被講明的心理障礙:升級(escalate)常被誤當成「承認自己搞不定」。
升級 ≠ 承認能力不足
→ 升級是「這件事的風險等級或不確定性,已經超出我
這個角色被授權獨自處理的範圍」,是角色設計裡
本來就該發生的正常路徑,不是一次失敗。
升級 = 把決策權放到對的層級
→ 一個值班者拖著不升級、想「自己再查五分鐘」,
付出的代價通常不是他自己承擔,而是使用者
在這五分鐘裡繼續承受風險——這正是 ⑧ 段
escalate_when 欄位要事先寫清楚門檻的原因。
如果一個團隊的文化讓「升級」聽起來像是能力不足的證明,值班者就會傾向拖到最後一刻才升級,而這個「拖」的代價幾乎全部由使用者承擔。⑧ 段 runbook 資料契約裡的 escalate_when 欄位,本質上就是把「什麼時候該升級」這個判斷,提前搬到沒有壓力的時候去討論定案,讓值班者在事故現場不需要同時背負「要不要升級」與「升級是不是等於我不行」這兩層決定。
Review 復盤當時的系統條件、決策與缺口,產出 blameless postmortem。重點是:不要把「找到根因」當作 Recover 的前置條件。故障發生時,找根因會延遲恢復;Review 才是找根因的適當時機。這個順序聽起來理所當然,但實務上很容易被打亂——尤其是當老闆或客戶在事故現場追問「到底是為什麼」時,團隊會有極大的壓力想在 Mitigate 甚至 Detect 階段就給出根因答案。這種壓力如果沒有被 incident commander 主動擋下,結果往往是「倉促下結論、事後被推翻」,比誠實地說「我們正在恢復服務,根因會在復盤裡詳細說明」更傷團隊的可信度。
把六段流程畫成狀態機會更清楚每個階段的「離開條件」是什麼:
[Detect] ──alert 觸發且已判定使用者風險──▶ [Triage]
[Triage] ──commander 指定、owner 分配完成──▶ [Mitigate]
[Mitigate]──已執行可逆降級、傷害停止擴大──▶ [Resolve]
[Resolve] ──觸發因素已移除(非「使用者已沒事」)──▶ [Recover]
[Recover] ──SLI 連續達到恢復門檻──▶ [Review]
[Review] ──postmortem 產出、Action Item 已指派──▶ 結案
這張狀態機圖的價值不在於它畫得多精確,而在於它逼你回答一個問題:每個階段「離開」的條件是什麼?誰來判定這個條件已經滿足? 沒有明確離開條件的階段,事故現場很容易卡在原地互相等待——沒有人敢說「我們可以進到下一步了」,因為沒有人被賦予這個判斷的責任。
團隊通常只追蹤一個籠統的 MTTR(Mean Time To Recovery),但把六段流程拆開後會發現,「MTTR」這個單一數字其實混合了至少四段完全不同性質的時間,而且每一段慢下來的原因也完全不同:
MTTD Mean Time To Detect
Detect 之前的時間:告警規則夠不夠敏感、覆蓋夠不夠完整?
MTTA Mean Time To Acknowledge
Detect 到 Triage 的時間:on-call 多快回應、IC 多快被指定?
MTTM Mean Time To Mitigate
Triage 到 Mitigate 完成的時間:runbook 夠不夠清楚、
有沒有已核准的低風險降級手段?
MTTR Mean Time To Resolve / Recover
到用戶 SLI 真正回穩為止:這段最容易受「殘留狀態」拖累
(見 ⑥ 段 Resolve 不是 Recover 的討論)
把 MTTR 拆成這四段的價值,在於它讓「這次事故為什麼恢復得比較慢」這個問題有了具體的答案來源,而不是籠統地說「這次比較久」。如果一季下來 MTTD 越拖越長,該檢討的是告警規則與覆蓋率;如果 MTTA 偏長,該檢討的是升級路徑與 on-call 輪值設計;如果 MTTM 偏長,該檢討的是 runbook 是否過期(見 ⑧ 段);如果只有最後一段 MTTR 偏長,通常代表系統帶著較多有狀態的殘留(快取、佇列),恢復注定需要更久,這時候該問的不是「怎麼修得更快」,而是「這個落差有沒有讓使用者在等待期間承受不必要的風險」。
一個常見誤解,是把「MTTR 有沒有變短」當成事故管理成熟度的唯一指標。這個指標有個明顯的反例:一個團隊如果把「單一根因」的簡單故障處理得越來越快,MTTR 平均值會持續下降,但如果同一時間,真正複雜、涉及多個版本座標的 AI workflow 故障完全沒有變快,這兩種故障混在同一個平均值裡,會讓儀表板看起來一片大好,卻完全反映不出真正該優先解決的那類故障。跟下篇會談到的「用平均值宣布恢復」是同一種陷阱的不同尺度版本——這裡是「用平均值宣布這一季進步了」。
傳統 SRE 的 postmortem 模板,是為「一段程式碼、一台機器、一個資料庫」的世界設計的。它問的問題大致是:哪個 deploy 出了問題?哪台機器過載?哪個查詢拖垮了資料庫?這些問題的共通點是答案通常是單一、離散、可以用版本控制系統精確定位的——git commit hash、deploy timestamp、config diff。
AI 系統的故障往往涉及多層次的版本與決策,而且這些層次彼此獨立變動、獨立部署、獨立失效:
傳統系統的故障座標 AI 系統的故障座標
───────────────── ─────────────────
code revision code revision
config code revision +
prompt version +
model version/route +
retrieval index version +
embedding model version +
tool schema version +
evaluator/threshold version
這張對照表想說的是:傳統系統故障時,「哪個版本出了問題」通常只有一個維度要查;AI 系統故障時,同一個使用者請求背後疊了至少六、七個可能各自變動的版本座標,而它們發布的節奏還經常不同步——prompt 可能一天改好幾次,模型版本可能幾週才換一次,retrieval index 可能是每小時重建,evaluator 的 threshold 又是另一組人在維護。任何一個維度的變動都可能是故障根因,但如果 postmortem 的模板只留了「code revision」這一格,其餘六個維度就會全部消失在證據鏈之外。
版本追蹤:記錄故障發生時的完整版本棧。
Evaluation 覆蓋:故障發生前,evaluation 為什麼沒有捕捉到?
這裡有一個經常被誤解的地方:很多團隊把「有做 evaluation」跟「evaluation 有效」畫上等號。發布前跑過 golden dataset、拿到一個看起來體面的分數,就當成「這個版本安全可以上線」的證明。但 evaluation 系統本身也是一套會失效的軟體——它可能因為 golden dataset 太舊、沒有涵蓋新的輸入分布而漏判;也可能因為評估邏輯本身有漏洞(例如只檢查回答格式是否符合 schema,卻沒檢查內容是否正確)而把明顯失敗的案例判為通過。
Postmortem 在這一段該問的,不是「evaluation 有沒有跑」,而是三層遞進的問題:
第一層:evaluation 有沒有跑?(最低標準,幾乎不會是答案)
第二層:evaluation 跑了,golden dataset 有沒有覆蓋到這次的失敗輸入?
第三層:即使覆蓋到了,evaluator 判斷的標準本身,有沒有把這種失敗誤判為成功?
第三層是最容易被忽略、也最值得深挖的一層——它問的其實是「我們用來判斷『系統有沒有壞』的那套系統,自己有沒有壞」,這是 Day 23 討論 evaluator 可信度時反覆強調的遞迴問題:evaluator 本身也需要被驗證,不能無條件信任它給出的綠燈。
把第二層的問題落地成一個可檢查的 golden dataset 條目,會長得像這樣:
{
"case_id": "policy-qa-citation-format-001",
"input": "遠端工作需要哪些核准流程?",
"expected_behavior": "回答必須包含 [source:...] 格式的引用",
"known_failure_modes": ["citation_format_missing", "citation_wrong_policy"],
"added_after_incident": "2026-04-citation-validation-regression",
"last_verified": "2026-06-01"
}
這個條目的 added_after_incident 與 last_verified 兩個欄位,正是把「第二層問題」變成可稽核事實的關鍵——一份 evaluation 系統如果無法回答「這個 golden dataset 裡有多少條目是某次真實事故後才補上的、多久沒被重新檢視過」,就沒有辦法誠實地宣稱自己的覆蓋率跟得上系統的變化速度。多數團隊在導入 evaluation 的第一年,golden dataset 幾乎全是上線前手動設計的案例;隨著時間推移,事故後回填的案例比例應該逐漸上升——這個比例本身,就是「evaluation 有沒有真的從故障中學習」的一個具體訊號。
安全攔截:系統的安全檢查是否工作?在哪裡失效了?
把「agent 本不該有的權限」這句話落地成程式碼層級的檢查點,會是這樣的對照:
# 事故發生時的狀態:agent 可以直接寫入認證狀態
def recover_account(user_claim: UserClaim, agent_ctx: AgentContext) -> None:
if agent_ctx.verify_selfie(user_claim.selfie):
auth_store.set_verified(user_claim.account_id) # 直接寫入,無人工關卡
# 事後改進:agent 只能「提案」,寫入權限收回給人工核准
def recover_account(user_claim: UserClaim, agent_ctx: AgentContext) -> None:
if agent_ctx.verify_selfie(user_claim.selfie):
review_queue.propose(
account_id=user_claim.account_id,
evidence=agent_ctx.verification_trace,
requires_second_factor=True, # 備用信箱或還原碼,缺一不可
)
# 沒有任何一行程式碼能繞過 review_queue 直接觸及 auth_store
這個對照想凸顯的,是「防護攔截了嗎」這個 postmortem 問題背後真正該檢查的位置——不是模型的判斷準不準,而是這段程式碼的結構本身,有沒有讓 agent physically 不可能繞過人工關卡。前一版的問題不在於 verify_selfie 判斷得不夠準,而在於就算判斷完全正確,auth_store.set_verified 這一行的存在本身就是風險——它讓一個原本該是「建議」的動作,變成了「執行」。postmortem 裡如果只問「這次辨識失準了嗎」,會完全問錯方向;該問的是「這個 agent 的程式碼裡,有沒有任何一條路徑,讓它可以繞過人工核准就改動高風險狀態」。
影響範圍明細:哪類輸入、哪段時間、哪個用戶群體受影響?
這個講究粒度的要求不是為了寫得漂亮,而是有實際後果的。「API 5xx 率上升 10%」這句話能做的事只有一件:讓管理層知道有事發生。但「使用 Retrieval 工作流的用戶在 14:00-14:45 UTC 間遇到 timeout」這句話能做至少三件事:告訴工程團隊該從哪個 span 開始查、告訴客服團隊該怎麼回覆受影響用戶的問題、告訴下一次做同類型變更的人「這個時間窗與這個工作流組合曾經出過事」。同一份 postmortem,粒度不同,能承載的行動完全不同——這也是為什麼本文一再強調「用戶結果」而非「系統症狀」作為敘述單位。
「公開版本與限制存取版本要分開」這句話說起來簡單,實際上需要一個具體的分流規則,否則團隊很容易因為怕漏寫細節,乾脆把兩個版本寫成同一份、事後再手動刪減——而手動刪減正是最容易漏東西的做法。比較可靠的方式,是讓兩個版本從一開始就由不同的資料來源產生:
公開版本(全公司可讀):
來源:dashboard 連結、trace_id、匯總後的 SLI 數字
不含:使用者輸入原文、模型輸出原文、任何可反推個資的片段
限制版本(僅事故相關人員 + 稽核角色可讀):
來源:公開版本的全部內容 + 原始 request/response 附件連結
存取:走既有的資料存取控制與稽核流程,開啟記錄本身也留痕
公開版本不是「限制版本刪減後的產物」,而是從一開始就只組裝可公開的素材;需要原始內容的人,走的是另一條本來就存在的存取途徑,而不是等著有人手動判斷「這句話該不該刪」。
Netflix 的例子:復盤不只記錄故障,也記錄「什麼防護有效」。
這個轉向值得多花一段解釋為什麼重要。如果一份組織裡所有的 postmortem 都只在「壞掉的時候」才被寫出來,這套文件系統久而久之會產生一種扭曲的自我形象——彷彿系統唯一值得記錄的時刻就是失敗的時刻,防護機制永遠隱形。但實際上,一個成熟系統裡「差點壞掉但被攔下來」的事件,數量遠遠多於真正演變成使用者可見故障的事件。只記錄後者,等於把組織的注意力全部導向「我們哪裡做得不夠好」,卻從不問「我們哪裡做對了、為什麼做對了、能不能把這個做對的機制複製到別的地方」。Netflix 把 near-miss 也寫進復盤,本質上是在建立一套「正向的證據鏈」——不只是避免重蹈覆轍,還要主動辨認並強化已經在工作的防線,這對 AI 系統尤其重要,因為一套 guardrail 攔下一次危險輸出的事件,往往比一次真正外洩的事件更常發生,卻更容易被忽視。
無責復盤(blameless postmortem)不是沒有責任,而是把責任落在可改的系統、流程與優先級,而不是個人。Google 的經驗顯示,當人們相信復盤不會被用來懲罰他們時,他們更願意透露完整的資訊,系統反而能更快改善。這句話背後有一個反直覺的因果關係,值得說清楚:追究個人責任不會讓系統更安全,反而會讓系統更危險——因為當工程師知道「說出真相」可能導致自己被究責,他們在復盤時會傾向於省略對自己不利的細節、選擇性地描述決策過程、甚至在未來遇到類似情況時,優先考慮「怎麼做才不會被怪」而不是「怎麼做對使用者最好」。無責復盤不是溫情主義,它是一套讓真實資訊能夠浮現的工程機制——這也是為什麼下篇會花更多篇幅拆解 Google 與 Netflix 具體怎麼把這套機制落地成日常實踐,而不只是停留在一句口號。
選 Day 26 中某一條 pager alert,進行一次「事後復盤」演習(postmortem training)。
這場演練會沿路產出三份東西:一份 Incident timeline(下面 markdown 模板逐階段填的內容本身)、一份更新過的 Runbook 片段(第三部分的卡點會回填進 runbook)、以及一份 Blameless postmortem(第二部分+Review 階段整理成文)。
在動手填表之前,先講一件事:這份演練用的是 markdown 模板,而不是自由格式的討論。這個選擇不是偷懶,是刻意的。事故復盤最大的敵人不是「大家不專業」,而是「討論會不自覺地滑向找戰犯或滑向技術細節的兔子洞」——一個開放式問題像「這次事故發生了什麼?」很容易變成漫無邊際的敘事,講到一半就會有人開始辯論「到底是誰的 code review 沒做好」。用結構化的模板逼問題聚焦在「系統條件」而不是「誰的錯」,這正是下篇會深入介紹的 Etsy 復盤方法論的核心精神——用「怎麼發生的」取代「為什麼發生」,用具體的觀察取代事後合理化的解釋。填這份模板時,請把每一格都想成「如果我不是當事人,我需要看到什麼證據才能相信這個結論」,而不是「我覺得原因大概是什麼」。
第一部分:重演六段流程
## Incident: [Alert 名稱]
### Detect 階段
- 哪一條 alert,何時觸發?
- Alert 的確切內容是什麼(例如「P99 latency > 5s」而非模糊的「API 變慢」)?
### Triage 階段
- 誰是 incident commander?誰負責應用層診斷、誰負責基礎設施層?
- 估計影響範圍:多少用戶、多少 request、多少錢的服務級別協議風險?
- 優先級判定:是停止所有新 rollout?還是只暫停該功能?
### Mitigate 階段
- 可用的降級策略是什麼?(停止 rollout、切 fallback、暫停非關鍵功能、限流)
- 執行時間與預期效果?
### Resolve 階段
- 故障的直接觸發因素何時、如何被移除?(例如 rollback 完成時間、故障節點下線時間)
- 移除觸發因素之後,還有哪些殘留狀態需要追上(cache、queue backlog、連線池)?
### Recover 階段
- 哪個 SLI 證明故障已解決?不是「error log 消失」,而是「用戶完成的工作回到基線」。
- 驗證方式:看 Metrics 的 P99 latency?還是需要 Trace 確認特定工作流的正常執行?
### Review 階段(產出 postmortem)
- 根本原因是什麼?(不是「工程師沒注意」,而是「什麼系統條件導致了這個 fault」)
- 哪個 failure mode 此次被暴露?(在 Day 11-13 的 failure mode 分類中是新增的嗎?)
第二部分:AI 系統特有的復盤問題
如果故障涉及 AI workflow(retrieval、LLM、tool execution),加入:
### AI 特有檢視
- 故障時的 model/prompt/index 版本是什麼?
- Evaluation 為什麼沒有提前發現?是 golden dataset 缺口、還是評估指標誤導?
- 安全檢查是否工作?(例如 tool policy decision、output validator)
- 輸入類型:是所有提示都出現問題,還是特定範圍(例如長上下文、特定領域)的提示?
第三部分:記錄卡住的地方
### 演練中卡住的地方
- Triage 時,我們花了多長時間才確認「是基礎設施問題還是應用問題」?
- 如果超過 5 分鐘,說明診斷步驟應該更清楚地寫進 runbook。
- Mitigate 時,是否明確知道「該切哪個 fallback」?
- 如果不知道,或備用方案本身也有問題,這就是 Action Item。
- Recover 時,SLI 的定義是否清楚?
- 如果「用戶已恢復」有多種解釋,runbook 應該定義標準驗證方式。
把這些卡點記下來,它們就能轉成下一週的改進工作:更新 runbook、補上診斷指南,並驗證 fallback 是否有效。
多數團隊寫演練紀錄時,只會留下「我們最後判定是 X 造成的」這種結論句,卡點——「我們花了 8 分鐘才確認是基礎設施還是應用問題」「我們不確定該切哪個 fallback,猶豫了 3 分鐘」——常常被當成過程雜訊,寫完結論就被略過。但這些卡點其實是整場演練裡最有價值的部分,理由很直接:結論通常只對這一次故障有用,卡點卻對下一次任何故障都有用。如果 Triage 花了 8 分鐘才確認故障層級,這代表 runbook 裡「如何快速區分基礎設施問題與應用問題」這一步驟本身寫得不夠清楚或工具不夠齊全,這個缺口不會只在這次故障出現,它會在下一次任何跟這條診斷路徑重疊的故障裡重複發生,直到有人真的去補上它。
這也是為什麼這場演練刻意設計成「三份產出」而非「一份 postmortem」:postmortem 記錄「發生了什麼」,但卡點記錄「系統與流程還缺什麼」,兩者服務不同的讀者——前者給想理解這次事故的人看,後者給下一個值班的人用。
跑這場演練時,團隊常犯的第一個錯誤發生在還沒開始填模板之前:選錯誰來當 incident commander。一個很直覺、但通常錯誤的分派方式,是讓「最懂這個系統的人」當 IC:
✗ 常見但錯誤:讓資深工程師(最懂系統內部)兼任 IC
→ 這個人同時忙著親自下場除錯、又要協調溝通、又要做優先級決策
→ 三件事互搶注意力,結果每件事都做得比分開做慢
✓ 較好的分派:IC 負責協調與決策,不親自動手除錯
→ IC 的工作是「彙整各路人馬回報的資訊,決定下一步」
→ 真正動手查 log、追 trace 的,是 IC 底下的診斷人員
這個誤解的根源,是把「最懂技術細節」跟「最適合做協調決策」當成同一種能力。實際上這是兩種不同的認知負荷:親自除錯需要的是深度專注、一次只追一條線索;IC 需要的是持續切換視角、同時追蹤多條並行的回報、並且在資訊不完整時快速拍板。讓同一個人同時做這兩件事,最常見的後果是這個人「卡在」自己正在看的那段 log 裡出不來,導致其他人回報的關鍵資訊沒有被即時吸收,決策因此被拖慢——這正是本節③段強調「誰負責應用層診斷、誰負責基礎設施層」這個問題背後真正想問的:先把「動手查」跟「決定怎麼做」這兩個角色明確分開,IC 才有餘裕做好協調這件事。
[ ] 六段流程中,每段都指向不同的決策者與行動
[ ] Resolve 與 Recover 有明確區分(觸發因素解除,不等於用戶端已回穩)
[ ] Recover 的驗證標準明確(不是「看起來正常」,而是「SLI X 回到基線 Y」)
[ ] AI 工作流的版本、評估、安全檢查都有被問及
[ ] 卡點被明確記錄,將成為下一期 runbook 的改進項目
[ ] 演練時間控制在 30-45 分鐘(模擬高壓下的實戰)
本文的演練限於文字紀錄與桌上討論,不會實際觸發 pager、切換 rollout 或呼叫真實的 incident 工具。
Google SRE 的 postmortem 文化強調「writing a postmortem is not punishment—it is a learning opportunity for the entire company」,核心假設是所有人出於好意,問題落在系統條件與流程;Netflix 則走得更遠,把「防護有效攔下的 near-miss」也寫進復盤,讓文化從「我們做錯了什麼」變成「我們的防線是否在工作」。
這兩個案例都不是靠一次感人的會議達成,而是靠可追蹤的 Action Item:下一次復盤時,回頭檢查上一次的改進有沒有被執行、效果如何。本文的 SRE Lab 演練也該用同一個標準檢驗自己,卡點有沒有變成 runbook 更新,不是演練有沒有講得順。
這句話值得再拆開講一次,因為「Action Item 兌現率」聽起來像是個管理指標,但它其實是判斷一個團隊的復盤文化是真實還是表演性質的最可靠訊號。表演性質的復盤有一個典型症狀:每次事故後都寫出一份格式完美、語氣誠懇的文件,Action Item 欄位也填得工整,但三個月後回頭看,上一季的 Action Item 完成率不到三成,而且沒有人追問過為什麼。這種復盤文化會產生一種危險的錯覺——團隊感覺自己「有在檢討」,實際上系統的脆弱點一個都沒有被真正修補,下一次類似的故障照樣會發生,而且往往發生在跟上一次幾乎一樣的地方。
判斷復盤文化是否健康,可以用一個很簡單的追問取代對文件品質的主觀評價:
不要問:「這份 postmortem 寫得夠不夠好?」
改問: 「上一份 postmortem 的 Action Item,有幾項真的做完了?」
前者是主觀的、容易被文筆帶偏的判斷;後者是客觀的、可以直接查 ticket 系統得到答案的事實。一個組織如果願意公開追蹤這個數字,即使數字不好看,也遠比從不追蹤來得健康——因為它至少誠實地反映了「檢討」跟「改進」之間目前的落差有多大。
要把「兌現率」這個抽象概念變成真的能追蹤的東西,Action Item 本身的寫法就要夠具體。一個常見的反面案例是這樣的:
✗ Action Item:「加強 retrieval 品質監控」
→ 三個月後查 ticket:狀態仍是 Open,沒有 owner,沒有 due date
✓ Action Item:「在 retrieval_score < 0.4 時觸發 P2 alert,
owner: @teamA,due: 2026-04-15,
驗收方式:下次同類事故發生時,alert 是否在 5 分鐘內觸發」
→ 三個月後查 ticket:狀態 Closed,附上驗收時的 alert 截圖
差別不在於文字寫得漂不漂亮,而在於後者的每一個欄位都能被追問:有 owner,就能問「這件事現在誰負責」;有 due date,就能問「為什麼過期了還沒做」;有明確的驗收方式,就能在下一次類似事故發生時直接檢驗這個 Action Item 到底有沒有生效,而不是憑印象覺得「應該有改善吧」。前者則是一句誰都無法反對、但也誰都無法驗收的口號,這種 Action Item 就算寫進 postmortem,也幾乎注定會停留在 Open 狀態直到被遺忘——不是因為團隊懶惰,而是因為它從一開始就沒有被設計成「可以被完成」的任務。
跑完演練最容易漏掉的一步,不是找根因,而是在 Resolve 之後就急著宣告結束。觸發因素移除了(rollback 完成、故障節點下線、config 還原),不代表用戶端已經完全回穩,快取、queue backlog 或連線池可能還需要時間追上;沒有明確的 Recover SLI,團隊很容易把「錯誤訊息不見了」誤當成「使用者已經沒事」。
回頭看這個漏洞為什麼這麼容易發生:Resolve 跟 Recover 在人的感受上幾乎是同一件事——告警安靜了、儀表板變綠了、on-call 群組裡的緊急訊息停了,這些訊號會同時出現,而它們對應的實際上是兩個不同層次的狀態。告警安靜代表「觸發故障的那個條件不再滿足」;儀表板變綠代表「新進來的請求開始正常」;但快取裡舊版本產生的錯誤回答還在,queue 裡堆積的待處理任務還沒消化完,這些「歷史負債」不會因為觸發因素消失就自動清空。跑過幾次演練後會發現一個規律:Resolve 跟 Recover 之間的落差,跟系統有沒有狀態(stateful)成正比——一個完全無狀態的 API,Resolve 跟 Recover 幾乎同時發生;一個帶著快取、佇列、連線池的系統,這個落差可能是幾分鐘,也可能是幾十分鐘,而這個落差正是使用者仍在承受、但團隊已經覺得「沒事了」的危險空窗期。
另一個容易在演練中被忽略的,是版本座標。prompt、model、retrieval index 只要有一項沒被記下來,調查就會退化成「大家還記得昨天改了什麼嗎」,這比想像中更常發生,也是本文一再強調版本追蹤的原因。跑過幾次演練會發現,版本座標缺失的模式通常不是「完全沒人記錄版本」,而是「記錄了一部分版本,但剛好漏了造成這次故障的那一個」——這也是為什麼 ③ 段強調版本欄位應該由部署管線自動預填,而不是仰賴人在演練或事故現場「記得該記什麼」。
把 Resolve 到 Recover 之間的落差寫成一條實際的時間線,會更容易看出問題出在哪裡:
14:02 觸發條件:retrieval index 部署到錯誤版本,回傳過期文件
14:05 告警觸發,on-call 開始處理
14:11 rollback 到前一版 index 完成 ← Resolve(觸發條件已移除)
14:11 告警安靜、dashboard 轉綠、on-call 群組沒人再發訊息
14:11–14:19 但 LLM 回應快取(TTL 15 分鐘)仍持續吐出 rollback 前產生的錯誤答案
14:19 快取自然過期,錯誤回答比例真正降到基線 ← Recover(使用者 SLI 恢復)
這段時間線裡,14:11 到 14:19 這八分鐘就是本節開頭說的「危險空窗期」——所有內部訊號都顯示「沒事了」,但外部使用者在這八分鐘裡收到的仍然是錯誤答案。如果 on-call 在 14:11 看到告警安靜就宣告 incident 結束,這八分鐘的使用者影響就永遠不會被計入任何一份 postmortem,事後也無從得知這次故障的真實影響範圍比大家以為的還要大。
Incident response 的目的不是寫出一份漂亮文件。它是讓用戶恢復正常工作,並讓同一條失敗路徑下次更難重演。對 AI 系統而言,這需要把 deployment、prompt、model、retrieval、tool、evaluator 與安全決策當成同一條證據鏈。先用可逆的動作降低傷害,再以 SLI 確認用戶真的恢復,最後把已知案例回填到測試、dataset、runbook 與 release gate。不要把一次 incident 的經驗留在 Slack 的幾百則訊息裡。
把這條證據鏈畫出來,會是這個樣子:
Deployment ──┐
Prompt ──┤
Model ──┼──▶ 同一次使用者請求
Retrieval ──┤ │
Tool ──┤ ▼
Evaluator ──┘ 故障發生時的完整快照
Safety ──┘ │
▼
regression test / dataset / runbook / release gate
這張圖想強調的是:故障發生的那一刻,上面七條軸線各自處於某個特定版本或狀態,它們共同組成了「這次故障為什麼會發生」的完整條件。事後回填的目的,是把這個完整條件轉換成可重複驗證的資產——一個 regression test 鎖住這次的輸入輸出組合、一筆 dataset 案例擴大下次 evaluation 的覆蓋範圍、一段 runbook 縮短下次同類故障的診斷時間、一條 release gate 規則攔住下次同樣的變更組合。少了任何一段回填,這次故障付出的代價就只換到「這一次恢復服務」,沒有換到「系統整體變得更難壞」。
Runbook 不是事故結束後才拿來補文件的地方。
它要在訊息跳出、上下文還不完整、值班的人剛被叫醒時,幫忙縮小選項。
如果它只有架構圖、服務名稱與一句「查看 Grafana」,那叫目錄,不叫 runbook。
這裡有一個很常見的用詞混淆,值得先釐清:「runbook」「playbook」「checklist」三個詞經常被交換使用,但它們在解決的問題上其實不同。
Checklist(檢查清單)
回答:「有沒有漏做什麼?」
例如:部署前確認 migration 已跑、config 已同步
Playbook(劇本)
回答:「這一類事件,標準流程是什麼?」
例如:資安事件發生時,誰要在幾分鐘內做什麼
Runbook(操作手冊)
回答:「這一個特定 alert,現在該怎麼判斷與行動?」
例如:AskApiHighLatency 觸發時,第一步查什麼、第二步做什麼
三者不互斥,一個成熟的 incident 管理系統通常三者都有,但本篇聚焦的是 runbook——因為它是值班者在最沒有時間思考的那幾分鐘裡,唯一會打開來看的東西。如果 runbook 裡塞的是 checklist 該放的內容(一長串「確認過了嗎」的勾選項),或是 playbook 該放的內容(組織層級的角色與升級規則),值班者翻開它時得先在腦中做一次「這跟我現在遇到的 alert 有什麼關係」的翻譯,這個翻譯的時間成本,在凌晨三點被叫醒的狀態下,經常就是壓垮決策品質的最後一根稻草。
一份可用的 runbook 至少回答五件事:
這個 alert 代表什麼使用者風險?
先看哪個證據能區分常見原因?
可以立刻做的低風險緩解是什麼?
何時要升級、停止 rollout 或宣告 incident?
什麼證據足以宣告恢復?
同一個 alert 不該讓每一位 on-call 自行發明診斷路徑。
把 alert、dashboard、log query、trace query 與操作權限連成一個小型資料契約。
以下是可放進 repository 的範例;欄位名稱可改,意圖不要改。
runbook_id: ask-api-latency-v1
service: ask-api
alert: AskApiHighLatency
owner: ai-platform-oncall
severity: page
user_symptom: "回答等待時間明顯變長,部分請求可能逾時"
first_check:
- "確認 deploy_id 是否在告警前 30 分鐘變動"
- "比較 gateway 與模型 provider 的 P95 latency"
- "抽查同一 request_id 的 trace,判斷卡在 retrieval、model 或 tool"
safe_mitigation:
- "暫停正在進行的 rollout"
- "把低優先級請求切到較小模型或 queue"
unsafe_without_approval:
- "提高全域 timeout"
- "關閉 output validator"
recovery_sli:
metric: ask_api_duration_seconds
condition: "P95 在連續 15 分鐘低於 2 秒,且 task failure rate 未升高"
escalate_when:
- "錯誤預算燃燒率超過既定門檻"
- "安全 validator 有系統性漏攔"
- "無法判斷資料是否外洩"
這不是讓 YAML 取代人的判斷。
它是把「第一個人腦中知道的事」轉成下一個人也能驗證的起點。
逐一拆開這份契約裡幾個容易被輕忽的欄位,會發現每一個都在回答一個具體的失敗模式:
user_symptom 這個欄位刻意用使用者語言而非系統語言描述——「回答等待時間明顯變長」而不是「P95 latency exceeded threshold」。原因是值班者第一時間需要判斷的是「這件事有多急」,而使用者語言比系統語言更快建立這種直覺;系統語言留給下面的 first_check 去處理診斷細節。
safe_mitigation 與 unsafe_without_approval 分成兩組,不是形式主義。它們解決的是一個具體的高壓決策問題:在事故現場,人的認知資源被壓縮,這時候「哪些動作我可以自己做決定」與「哪些動作我需要先問過別人」如果沒有事先劃分清楚,值班者要嘛過度保守(連該做的緩解都不敢做,拖長恢復時間),要嘛過度大膽(把高風險動作當成快速修復,見下一節的具體案例)。這條界線需要在沒有壓力的時候先劃好,而不是在壓力最大的時候才臨場判斷。
escalate_when 的三個條件都不是純技術指標,而是「風險判斷已經超出這個 runbook 能安全處理的範圍」的訊號——錯誤預算燃燒率、safety validator 的系統性漏攔、資料外洩的不確定性,這些都是「即使我照著 runbook 一步步做,也不該再獨自往下走」的停損點。一份好的 runbook 不只告訴你怎麼做,也誠實地告訴你它的能力邊界在哪裡。
寫好一份 runbook,容易讓團隊產生一種錯覺:這份文件從此就是可信賴的。但 runbook 描述的是系統在某個時間點的行為,系統本身持續在變——服務改了 owner、fallback 的目標服務下線了、alert 的 threshold 被調整過但沒人回頭改 runbook 裡寫的數字。沒有任何機制會在這些變動發生時「順便」提醒你去更新對應的 runbook,這件事永遠得靠人記得。
實務上能做的補強,是把「這份 runbook 上次被驗證過」這件事本身也變成一個欄位,而不是只留在某個人的記憶裡:
last_drilled: "2026-06-12"
last_drilled_by: "ai-platform-oncall"
drift_check:
- "fallback 目標服務 legacy-answer-svc 是否仍在線上?"
- "recovery_sli 的門檔數字是否與目前 SLO 一致?"
last_drilled 沒有標準的過期天數,但一個簡單的經驗法則是:如果一份 runbook 超過一次發布週期都沒被真的用過或演練過,就該把它當成「未驗證」,而不是「應該還可以用」。AWS 官方對 2017 年 2 月那次 S3 大型中斷的事後說明,正是這個道理的真實版本——問題不是缺乏復原程序,而是負責 metadata 與 placement 的兩個子系統「已經好幾年沒有經歷過完整重啟」,第一次真正跑這套復原程序,就是在生產事故現場,恢復時間因此遠比預期久。runbook 的風險不只是「寫得不夠好」,也包括「曾經寫得很好,但系統已經變了、規模已經變了,沒有人真的再驗證過一次」。
「看 log」不是步驟。
一個可執行步驟需要輸入、預期畫面、分支與停止條件。
以 P95 latency 告警為例:
輸入:Alert 的開始時間與 deployment ID。
觀測:比較 API、retrieval、LLM、tool 四段 span 的 duration。
判斷:只有 LLM span 增加,先查 provider;所有 span 增加,先查入口或資源飽和。
動作:依判斷採取既定 fallback,或升級給相應 owner。
停止:恢復門檻尚未連續達成前,不宣告 resolved。
這種寫法看似囉嗦,但 incident 時最貴的是模糊。
模糊會讓三個人同時查三個無關方向,最後還不知道誰做過什麼。
拆開「輸入、觀測、判斷、動作、停止」這五個欄位,會發現它們對應到認知心理學裡描述決策流程的一個經典結構:先確定要看什麼資料,再確定資料告訴你什麼,再確定不同資料指向的分支,再決定行動,最後決定什麼時候可以停手。這五步任何一步被省略,決策流程都會出現裂縫——省略「輸入」,值班者不知道從哪裡開始查;省略「觀測」,不同人可能看不同的儀表板,得出互相矛盾的結論;省略「判斷」,就算大家看到同樣的數據,也可能做出不同決定;省略「動作」,知道問題出在哪卻不知道該做什麼;省略「停止」,最常見的後果就是本篇反覆強調的「把 Resolve 誤當 Recover」——沒有明確的停止條件,人會傾向用「感覺穩定了」代替「數據證明穩定了」來做判斷。
團隊把 runbook 寫成熟之後,很自然會想到下一步:既然每個步驟都已經明確到「輸入、觀測、判斷、動作、停止」,何不乾脆寫成自動化腳本,alert 一觸發就自動執行,省掉值班者手動操作的時間?這個直覺在傳統系統裡經常是對的——重啟一個無狀態服務、擴充一組 worker,這類動作本來就該自動化。但把這個直覺原封不動套進 AI 系統的 runbook,會漏掉一個關鍵差異:
Runbook 自動化(傳統系統常見的正確做法)
觸發:CPU > 90% 持續 5 分鐘
動作:自動擴充 2 個 worker instance
風險:可回復、代價已知、幾乎沒有模糊地帶
Runbook 自動化(套用在 AI 系統上容易出錯的做法)
觸發:quality evaluator 分數低於門檻
動作:自動切換到 fallback 模型或關閉某個 validator
風險:fallback 模型的能力邊界可能跟主模型不同,
「切換」本身可能造成使用者看到的答案品質產生
另一種、事先沒被評估過的偏差——這是一個需要
人判斷「這個當下值不值得」的決定,不是單純的
資源擴充
差別在於:CPU 擴充 worker 的後果空間很小,成功或失敗都容易預期;但「自動切換模型」或「自動關閉某個安全檢查」的後果空間,往往取決於觸發當下的具體情境——同一個 quality evaluator 分數低於門檻,可能是暫時的資料波動,也可能是真正的模型退化,兩種情況下「自動切到 fallback」造成的使用者體驗完全不同。這正是為什麼 runbook 的資料契約裡要刻意把 safe_mitigation 跟 unsafe_without_approval 分成兩組——前者代表後果空間夠小、可以放心自動化或授權值班者立刻執行;後者代表後果空間仍需要人在當下判斷,自動化反而是把本該由人承擔的風險評估,悄悄轉嫁給一段沒有情境判斷能力的腳本。runbook 寫得越成熟,這個誘惑越大,因為步驟已經寫得足夠清楚、「看起來」很適合直接自動化——但清楚不等於後果空間小,這是兩件不同的事。
AI 服務尤其容易出現一種危險的「快速修復」:
延遲高 → 拉長 timeout
回答失敗 → 關閉 validator
模型限流 → 放大 retry
檢索錯誤 → 暫時略過 source check
這些動作可能讓圖表變綠,卻把使用者風險推到下一層。
拉長 timeout 會耗住 worker。
關閉 validator 會讓錯誤或危險回答直接送出。
放大 retry 可能把 provider 的短暫限流放大成流量風暴。
這四個「快速修復」的共通結構值得看清楚:它們都是用犧牲某一層防護,換取表面指標(延遲、error rate)立刻好看。這種交換之所以誘人,是因為它的效果幾乎是即時的——拉長 timeout,逾時的請求瞬間變少;關閉 validator,被攔下的回應瞬間變少;放大 retry,暫時性錯誤瞬間變少。圖表在幾分鐘內從紅翻綠,這種立即的正回饋,在高壓、被緊盯著進度的 incident 現場,是極難抗拒的誘惑。但這些交換換來的代價,往往要等到下一層(worker 資源、內容安全、依賴系統的穩定性)才會顯現——而顯現的時候,通常已經是另一場獨立的事故了。
2024 年 12 月 OpenAI 的大停機就是教訓:新部署的 telemetry service 沒有執行限制,導致 Kubernetes API 被淹沒。事件期間,如果團隊從 runbook 的「快速修復」清單跳到「關閉 telemetry」,可能藏住真正的問題。正確的做法是:先用低風險降級(如限流、非關鍵功能降級)換取時間,再在高壓下做出可逆的決策。不能因為著急就把 unsafe_without_approval 的項目變成第一招。
這裡值得多講一句 OpenAI 這次事故裡最容易被忽略的細節:出問題的不是核心服務本身的程式碼,而是一個新上線、原本用來「幫忙觀測系統」的 telemetry 元件。這提醒我們一個常被低估的原則——任何新加入系統的元件,無論它的目的是不是觀測或防護,本身都是新增的故障來源,不會因為它的職責是「幫忙看住系統」就自動免疫於自己讓系統倒下。這正是為什麼 runbook 裡的診斷路徑,也應該考慮「新上線的旁路元件(telemetry、sidecar、collector)本身異常」這種情境,而不是只假設故障永遠發生在核心服務裡。
因此 runbook 要刻意區分 safe_mitigation 與 requires_approval。
遇到不確定的安全或資料風險時,讓服務少回答一點,通常比自信地多回答錯話好。
這個「少回答一點」的原則,某種意義上也是本篇要反覆強調的核心立場的縮影:面對不完整的資訊,選擇可逆、保守的動作,永遠比選擇看起來有效率、但賭上未知風險的動作更划算——這句話在 Mitigate 階段是這樣,在下篇的時間線紀錄與判讀原則段落裡,也會用不同的形式再出現一次。
下篇會接著處理三件事:一場 AI incident 的時間線該記哪些軸線、一套可重跑的七步驟 DIY 演練怎麼把版本追蹤與事件分類寫進程式碼、以及 Google、Netflix、Etsy 與 Richard Cook 各自怎麼看待「復盤」這件事。
這篇是 Learning SRE for the AI Era 系列的一部分。
Build → Trace → Break → Measure → Evaluate → Recover → Improve.