iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
AI Engineering

Learning SRE for the AI Era:從 SRE Lab 到 Production AI Reliability系列 第 32 篇

Day 20(下)|Error Budget 與 Burn Rate:預算用完要真的改變行為

  • 分享至 

  • xImage
  •  

GitHub:darkstar1227/learning-sre-for-ai-era

結論先說:算出 burn rate 只是前半段,真正決定行為的是把它接進 rollout gating、事故應變與 postmortem 流程——沒有這一步,SLO 永遠只是牆上的裝飾。

承接上文:(上)篇把 error budget 與 burn rate 的觀念講清楚(budget 是使用者讓渡的信任額度,不是故障配額)、用 DIY 寫出一份可執行的 policy 工具、拿真實案例套過公式,並示範怎麼把 burn rate 寫成 Prometheus 可查詢、可雙窗口告警的規則。這篇接著談:AI 服務要分幾種 budget、budget 怎麼接進 rollout gating、事故發生時 policy 該回答什麼問題,以及常見的錯法有哪些。

⑨ AI 服務要有多個 budget,但不要假裝它們可互換

Day 19 將 task success、quality 與 safety 分開;Day 20 再加上 availability。實務上可先用下列四個 bucket 討論,不必一開始就替每一個 bucket 訂一組數字。

bucket 事件例子 主要量測方式 是否可以和別桶相抵? 常見 owner
Availability timeout、5xx、stream 中斷 request/synthetic probe 不可以 service owner
Workflow retrieval 無結果、tool schema 無效 span、workflow event 不建議 workflow owner
Quality 來源不支持回答、關鍵事實錯誤 golden dataset、抽樣覆核 不可以 product + AI owner
Safety 越權 tool action、敏感資料外洩風險 policy event、人工判定 絕對不可以 safety owner

「不能相抵」不是說它們沒有關係。一個 provider timeout 可能同時拉低 availability 與 task success;retrieval index 換版可能讓 quality 下滑。差別在於 dashboard 和 release decision 要保留失敗的來源,而不是把它們加權成一個看似平靜的平均數。

容易被忽略的 Workflow bucket:介於「活著」與「答對」之間的失敗

上面那張四桶表格裡,Workflow 這一桶最常被團隊直接省略——它既不像 availability 那樣有現成的 HTTP 狀態碼可以量,也不像 quality 那樣需要複雜的語意評估,導致很多團隊乾脆把它的事件混進 availability 或直接不量測。但 Workflow 失敗有它自己獨特的樣貌:retrieval 明明呼叫成功(沒有 timeout、沒有 5xx),卻回傳零筆相關文件;agent 呼叫 tool 時傳入的參數格式不符合 schema,被下游拒絕,但這個呼叫本身在 HTTP 層是「成功」的。

一次「回答不了問題」的請求,實際上可能是:

  availability 正常(HTTP 200,2 秒內回應)
      │
      ▼
  retrieval 呼叫向量資料庫 ── 成功連線,但比對出 0 筆相關文件 ← Workflow bad event
      │
      ▼
  LLM 只能在「沒有檢索到任何文件」的情況下生成答案
      │
      ▼
  quality evaluator 判定「答案缺乏來源支持」 ← Quality bad event

這個例子刻意示範一件事:同一次請求,可能同時觸發 Workflow 與 Quality 兩個桶的 bad event,而且它們是因果相連的——retrieval 找不到文件(Workflow 層的問題),直接導致 LLM 被迫瞎編(Quality 層的症狀)。如果只量 Quality、不量 Workflow,dashboard 只會告訴你「答案品質下降了」,卻不會告訴你「是因為檢索端出了問題,不是模型本身變笨了」——這兩種根因需要完全不同的修復動作:前者該檢查向量索引是否過期、embedding 模型是否需要更新;後者才需要檢查 prompt 或模型本身。Workflow bucket 存在的價值,正是替 Quality 問題提前標出「這其實是上游的錯,不是 AI 本身的錯」這條線索。

一個桶要不要獨立出來,看的是「這個失敗誰該負責修」

四個桶不是固定不變的清單——不同團隊、不同產品階段,可能需要拆出更多桶(例如把 Workflow 拆成 Retrieval 跟 Tool-calling 兩個獨立的桶),也可能一開始先合併幾個桶、等系統複雜度上升後再拆分。判斷「要不要獨立出一個新桶」,比較實用的問法不是「這個失敗類型聽起來夠不夠獨特」,而是「修好這個失敗,需要的是不同的 owner、不同的修復流程嗎?」如果答案是肯定的,拆成獨立的桶就有意義;如果兩種失敗最後都會找同一組人、用同一套流程處理,硬拆成兩個桶只是徒增管理負擔,不會讓決策變得更清楚。這正好呼應④節「先求最小可運作再逐步演化」的態度——不需要在第一天就把桶分得又多又細,等實際運作中發現某個桶的責任歸屬經常混淆,再拆分也不遲。

為什麼「加權平均成一個總分」聽起來合理,卻是個陷阱

把四個桶的分數加權平均成一個「總體健康分數」,是很多團隊在第一次做 dashboard 時會忍不住做的事——畢竟一個數字比四個數字好看、好報告。但這個總分會製造一種假象:

Availability: 99.8%  (權重 40%)
Workflow:     98.5%  (權重 20%)
Quality:      92.0%  (權重 30%)  ← 明顯低於閾值,卻被稀釋掉
Safety:       99.99% (權重 10%)
──────────────────────────────
加權總分:    97.6%  ← 看起來「健康」,實際上 quality 已經越過警戒線

97.6% 這個數字會讓人以為系統整體表現良好,但真正該觸發行動的 quality 92% 已經被 availability 的高分稀釋掉了。這正是③節 GPT-4「變懶」事件的加強版:不只是「HTTP 200 掩蓋了語意失敗」,而是「就算你真的量了 quality,只要把它跟其他三桶加權平均,它一樣可能被藏起來」。四個桶各自獨立呈現、各自有各自的門檻與 owner,才能避免這種平均數陷阱——這也是為什麼 Day20/DIY 的 RollingWindowBudget 要讓 BudgetType 分開實例化,而不是設計成一個「total_score」欄位。

quality budget 的分母不能假裝全知

availability 的分母通常很明確,quality 卻不是。你未必知道每一筆線上回答的標準答案。比較誠實的做法是把 coverage 寫出來:

線上總回答數:5,000
被 deterministic rule 檢查:5,000
進入 LLM evaluator:800
人工複核:80
已確認重大 quality failure:3
未覆蓋或結論不確定:4,120

這份資料不能推出「quality failure rate = 3 / 5,000」。它只支持「在 80 筆人工複核中確認 3 筆重大失敗」,而且抽樣方式會影響解讀。coverage、抽樣規則、evaluator version、人工 disagreement 都要連同分數保存。

這也是 quality budget 初期常適合用 release gate 或人工 review,而不是照 availability 一樣設 paging threshold 的理由。數學很吸引人,但分母不可靠時,漂亮的小數點只是裝飾。

把上面那組 coverage 數字存成結構化資料,而不是寫在一份 markdown 表格裡,才有辦法在時間軸上追蹤「coverage 有沒有悄悄下降」:

@dataclass
class QualityCoverage:
    window_name: str
    total_responses: int
    deterministic_checked: int   # 走過規則式檢查的筆數
    evaluator_sampled: int       # 進入 LLM evaluator 的筆數
    human_reviewed: int          # 人工複核的筆數
    confirmed_failures: int      # 人工複核中確認的重大失敗

    @property
    def human_review_rate(self) -> float:
        if self.total_responses == 0:
            return 0.0
        return self.human_reviewed / self.total_responses

    @property
    def failure_rate_within_reviewed(self) -> float:
        """只能算『被複核樣本』裡的失敗率,不能推論到全體。"""
        if self.human_reviewed == 0:
            return float("nan")  # 沒有複核資料,誠實回報「不知道」
        return self.confirmed_failures / self.human_reviewed

failure_rate_within_reviewed 刻意在 human_reviewed == 0 時回傳 nan 而不是 0——回傳 0 會製造一種「複核了、而且沒發現問題」的假象,回傳 nan 才誠實反映「這段時間根本沒有複核資料,無法判斷」。這個小小的實作選擇,呼應的正是第⑦節「分母可能為零時不能把 NaN 硬轉成健康」的原則:quality budget 的分母比 availability 更容易出現「資料不足以判斷」的情況,程式碼層級就該把這種不確定性明確表達出來,而不是用一個看似正常的預設值悄悄蓋過去。

NaN 這個設計也解釋了為什麼 quality budget 的 dashboard,不該只畫一條「failure rate」的折線——如果折線圖遇到 NaN 直接跳過不畫,畫面上會出現一段平滑無縫的空白,讓人誤以為那段時間「什麼都沒發生」,而不是「沒有資料」。比較誠實的呈現方式,是額外疊加一條 human_review_rate 的折線,讓讀圖的人能同時看到「複核覆蓋率」跟「複核範圍內的失敗率」——覆蓋率掉到接近零的那幾段時間,failure rate 折線的意義自然就該被打折扣看待,而不是照單全收。

理想的 quality dashboard 應該同時呈現兩條線,而不是只有一條:

failure_rate_within_reviewed
  ▁▁▂▁▁▃▁▁▁▂▁▁         ← 這段看起來很平穩,但不能只看這條線
  ░░░░░░░░████░░░░     ← human_review_rate(灰色低代表複核覆蓋率低)
                 ↑
                 覆蓋率突然拉高的這段,failure_rate 才真正有參考價值;
                 覆蓋率長期偏低的那幾段,平穩的 failure_rate 線
                 可能只是「沒認真檢查過」,不是「真的沒問題」

safety event 不該等到「平均超標」

若 policy engine 記錄到未授權的高風險 tool action,或發現輸出含不應外洩的敏感內容,處理方式通常不是先累積到月度預算再說。policy 應寫成事件級別的攔截、保全、升級與復原程序。

safety event
  → 保留 request_id、trace_id、policy version 與最小必要證據
  → 停止受影響的 tool route 或 promotion
  → 通知指定 safety owner
  → 依資料處理與事件回應程序判斷後續通報

本文不提供任何組織的通報時限或法律判定。這些要求取決於資料類型、所在地、合約與內部程序,必須由有權責的人制定。

Day20/DIY/scripts/calculate_budget.py 的 scenario_4_safety() 用一個「baseline 有 2 次合理的 safety block(tool invocation error),加上 1 次異常事件(guardrail 未能攔截 PII 擷取嘗試)」的假設場景,示範這套判定的具體樣子。刻意把異常事件的說明寫成「1 個請求取得敏感資料,但未造成使用者傷害(被下游的 log filter 攔截)」——這個細節值得留意:即使最終沒有造成實際傷害,只要 guardrail 本身的判斷邏輯失效了,這件事本身就足以觸發⑨節的單一事件規則,不需要等到「真的造成傷害」才算數。這正是 safety budget 跟其他三桶最大的心態差異:其他桶問的是「這次的影響有多大」,safety 桶問的是「防線本身有沒有破洞」,即使這次剛好被下一道防線接住了。

把 safety event 的處理畫成狀態機,而不是一條 if-else

上面那段「保留證據 → 停止路由 → 通知 owner → 判斷通報」的流程,寫成散文很容易被讀成一連串「先做這個、再做那個」的線性步驟。但實務上 safety event 的處理更接近一個狀態機——事件可以在調查過程中反覆在幾個狀態間移動,而不是一路往前跑到底:

                ┌──────────────┐
                │   DETECTED   │  policy engine 或人工回報觸發
                └──────┬───────┘
                       │ 保留證據(request_id / trace_id / policy version)
                       ▼
                ┌──────────────┐
        ┌──────▶│ CONTAINED    │  受影響 route 或 promotion 已停止
        │       └──────┬───────┘
        │              │ 通知 safety owner
        │              ▼
        │       ┌──────────────┐
        │       │ INVESTIGATING│──── 發現證據不足/誤判 ────┐
        │       └──────┬───────┘                          │
        │              │ 確認為真實 safety violation        │
        │              ▼                                   ▼
        │       ┌──────────────┐                    ┌──────────────┐
        │       │  ESCALATED   │                    │  DISMISSED   │
        │       └──────┬───────┘                    └──────────────┘
        │              │ 依內部程序判斷是否需要外部通報
        │              ▼
        │       ┌──────────────┐
        └───────│  RESOLVED    │  根因修復、postmortem 完成
                └──────────────┘

畫成狀態機的好處,是能明確表達「CONTAINED(已隔離)」跟「RESOLVED(已解決)」是兩個不同的里程碑,中間可能隔著數天甚至數週的調查——而在這段調查期間,受影響的功能應該持續維持 CONTAINED 狀態,不能因為「調查還沒有結論、看起來也沒有新的事故」就自行解除限制。這正是很多團隊在處理 safety 事件時最容易犯的錯:把「暫時沒有觀察到新事件」誤判成「已經沒事了」,提早把 CONTAINED 解除,卻還沒有真正走到 RESOLVED。DISMISSED(判定為誤報)分支也值得特別標注——不是每一個觸發 policy engine 的事件最後都會被證實是真正的 safety violation,但「先隔離、再調查」的順序不能反過來,即使最後證實是誤判,也應該留下這次誤判的紀錄,用來校準 policy engine 的敏感度。

⑩ 將 budget 接到 rollout:否則它只會在月底被拿來檢討

error budget policy 最有用的時刻,通常不是 dashboard 變紅,而是有人準備部署。把規則寫進 change 流程,讓「可不可以繼續」不必由最會辯論的人決定。

把 budget 檢查接進 rollout 流程,等於在 CI/CD pipeline 裡多插一個關卡:

git push / merge
      │
      ▼
run tests, build image
      │
      ▼
┌─────────────────────────┐
│  Budget Gate(新增的關卡) │
│  查詢 availability/      │
│  quality/safety 三桶       │
│  目前的 burn rate 與消耗%   │
└─────────────────────────┘
      │
      ├─ Green:全部健康 ──────────────▶ 正常 rollout
      ├─ Yellow:局部偏快 ─────────────▶ 小比例 canary + 加強觀測
      └─ Red:page 級 burn 或 safety event ▶ freeze,change 被擋下

這個關卡不需要是一個全自動、會擋下 pipeline 的硬性 gate——很多團隊一開始只是把當下的 burn rate 數字顯示在 PR 或 deploy dashboard 上,讓值班者「決定要不要按下部署鍵」時有數據可看,而不是自動阻擋。等團隊對這套規則有足夠信心,再考慮把它變成自動化的硬 gate。重點是「這個檢查存在」,而不是它一開始就要多聰明。

實務上,burn rate 的決策經常比預期更快。某個消費級 AI 應用在推送新 prompt 版本後,開始觀察到生成質量下降(用戶投訴增加)與成本激增(同樣任務需要更多重試)。Cost per successful task 的 burn rate 從 1.0 升到 1.5,意味著月度 cost budget 會在三週內被消耗光。根據既有的 error budget policy,當任何 dimension 的 burn rate 達到 1.5 時,團隊應該暫停新功能推送。Team lead 決定執行 rollback,新 prompt 被回滾到前一版本。結果是 cost 重新回到正常水位、用戶滿意度恢復。如果沒有預先定義好這個 policy,這個決定會在混亂中延遲數小時,期間被浪費的成本與失去的用戶信任會持續增長。

把「cost 也能燒 budget」寫成算式,其實跟 availability burn rate 是同一套結構,只是把「bad event」換成「超支金額」:

月度 cost 目標 = 預期每千次任務成本 × 預期任務量
可接受 cost burn rate = 1.0(照原訂預算精準花完)

實際 cost per successful task = 實際花費 / 成功完成的任務數
cost burn rate = 實際 cost per successful task / 目標 cost per successful task

這裡「成功完成的任務數」是分母,不是「請求數」。一次因為輸出品質不足而被使用者重新請求的呼叫,錢已經花掉,但沒有產出一次「成功」——它會讓分母不動、分子(花費)增加,burn rate 自然升高。這正是上面案例裡「質量下降」與「成本激增」同時發生的原因:兩者共用同一條因果鏈,不是巧合。也因為如此,cost budget 通常不能只看 API 帳單的絕對金額,而要看「每個成功結果的成本」,才抓得到「重試變多、但沒人注意到」這種慢性失血。

把 cost burn rate 的算式接進同一套 RollingWindowBudget 結構,其實只需要多定義一個 BudgetType,計算邏輯完全可以複用——這也是④節把 SLODefinition/WindowMetrics/RollingWindowBudget 拆成三個獨立 dataclass 的另一個好處:新增一種 budget type,不需要重寫計算邏輯,只需要替它定義門檻:

class BudgetType(str, Enum):
    AVAILABILITY = "availability"
    QUALITY = "quality"
    SAFETY = "safety"
    COST = "cost"              # 沿用同一套計算邏輯,只是「bad event」換成「超支金額」

# 用法完全比照前面三種 budget type:
cost_slo = SLODefinition(
    budget_type=BudgetType.COST,
    slo_percentage=100.0,          # 目標:完全不超支(cost burn rate = 1.0 為基準)
    window_days=28,
    owner="Finance + Eng",
)

# WindowMetrics 的 bad_events 這裡代表「超出目標的花費單位數」,
# 而不是字面意義上的「壞事件次數」——這是刻意借用同一套結構,
# 而不是另外為 cost 寫一套獨立的計算程式。

這裡刻意留一個提醒:把 cost 硬塞進「budget type」的框架裡,好處是計算邏輯可以重複使用,但也隱藏一個風險——bad_events 這個欄位名稱天生是為「失敗次數」設計的,拿來裝「超支金額」時,容易讓後來維護程式碼的人誤解欄位語意。如果團隊真的決定把 cost 也接進同一套系統,比較誠實的做法是額外寫清楚的註解或型別別名,而不是預設每個人都會自動理解這裡的「bad event」其實是「錢」,不是「錯誤」。

以下是可放進 release checklist 的教學版本:

## Release pre-check

- [ ] availability budget 在 policy 的 rollout 門檻內。
- [ ] 最近短、長窗口沒有符合 page 條件的 burn alert。
- [ ] Golden Dataset 的比較結果已附上 dataset、prompt、model 與 evaluator version。
- [ ] 已知 quality regression 有 owner、風險說明與核准紀錄。
- [ ] safety policy、tool permission 與 retrieval index version 已記錄。
- [ ] rollback 目標版本與負責人已確認。

這份 checklist 不應變成「全部勾完才准上線」的官僚牆。它的工作是把能造成使用者損失的未知項目叫出來。低風險 UI 調整和會改變 tool action 的 agent workflow,當然可以有不同的審查強度。

三種 rollout 狀態

狀態 條件範例 可以做什麼 必須留下什麼
Green budget 健康,無持續 burn 正常 rollout 版本、觀測連結、rollback 點
Yellow budget 消耗偏快或 quality 訊號不確定 小比例 canary、加密集觀測 owner、觀察期限、停止條件
Red page 級 burn、重大 quality regression 或 safety event freeze 受影響 promotion 事件紀錄、修復計畫、解除條件

Red 不代表世界末日,也不代表所有開發工作必須停止。它應限制的是受影響服務或風險路徑的變更;安全修補、回滾、改善可觀測性、故障隔離通常反而要加速。

一個 prompt promotion 的例子

假設 prompt-v12 讓 FAQ 的語氣更簡短,離線評估多數題目通過;但 canary 後發現「條件題」的 source-validation failure 增加。HTTP 200 沒有下降,availability budget 仍健康。

此時 policy 可以要求:

  1. 停止 prompt-v12 擴大到更多流量。
  2. 將失敗 trace 去識別化後加入 regression dataset。
  3. 確認 failure 是 prompt、retrieval chunk、文件版本還是 evaluator 誤判。
  4. 修正後用相同 dataset 比較 v11 與候選版本。
  5. 把 evaluator coverage 與人工覆核結果寫進 release record。

這是一個 quality budget 改變行為的例子。若只看 availability SLO,這次 regression 很可能一路擴大,因為監控圖會很安靜。

這個例子也值得跟①節開頭那個「凌晨三點」的場景對照著看:兩者都是「同一份 policy,在不同時間點被拿來用」,差別是①節發生在事故現場、決定的是「要不要叫醒人」,這裡發生在部署前,決定的是「要不要繼續推」。同一份文件同時服務這兩種完全不同節奏的決策——一個要求秒級反應,一個容許花上幾分鐘討論——是 error budget policy 真正的設計挑戰所在:寫得太細會拖慢部署前的討論,寫得太粗又無法在凌晨三點提供足夠的判斷依據。比較實際的做法,通常是把「凌晨三點用的」跟「部署前用的」拆成兩份互相引用的文件——一份是精簡的 alert runbook(見第⑧節步驟 D),另一份是稍微詳細一點的 release checklist(本節),兩者共用同一套 budget 定義與門檻,但呈現給讀者的資訊密度不同。

把 Green/Yellow/Red 寫成 canary 流量比例的具體規則

⑩節前段那張 Budget Gate 的架構圖,畫出了「檢查」發生在流程的哪一個位置,但沒有回答一個更實際的問題:Yellow 狀態下的「小比例 canary」,具體應該是多少比例?停留多久?誰有權把它從 5% 推到 50%?以下是一份把這些數字寫死的教學版本,目的是示範「budget 狀態」跟「流量比例」之間應該有明確的對應關係,而不是憑感覺調整:

canary_policy:
  service: policy-api
  stages:
    - budget_status: green
      traffic_percentage: 100
      minimum_soak_time: 0m
      auto_promote: true

    - budget_status: yellow
      traffic_percentage: 5
      minimum_soak_time: 30m
      auto_promote: false        # 需要人工確認才能繼續推進
      promote_condition:
        burn_rate_short_window_lt: 2.0
        quality_regression_detected: false

    - budget_status: red
      traffic_percentage: 0      # 完全不放行新流量
      minimum_soak_time: null    # 直到 policy 明確解除 freeze 前,沒有時限
      auto_promote: false
      required_action: "回到 ⑨ 節的 safety 狀態機,或 ⑩ 節的 release checklist 重新走一次"

這份 policy 想表達的重點,是「Yellow」不該是一個模糊的中間地帶,而應該有具體的流量比例、具體的停留時間、具體的晉級條件——沒有這些數字,Yellow 狀態很容易變成「大家都知道現在有點不對勁,但沒人知道該觀察多久、什麼條件下可以放行」的乾等狀態,跟①節「凌晨三點」那個沒有 policy 的場景犯的是同一種錯,只是換了一個發生在白天、發生在 rollout 決策上的版本。auto_promote: false 這個欄位也值得留意:Yellow 狀態刻意不設計成自動晉級,即使 promote_condition 的數字都達標,也需要一個人按下確認——這是因為 budget 狀態轉為 Yellow 通常意味著「有些訊號還不夠明確」,自動化在訊號不明確時去做「繼續擴大流量」這個決定,風險不對稱:判斷錯了頂多多等一下,判斷過早卻可能讓一個真正的 regression 擴散到更多使用者。

值得多說一句的是 minimum_soak_time 這個欄位。Green 狀態的 0m 代表 budget 健康時不強制等待,符合本文反覆強調的「先求最小可運作」——沒有異常訊號時,不需要為了流程而流程;Yellow 狀態的 30m 則是刻意給的下限,逼著即使數字已經回穩,也要讓系統在小比例流量下真實運作過一段時間,才有資格繼續推進。這是因為 canary 階段的流量通常刻意選擇低風險使用者或低峰時段,過早把觀察到的「暫時穩定」直接放大到全量流量,等於是在賭「low-risk 樣本的結果可以代表 high-risk 樣本」——這個賭注在 AI workflow 尤其危險,因為③節提過的語意層問題往往只在特定使用情境(複雜問題、長對話、邊界案例)才會浮現,小比例、低風險的 canary 樣本很可能剛好躲過這些情境,讓一個實際上帶病的版本看起來安然通過了 30 分鐘的觀察期。

⑪ 今日可自行完成的 Lab 流程

以下步驟假設你已完成前面幾天的 metrics 基礎。它們是讀者可自行執行的練習說明,不代表本文已建立、啟動或驗證 Day20/DIY。

步驟 A:先寫 SLI contract

建立一份 sli-contract.md,把一條 SLI 寫成可被反駁的句子:

SLI name: policy-api availability
Good event: HTTP 2xx response that is parseable and completes within 2 seconds.
Bad event: timeout, HTTP 5xx, or unparseable response.
Exclusions: scheduled synthetic traffic is tagged separately and is not mixed into user traffic.
Window: rolling 28 days.
Owner: policy-api team.
Known gap: this SLI does not measure factual correctness or safety.

如果你不知道某個事件要不要算進分子,不要先急著寫 query。先找出那個事件對使用者代表什麼,再讓 product、service 和 on-call owner 共同確認。SLO 不是監控團隊單方面宣布的計算題。

步驟 B:用試算表或小程式核對手算

用下面這組假設資料,手算後再交給你自己的工具核對:

total_requests = 2,400
bad_events = 18
slo_target = 0.995

error_ratio = 18 / 2,400 = 0.0075
allowed_error_ratio = 1 - 0.995 = 0.005
burn_rate = 0.0075 / 0.005 = 1.5

預期結果是 burn_rate = 1.5。如果結果是 0.015,你算到的多半是 error ratio;如果是 150,通常是百分比與小數混用。這兩種錯誤都很常見,而且一進告警設定就會把人折磨很久。

算完這組小數字後,可以拿第②節那個手機遊戲的真實案例再核對一次思路,確認自己不是只會套公式:四週 3,663,253 次請求、97% SLO,反推出允許失敗數是 109,897;一次 NPE 事件在 4 小時內吃掉 14,066 次失敗,占 13%——用你剛剛練過的公式重新算一次「4 小時窗口的 burn rate」會發現,光憑「占 13% 月度預算」這個數字,看不出這次事件當下有多急迫,一定要換算成「這 4 小時內的實際 bad rate 相對 SLO 允許值高出多少倍」才看得出來。這正是①、⑤節反覆強調的:累積消耗的百分比和當下的燒錢速度,是兩個必須分開算、分開讀的數字。

步驟 C:為短、長窗口各建立一張圖

你的 dashboard 至少需要同時看到:

短窗口 error ratio
長窗口 error ratio
短窗口 burn rate
長窗口 burn rate
請求量或完成工作量
剩餘 error budget
目前 deployment / prompt / model route

預期觀測不是兩條完全相同的線。短窗口通常較敏感、較鋸齒;長窗口較慢,但更能確認趨勢。若兩者完全相同,先檢查 range vector 或 dashboard variable 是否不小心指向同一段時間。

步驟 D:寫一個明確的告警訊息

把 alert annotations 寫到讓不熟服務的人也能開始調查。範例如下:

Summary: policy-api availability budget is burning quickly.
SLO: 99.5% over 28 days.
Observed: 1h burn rate 12.4; 6h burn rate 5.1.
Traffic: 1,860 requests in the long window.
First actions: stop non-essential rollout; inspect recent deployment,
dependency health, and error traces; follow runbook URL.
Escalate: page policy-api on-call now; page dependency owner when the
failing dependency is confirmed.

把實際 dashboard URL、runbook URL 和 routing key 補進你的版本,但不要把 secret、完整 prompt 或使用者資料寫進 annotation。

步驟 E:演練三個決策,而不是只讓圖表變紅

可在不碰 production 的前提下,用假資料或本機 Lab 分別模擬下列結果:

情境 觀測結果 你應寫下的決定
短暫 5xx 尖峰 短窗口高,長窗口低 暫不 page,設定觀察期限與升級條件
持續 dependency timeout 短、長窗口都高 page、暫停受影響 rollout、檢查 dependency owner
200 但 source validation 失敗 availability 正常,quality regression 停止 prompt promotion、加入 dataset、人工複核

每個情境都要寫出「誰決定」「何時解除」「證據在哪裡」。只寫「請立即處理」不是 runbook。

演練這三個情境時,順便問自己一個延伸問題:如果同一天先後遇到「短暫 5xx 尖峰」跟「200 但 source validation 失敗」,你的 policy 會不會因為前者被判定「暫不 page」,就連帶放鬆對後者的警覺?這是實務上很容易發生的心理偏誤——值班的人剛處理完一次「虛驚一場」,容易把下一個訊號也預設成同一類。availability 與 quality 是兩個獨立的桶(見第⑨節),這個演練值得刻意練習「同一天,兩種訊號,兩套獨立判斷」,而不是讓前一個結論影響後一個判斷。

步驟 F:試著讓自己的門檻「吵起來」,再修正它

⑬節錯法七提到告警門檻會在壓力下被悄悄調鬆,這個現象最好的預防方式,是在門檻剛設計好的時候就主動壓力測試它,而不是等上線後被真實的誤報疲勞牽著鼻子走。用假資料模擬一整週的「正常波動」,餵進你在步驟 D 寫好的告警規則,統計一下這組門檻在完全沒有真實事故的情況下,一週會誤報幾次:

模擬情境:7 天的假設流量,故意加入下列正常波動
  - 每天凌晨 2-4 點流量降到平日的 15%(低流量時段)
  - 週末流量比平日低 30%
  - 每天固定有一次批次任務造成 5 分鐘的短暫延遲尖峰

執行後記錄:
  ticket 級告警觸發次數:___
  page 級告警觸發次數:___
  其中你判斷為「合理該觸發」的次數:___
  其中你判斷為「不該觸發、門檻或守門邏輯需要調整」的次數:___

如果模擬出來的誤報次數多到你自己都想調鬆門檻,這正是在問題還沒真正上線、還沒有人被半夜吵醒之前,用可控的方式先把門檻或低流量守門邏輯改對;如果模擬完全沒有觸發任何告警,也該反過來懷疑:這組門檻是不是訂得太寬鬆,遇到真實故障時可能反應太慢。這個步驟不需要複雜的工具,一支跑過⑥節那些手算數字的小腳本,配合幾組刻意設計的假資料,就足以先把最明顯的雜訊問題攔在上線之前。

Lab 驗收清單

  • [ ] SLI 的 good event、bad event、排除項與 owner 都能被另一位同事讀懂。
  • [ ] 能手算 error ratio、allowed error ratio 與 burn rate,且沒有把百分比當小數。
  • [ ] dashboard 同時呈現短、長窗口與 traffic,沒有只看單一比例。
  • [ ] alert 內容含 SLO、窗口、流量、第一步與 escalation 對象。
  • [ ] rollout policy 說明 Green、Yellow、Red 下分別能做什麼。
  • [ ] availability、quality、safety 的失敗沒有被加成一個無法解釋的總分。
  • [ ] 所有練習資料都明確標為假設資料,沒有被描述為 production 成果。

⑫ 事故發生時,budget policy 要能回答什麼

error budget 不會幫你找到 root cause。它要做的是在 investigation 尚未完成前,先避免風險持續擴大。以下是值班者收到 page 後可使用的最小問題清單:

1. 哪個 SLO、哪兩個窗口觸發?分子、分母與流量是多少?
2. 是哪一個 route、region、deployment 或 dependency 集中失敗?
3. 最近是否有 rollout、prompt、model route、retrieval index 或 policy 變更?
4. 失敗是 HTTP / timeout,還是 technical success 下的 workflow / quality 問題?
5. 哪個行動可以立即降低使用者損失:rollback、feature flag、降級、隔離或限流?
6. 誰擁有下一步,何時再評估解除 freeze?

問題 4 刻意放在中間。很多 AI 系統的事故一開始只看 HTTP status,等發現使用者結果不對時,已經過了幾個 deployment。Day 21 會把 metrics、logs、traces 接起來,讓這張清單有資料可查。

誰該接手哪一種問題:一張責任對照表

六個問題問完之後,緊接著的困難常常不是「該做什麼」,而是「該找誰做」。一份完整的 policy 應該替每一種 budget type 指定明確的第一線責任人,而不是讓值班工程師自己去猜這次的問題歸誰管:

觸發的 bucket 第一線 owner 何時該往上升級 升級對象
Availability service on-call page 級門檻持續超過 30 分鐘未解 service 團隊 lead
Workflow 相關子系統 owner(retrieval/tool 團隊) 根因跨越多個子系統,責任不明確 架構或平台團隊
Quality product + AI owner 聯合 golden dataset 大量失敗、疑似模型層系統性問題 ML/model provider 對接窗口
Safety safety owner 任何疑似違規(不需要等到「持續」) 立即,見⑨節狀態機

這張表最重要的一格是 Safety 那一列的「何時該往上升級」:其他三桶都寫著某種「持續一段時間」或「規模夠大」才升級的條件,唯獨 Safety 寫的是「立即」——這不是排版失誤,而是刻意呼應⑨節「safety event 不該等到平均超標」的立場:其他三桶的升級門檻可以用消耗比例或持續時間來衡量,Safety 的升級門檻則是「發現了,就升級」,中間沒有觀察期。這張表也解釋了為什麼④節的 SLODefinition 要求每個 budget type 都填寫 owner 欄位——owner 不是一個裝飾性的 metadata,而是事故當下決定「該打電話給誰」的依據,缺了它,六個問題答得再精確,也可能因為找不到對的人而延誤處理。

把這六個問題排成一個決策流程,比條列清單更容易在壓力下依序執行:

收到 page
  │
  ▼
Q1 哪個 SLO/窗口觸發?分子分母是多少?───▶ 判斷這是不是雜訊(樣本太小)
  │ 不是雜訊
  ▼
Q2 集中在哪個 route/region/dependency?──▶ 縮小懷疑範圍
  │
  ▼
Q3 最近有變更嗎?───────────────────────▶ 有:優先假設是這次變更造成,
  │                                        準備 rollback
  │ 沒有
  ▼
Q4 是 HTTP 層失敗,還是 200 但語意/工作流失敗?──▶ 決定接下來該查哪一組訊號
  │                                              (traces/logs vs evaluator)
  ▼
Q5 現在能做什麼降低使用者損失?────────────▶ 執行:rollback/flag/降級/隔離
  │
  ▼
Q6 誰接手、何時再評估解除?────────────────▶ 記錄下來,結束這一輪

這張流程圖沒有回答「根因是什麼」——那是 postmortem 與後續調查的工作。它回答的是一個更急迫、範圍更小的問題:「在還不知道根因之前,我現在應該做什麼,才能讓情況不繼續惡化?」這正是 error budget policy 存在的核心價值,也是為什麼它不能等到事後才寫:一份寫在事故發生「之前」的流程,才有機會在凌晨三點被值班者直接拿來用,而不是臨時現場發明一套邏輯。

不要把例外寫成預設逃生門

policy 常見的破口是「第三方故障不算」「壓力測試不算」「內部使用者不算」寫得太寬。排除項可以存在,但每一種都要說明:

  • 為何排除,以及使用者是否真的不受影響。
  • 誰有權宣告排除,是否留下時間範圍與證據。
  • 被排除的事件是否仍需 incident record、capacity 改善或供應商追蹤。

例如 LLM provider timeout 即使不算在某個供應商 SLA 的 error budget,對你的使用者仍然可能是可用性失敗。把它從一個報表排除,不會讓使用者突然拿到答案。

一份可以直接抄的事件時間軸模板

上面六個問題回答的是「現在該做什麼」,但事件結束後,postmortem 需要的是「當時發生了什麼、按什麼順序發生」。與其在事後憑記憶拼湊,不如從收到 page 的第一分鐘就開始記錄一份結構化的時間軸——格式不需要複雜,重點是每一筆都帶時間戳記與資料來源:

2026-09-21 03:14 UTC  [ALERT]   1h burn rate 12.4 觸發 page(policy-api availability)
2026-09-21 03:16 UTC  [ACK]     on-call 確認收到,開始調查
2026-09-21 03:19 UTC  [OBSERVE] dashboard 顯示錯誤集中在 /api/answer route,其他 route 正常
2026-09-21 03:22 UTC  [OBSERVE] 最近一次 deploy 是 02:58 UTC 的 prompt-v12 rollout,時間點吻合
2026-09-21 03:25 UTC  [ACTION]  執行 rollback 到 prompt-v11,宣告 Yellow → Red
2026-09-21 03:31 UTC  [OBSERVE] burn rate 開始回落,1h 窗口降到 3.2
2026-09-21 03:47 UTC  [OBSERVE] burn rate 降到 0.8,低於 ticket 門檻
2026-09-21 04:17 UTC  [DECISION] 觀察 30 分鐘無復發,解除 freeze,記錄未完成的 quality 調查
2026-09-21 09:00 UTC  [FOLLOWUP] 排入 postmortem,owner:policy-api team

這份時間軸最重要的價值不是「記錄下來給誰看」,而是寫的當下就會強迫你回答⑫節那六個問題——[OBSERVE] 那幾行對應 Q1、Q2、Q3,[ACTION] 對應 Q5,[DECISION] 對應 Q6。如果你發現自己寫不出某一行(例如遲遲確認不了「最近是否有變更」),這件事本身就是一個訊號:可能是 deploy 紀錄沒有做到可以快速查詢,也可能是 dashboard 沒有把「目前線上版本」跟「burn rate 圖表」放在同一頁。⑭節列的「一份假資料 incident 演練紀錄」,指的就是這種格式的產物。

⑬ 常見的錯法:數字正確,決策仍然錯

錯法一:把 99.9% 當成所有服務的目標

99.9% 不是可靠性的預設答案。每月約 43 分鐘的不可用時間對某些內部報表可接受,對付款、醫療流程或安全控制可能完全不可接受。目標要從使用者旅程與修復成本開始,而不是從三個 9 開始。

第②節提過 Google 自己內部就沒有把單一數字套用在所有產品:企業導向的 Google Apps for Work 對外承諾 99.9%,2006 年還在衝刺功能的 YouTube 卻刻意訂得更低;同樣做廣告系統的 AdWords 與 AdSense,也因為使用者情境不同而容忍完全不同的延遲。這不是 Google 特有的奢侈——任何團隊都可以問自己同一個問題:這個服務的使用者,在它失敗的那幾分鐘裡,實際承擔的代價是什麼?答案決定了 SLO 該訂多嚴,而不是行業慣例或隔壁團隊的數字。

錯法二:預算只在月底報告

月底才看 budget,等於只知道自己已經撞牆。burn rate 的價值在於過程中的速度訊號;release gate 的價值在於撞牆前改變行為。這正是第②節「手機遊戲 API」那個真實案例想凸顯的對比:如果團隊只在月底看報表,NPE 事件與資料庫停機這兩起故障,會混在同一個「這個月消耗了 78% 預算」的總結數字裡,分不出哪一起是 4 小時的急性爆發、哪一起是 20 小時的慢性失血,也就抓不到「這次真正該優先修的是什麼」。

錯法三:error ratio 降了就解除 freeze

短窗口回到正常,可能只是流量變少或 incident 暫時躲起來。解除條件應至少包含:根因假設、修復或緩解措施、觀察窗口、明確 owner,以及是否有未完成的 quality/safety 調查。一個常見的踩雷情境是深夜流量本來就低,錯誤率的分母跟著變小,讓比例「看起來」恢復正常,但這其實正是第⑥節「低流量時,比例會騙人」的翻版——只是這次是被拿來當作解除的理由,而不是觸發告警的理由,同一個陷阱換了個方向出現。

一份寫得完整的 freeze 解除紀錄,通常需要涵蓋下面這些欄位,而不是只寫一句「已恢復正常」:

freeze_lift_record:
  incident_id: INC-2026-0921-01
  freeze_started: 2026-09-21T03:25:00Z
  freeze_lifted: 2026-09-21T04:17:00Z
  root_cause_hypothesis: "prompt-v12 rollout 引入的 retrieval prompt 格式錯誤"
  mitigation_applied: "rollback 到 prompt-v11"
  observation_window_after_mitigation: "30 分鐘,1h 與 6h burn rate 皆低於 ticket 門檻"
  outstanding_investigations:
    - "為何 CI 的離線評估沒有攔到這個 regression(quality gate 的覆蓋率問題)"
  approved_by: "on-call lead + policy-api team lead"

outstanding_investigations 這個欄位刻意保留,是因為「burn rate 回到正常」跟「這次事故的所有疑點都已經釐清」是兩件不同的事——解除 freeze 只代表「立即的使用者影響已經被控制住」,不代表 postmortem 已經完成。把還沒查清楚的問題明確寫下來,能避免它們在事後被遺忘——這正是⑭節提到的「一份假資料 incident 演練紀錄」該具備的完整度,也是它跟一句簡單的「已修復」在資訊量上的差別。

錯法四:把 evaluator 的分數當成 quality SLO

LLM-as-judge 或 rule-based evaluator 可以是訊號,不能自動變成真相。若 evaluator prompt、model 或資料集改版,趨勢線可能變了,卻不是系統品質真的變了。把 evaluator version 當成 telemetry 的一部分,並保存可供人工抽查的樣本。這個錯法跟第③節「模型沒換版本號,行為卻明顯漂移」是一體兩面:一邊是被評估的模型可能悄悄變了,另一邊是拿去評估別人的 evaluator 本身也可能悄悄變了——兩者都要求把「量測工具的版本」本身視為需要追蹤的變因,而不是預設它永遠一致。

錯法五:用平均數掩蓋嚴重族群失敗

整體 quality score 健康,仍可能有某個語言、文件類型、工具路徑或高風險任務持續失敗。分群要以能行動的低基數欄位進行,並注意不要把使用者個資變成 metric label。這正好呼應第⑨節「為什麼加權平均成一個總分聽起來合理卻是個陷阱」——族群失敗被平均數字掩蓋,跟四個 bucket 被加權平均掩蓋,是同一種數學操作在不同粒度上重演,解法也一樣:拆開來看,不要合併成一個好看的總分。

錯法六:把「例外」用成永久豁免,而不是一次性審查

policy 常見的第二種破口,是把某個依賴(例如某個 LLM provider)一次性寫成「永久排除」,之後再也沒人重新檢視這個排除是否還合理。provider 的可靠性會隨時間改變——它可能在你設定排除的當下確實不穩定,半年後卻已經穩定下來,或反過來;沒有到期日、沒有定期複審的排除規則,很容易變成一個「這條規則存在的原因,已經沒人記得」的化石。比較穩健的寫法是替每一條排除加上到期日與下一次複審的時間,逼團隊定期重新確認這條例外還站得住腳,而不是讓它無限期地把某類事件從預算計算裡隱形。

錯法七:讓 alert fatigue 悄悄把門檻調鬆

前面六種錯法談的都是「一次性」的判斷失誤,第七種是一個會隨時間慢慢累積的過程性問題。假設一套 burn rate 告警規則剛上線的頭幾週,門檻訂得偏敏感,導致值班工程師三不五時就被 ticket 級告警吵到——這時候常見的下一步,是有人乾脆把門檻悄悄調高,讓告警安靜下來。這個決定在當下往往合情合理(畢竟沒人喜歡處理一堆最後證實是誤報的告警),但如果每一次「調鬆門檻」都不留紀錄、不經過團隊確認、也沒有回頭檢視「是不是告警邏輯本身有問題(例如少了低流量守門),而不是門檻真的訂錯了」,這套規則會在幾個月後變得極度遲鈍,卻沒有人記得它是怎麼一步步鬆到這個地步的。

這個過程有一個更隱蔽的變體:不是把門檻數字直接調高,而是把「for」子句的等待時間拉長(見⑧節那份 Prometheus 規則裡的 for: 2m),或是偷偷把低流量守門的 min_requests 調得很高。這些調整不會出現在門檻數字的變更紀錄裡——如果團隊只 review「門檻值有沒有變」,卻沒有 review 整條 alert 規則的完整邏輯,這類「用另一種方式讓告警變遲鈍」的調整反而更容易被放過。這也是為什麼比起單獨盯著某一個數字,更穩妥的做法是把整份 alert 規則(包含 for、min_requests、門檻值)都放進同一份 policy 文件、一起接受複審——見⑯節「production takeaway」提到的、把 policy 當成程式碼一樣版本管理的立場。

Week 1:門檻 burn_rate > 2 → 一週內誤報 12 次,值班工程師抱怨連連
Week 2:有人把門檻悄悄調成 burn_rate > 4,沒有記錄原因,也沒有人 review
Week 6:門檻又被調成 burn_rate > 8,同樣沒有留下決策紀錄
Week 12:一次真實的 quality regression,burn rate 只衝到 5,沒有觸發任何告警
         → 事後追查才發現,門檻已經被悄悄調到當初設計值的 4 倍

這正是為什麼⑯節會強調 error budget policy 要進 git、要有版本管理——不是為了形式上的合規,而是讓「門檻被調整」這件事本身變成一個需要 code review、需要留下理由的動作,而不是值班工程師在半夜為了少收一封告警信而悄悄做的個人決定。比較健康的處理方式,是把「告警太吵」本身當成一個要調查的訊號,先問「是低流量守門機制沒做好,還是門檻設計本身脫離現實」,把根因修掉,而不是直接調鬆數字治標——這個原則跟第⑬節錯法五「用平均數掩蓋嚴重族群失敗」是同一種心態的變形:遇到「數字讓人不舒服」的狀況時,比起調整數字讓自己好受一點,更該做的是先搞清楚數字背後在反映什麼。

⑭ 把今日產物留下來,Day 21 才接得起來

今天的文章沒有要求你執行新的 DIY,也沒有宣稱任何 monitor 已經正常運作。若你要自行實作,完成後應留下以下可審查的產物:

sli-contract.md
error-budget-policy.yaml
dashboard 的短、長窗口截圖或連結
alert annotation 範本
release checklist
一份假資料 incident 演練紀錄

這些東西看起來不像程式碼,卻是可靠性系統的一部分。它們替 Day 21 的 metrics、logs、traces 定下共同目的:budget 快速消耗時,能找出哪裡壞了、先停什麼、由誰處理。

把這些產物之間的關係畫成一張圖,會比條列檔名更容易看出它們為什麼要一起存在:

sli-contract.md ──定義──▶ error-budget-policy.yaml ──設定門檻──▶ alert 規則
      │                          │                                  │
      │                          ▼                                  ▼
      │                   release checklist                  dashboard(短/長窗口)
      │                          │                                  │
      └──────────────共同回答────┴──────────────────────────────────┘
                    「這個系統可靠嗎?快撐不住的時候,
                     誰該知道、該做什麼?」

sli-contract.md 定義「什麼算好、什麼算壞」;error-budget-policy.yaml 把這個定義換算成門檻與行動;alert 規則與 dashboard 是這份 policy 在營運現場的具體呈現;release checklist 則是把同一套邏輯往前搬到部署決策的關卡。少了任何一環,剩下的都會變成沒有依據的孤立文件——沒有 SLI contract 的 policy,門檻是憑空訂的;沒有 policy 的 dashboard,只是好看的圖表;沒有 dashboard 的 policy,沒人知道現在該不該觸發它。Day 21 要接上去的 metrics、logs、traces,本質上就是把這張圖右邊的「dashboard」箱子展開成一整個可觀測系統。

這份清單裡看似最不起眼的一項,其實是「一份假資料 incident 演練紀錄」。跟其他五項比起來,它不是一份「定義」文件,而是一份「使用紀錄」——它證明了前面五項不是寫完就束之高閣的裝飾品,而是真的被人在壓力情境下拿出來用過、走過一次完整的決策流程。⑬節錯法七提到的「告警門檻被悄悄調鬆」之所以會發生,往往正是因為一套 policy 從寫完的那天起,就再也沒有人真的照著它走過一次演練,久而久之連值班的人自己都不確定裡面寫的東西還適不適用。定期重跑一次演練,花的時間通常不長,卻是檢驗「這份 policy 是活的,還是只是掛在牆上」最直接的方法。

⑮ What I Learned:burn rate 不是錯誤率的另一種寫法

寫這份 policy 之前,我以為 burn rate 只是錯誤率換了個名字。實際算過才發現差別:錯誤率回答的是「這個 window 裡壞了多少比例」,burn rate 回答的是「照這個速度,預算還能撐多久」。同樣 1% 的錯誤率,配上 0.5% 的月度預算,換算下來是 2 倍速燒錢——這個數字才是該不該行動的依據,而不是那個 1%。

這個差異一開始不容易感覺到,因為兩者在「事情很糟」的時候會給出相似的結論——錯誤率飆到 20%,burn rate 當然也會爆表,不需要精算就知道要處理。真正拉開差距的是中間地帶:錯誤率 0.3%,聽起來平淡無奇,甚至可能比上週還低;但如果 SLO 只給 0.1% 的預算,這其實是 3 倍速燒錢,一個月的預算撐不到十天。只看錯誤率的人會覺得「還好」,看 burn rate 的人會知道「這個月的預算要提前用完了,得決定現在放慢腳步還是等真的撞牆」。這正是①提到 GPT-4 那個故事的鏡像版本——那裡的問題是指標選錯(可用性正常但語意品質下降),這裡的問題是就算指標選對了,沒有換算成「相對預算的消耗速度」,數字本身還是不會說話。

另一個認知調整是:error budget policy 管理的不是「這次要不要修」,而是「故障之後能否做出一致決定」。沒有 policy 的時候,每次事故的處理方式都取決於當天誰在線上、心情如何、老闆盯得多緊——同樣消耗 25% 預算的事故,這次可能被輕輕放過,下次卻被要求全員加班。Google 的 20% 單一事件規則(⑨已用手機遊戲案例示範過)先劃出一條線,讓 postmortem 跟停止 rollout 這些決定不必每次重新辯論;規則本身可能不完美,但「有一致規則」比「每次重新吵一次」更重要,這點跟法律體系裡「惡法亦法優於無法」的邏輯是類似的——當然,跟法律不同的是,error budget policy 是團隊自己寫的,覺得不合理隨時可以在下次複審改掉,不需要走冗長的修法程序。

寫這篇之前我也低估了「burn rate 需要兩個以上窗口才算完整」這件事。單看 1 小時窗口的高 burn rate,區分不出這是真正的大範圍故障,還是某個使用者剛好在這個小時內連續重試了十次;單看 6 小時窗口,又會錯過快速惡化的問題,眼睜睜看著預算被燒光才反應過來。⑧的三級門檻設計,本質上就是承認「burn rate 只是一個數字,單靠它做判斷還不夠,需要跟另一個時間尺度的同一個數字互相印證」——這跟 Trace 需要跨服務對照、Log 需要跨時間對照,是同一種「單一訊號不可信、要交叉驗證」的工程直覺。

⑯ Production Takeaway

若這要上線成 production system,第一件事會是把 availability、quality、safety 分成獨立的 budget,因為它們的容錯與升級對象不同——這不是理論潔癖,而是③已經示範過的教訓:混在一起算,quality 下滑可以被 availability 的高分掩蓋,等使用者用腳投票才發現問題。三個 budget 應該各自有自己的 SLI 定義、自己的門檻、自己的 owner,甚至可以放在不同的 dashboard 面板,避免視覺上被平均成一條看似健康的線。

第二件事是 burn rate 告警至少看兩個 window(例如 1 小時與 6 小時),降低單一雜訊造成無效 page 的機率。這點⑧已經用 Google 真實的三級門檻示範過,這裡想強調的是「至少」兩個字——兩個窗口是最低限度,不是理想值。如果團隊發現半夜常被 6 小時窗口的殘留 burn rate 吵醒(故障已經修好,但過去幾小時的壞事件還沒被平均掉),加一個更短的窗口(例如 5 分鐘)做「已恢復」判斷,是合理的疊代,不是過度工程——這正好呼應⑬錯法六提到的、policy 需要隨著實際運作被修正的立場。

第三件事,也是最容易被忽略的一件:「預算剩多少時該做什麼」要寫成團隊同意、可定期複審的文件,不是寫在某個資深工程師的腦子裡。error budget policy 會過時——新功能上線改變流量模式、SLO 目標值隨業務成熟度調整、team 邊界重組導致 owner 換人——這些都會讓舊 policy 的門檻失準。它和程式碼一樣需要版本管理、需要 code review、需要在明顯不合時宜時被重寫,而不是被當成一次寫定、永遠正確的神聖文件。⑭列的那份 error-budget-policy.yaml 進 git 的意義正在這裡:它的變更歷史本身就是一份「我們對可靠性認知演進」的記錄。

⑰ 本文結論

SLO 沒有行動政策,只是另一張 KPI 圖,貼在牆上好看,出事時沒人知道該看哪一格。burn rate 告訴你花錢的速度,policy 告訴你錢快花完時該怎麼辦;兩者缺一個,SLO 都只是牆上的數字——有 burn rate 沒 policy,你知道在燒錢卻不知道該不該停手;有 policy 沒 burn rate,你有一套規則卻沒有觸發它的即時訊號。這兩者合起來,才是①開頭那句「SLO 是一份還沒被讀完的合約」的下半段——burn rate 讓你隨時知道合約履行到哪一條,policy 讓你知道違約時該找誰、該怎麼辦。下一個 phase 把 metrics、logs、traces 排成能從警報查到根因的觀測系統,讓「budget 快撐不住了」這個結論,能一路往下追到「是哪個服務、哪一段程式碼、哪一次 deploy 造成的」。

Day 21 預告

下一篇會接著處理 Metrics、Logs、Traces 這三種訊號各自回答什麼問題,以及怎麼在同一個 FastAPI 服務裡把它們同時建起來。

延伸閱讀


這篇是 Learning SRE for the AI Era 系列的一部分。

我會從 SRE 的服務可靠性基礎開始,逐步探索當系統加入 LLM、RAG、Agent 與 GPU Infrastructure 後,如何讓 AI 系統不只可用,也能被觀測、評估、控制成本並安全演進。

Build → Trace → Break → Measure → Evaluate → Recover → Improve.


上一篇
Day 20(上)|Error Budget 與 Burn Rate:預算用完要真的改變行為
下一篇
Day 21(上)|Metrics、Logs、Traces:同一件事,三種問題
系列文
Learning SRE for the AI Era:從 SRE Lab 到 Production AI Reliability 共 44 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言