Day 22 的 Rollback Gate 解決了一個很急的問題:服務出現異常時,現在是否有足夠證據支持回滾,而且準備回去的版本是不是 last known good。
但回滾指令回傳成功,事情還沒有結束。流量可能還在舊的 route,資料修復可能還沒完成,錯誤率也可能只是短暫下降。這時候不能只看一行 rollback completed,就把 incident 標成恢復。
今天加入 Recovery Verification Gate。它是一個唯讀驗證層,回答的問題是:
rollback 之後,系統是否真的回到宣告的 target candidate,而且在一段完整觀察期間維持健康?
它不切 traffic、不修資料、不重跑 deployment,也不自動關閉 incident。它只把「恢復」需要的 evidence 固定下來,交給負責人做最後 closeout。
想像你在電商平台上線新的付款流程。上線後 webhook timeout 增加,團隊依照 Day 22 的 Rollback Gate 回到上一個 candidate。rollback command 顯示成功,但使用者回報仍然刷不過卡。
這時候可能有幾種情況:
所以要把三個狀態分開:
| 狀態 | 它真正證明什麼 | 它不能假裝證明什麼 |
|---|---|---|
rollback_executed |
rollback 指令完成,且回傳執行證據 | 使用者已恢復、資料已安全 |
recovery_verified |
target、traffic、window、metrics、checks 與 evidence 都對上 | incident 已經被人類正式結案 |
human_closeout |
負責人讀過 evidence,決定後續追蹤與結案 | 可以省略 audit 或 postmortem |
Recovery Verification Gate 的目標不是讓團隊更晚恢復,而是避免把「指令成功」誤報成「服務恢復」。
回滾改變了 serving state,所以回滾之後不能沿用回滾之前的結論。要建立一個新的 recovery observation,至少固定:
rollback_id:這次回滾事件的識別碼。run_id:產生這批觀察的執行批次。source_candidate_id:發生異常、原本正在 serving 的 candidate。target_candidate_id:Recovery Gate 預期已經 serving 的 candidate。source_commit、input_digest、environment_id、target。rollback:執行狀態、實際套用的 target 與 execution evidence。recovery_window:是否完成、觀察秒數與樣本數。metrics:使用者結果與系統壓力的量測值。checks:health、traffic、data integrity 等 required checks。traffic:route state 與實際 serving candidate。recovery_evidence:名稱與 digest,讓結果能回連到同一份 observation。只要 identity 有一個欄位不同,就先停在 blocked_identity。不要因為兩份資料的 candidate 名稱相似,或都是 production,就把別的 incident 當成這次的恢復證據。
flowchart LR
R[Rollback executed] --> I[Fix recovery identity]
I --> W[Collect recovery window]
W --> M[Read metrics and checks]
M --> T[Read serving traffic]
T --> E[Bind recovery evidence]
E --> C{All conditions pass?}
C -->|否| B[Blocked with reason code]
C -->|是| V[Recovery verified]
V --> H[Human closes incident]
圖 1|Recovery Verification Gate 從回滾執行結果重新收集 window、metrics、traffic 與 evidence,最後才交給人類結案。
rollback completed 不是單純看 API HTTP 200,而是要確認 observation 中的 rollback result:
{
"rollback": {
"status": "completed",
"applied_target_candidate_id": "candidate-b",
"execution_evidence": "sha256:rollback-execution"
}
}
以下狀態都不能進入恢復驗證:
status 是 failed、pending 或缺少。applied_target_candidate_id 不是 intent 宣告的 target。Gate 會分開輸出 rollback_not_completed 與 rollback_target_mismatch。這比只回傳 false 更有用,因為 release owner 知道是要重新確認執行結果,還是要修正 recovery intent。
恢復不是一個瞬間。intent 要先寫明最少觀察條件:
{
"min_recovery_window_seconds": 900,
"min_samples": 100
}
Gate 會檢查三件事:
complete 必須是 true。duration_seconds 不得少於最小秒數。sample_count 不得少於最小樣本數。如果 window 只收集了 30 秒,或剛好沒有流量,不能用漂亮的平均值填補缺口。缺口會輸出:
recovery_window_incomplete
recovery_window_too_short
recovery_sample_count_shortfall
這讓「還需要觀察」成為明確狀態,而不是讓模型自行猜測恢復了。
即使 traffic 已經回到 target candidate,仍然要檢查使用者真正感受到的結果。範例用四個指標說明:
| 指標 | intent 門檻 | 它要回答的問題 |
|---|---|---|
| availability | 不低於 0.999 |
使用者是否拿得到服務? |
| p95 latency | 不高於 450 ms |
大多數較慢請求是否恢復? |
| error rate | 不高於 0.01 |
失敗是否已經降回可接受範圍? |
| queue depth | 不高於 20 |
背後工作是否仍然堆積? |
任何一項超標,就回報對應的 recovery_metric_* reason。不要只看 availability,因為「服務有回應」不代表付款流程沒有持續變慢或排隊。
Metrics 是量測結果,checks 是驗證步驟。兩者要一起存在。這次 fixture 要求:
rollback_execution
health
traffic
data_integrity
每一項都必須明確是 passed。pending、skipped、failed 或缺欄位都要 fail-closed:
recovery_check_missing:data_integrity
recovery_check_not_passed:health
這個規則很重要,因為一個 health dashboard 綠燈,不能替代 data integrity 的驗證。恢復判斷要保留每個責任面,而不是把所有結果壓成一個總分。
Recovery Gate 會再次讀取 route state:
{
"traffic": {
"state": "serving",
"serving_candidate_id": "candidate-b"
}
}
以下兩件事都要成立:
serving。target_candidate_id。如果 route 還在 draining,或某個 region 仍然服務 candidate A,就輸出:
recovery_traffic_not_serving
recovery_serving_candidate_mismatch
這也是為什麼 Recovery Verification 不能只讀 rollback command 的輸出。執行控制面與實際流量面是兩份不同 evidence,必須再次回讀。
最後一關是 evidence digest。intent 先宣告預期的 recovery observation:
{
"recovery_evidence": {
"name": "recovery-observation",
"digest": "sha256:recovery-current"
}
}
observation 讀回來的名稱與 digest 必須完全一致。名稱不一致代表你可能拿錯資料;digest 不一致代表內容可能在核對後被替換。
這裡不是追求「所有欄位都很完整」的形式,而是要讓下一個人能回答:
我現在看到的 recovery verified,是根據哪一份資料得出的?
如果答案不清楚,狀態就應該停在 blocked_evidence,而不是繼續傳遞一個無法回放的綠燈。
example-recovery-verification-gate/ 是一個只使用 Python 標準函式庫的最小範例。它把 intent 與 observation 當成輸入,依固定順序輸出 JSON:
cd day23/example-recovery-verification-gate
python3 -m unittest -v
python3 -m py_compile recovery_verification_gate.py test_recovery_verification_gate.py
python3 recovery_verification_gate.py fixtures/intent.json fixtures/observation.json
成功 fixture 的輸出是:
{
"allowed": true,
"state": "recovery_verified",
"reasons": []
}
範例特別保留三個界線:
allowed=true 只代表恢復證據符合宣告的條件。它不是「事件已結案」,也不是自動重新發布的許可。
這條系列一路把「AI 可以猜」的空間縮小:
flowchart LR
C[Fresh context] --> D[Change evidence]
D --> R[Release candidate]
R --> S[Runtime stability]
S --> U[User-facing SLO]
U --> A[Human approval]
A --> X[Executed change]
X --> RB[Rollback gate]
RB --> RV[Recovery verification]
RV --> H[Human closeout]
圖 2|證據鏈從新鮮 Context 走到恢復驗證;每一次狀態改變都需要自己的 intent 與可回讀 evidence。
Recovery Verification Gate 的重點不是拖延結案,而是把「服務恢復」從一句口頭報告,變成下一個人可以檢查的狀態。
請記住三句話:
rollback_executed 不等於 recovery_verified;指令成功後仍要觀察真實結果。recovery_verified 只代表證據通過,human_closeout 仍由真正負責的人讀取、決定並留下後續責任。day23/article.md
example-recovery-verification-gate/
diagrams/recovery_verification_gate_flow.mmd
diagrams/recovery_verification_states.mmd
目前 iThome、YouTube 與 GitHub 外部同步仍交由後續 Release lane;本 Producer 只產製與驗證本機內容,沒有執行外部發布。