假設一批退款有九十筆在三十秒內完成,另外十筆卡了二十分鐘。儀表板只在退款完成時記錄耗時,於是已完成樣本的 p95 仍然很好看。那十位顧客沒有拿到結果,也沒有貢獻一個「很慢」的完成樣本。
今天要決定告警依據:從資料庫仍未解決的業務操作計算等待年齡,另外監看這份觀測是否新鮮。 完成耗時保留作效能分析,但不單獨承擔發現卡單的責任。
商店、數字與時限都是教學假設,沒有部署退款服務、Prometheus 或執行壓測。這篇延續共用重試額度中「受理十五分鐘仍未有終局就轉人工」的規則,補上如何及早發現需要接手的案件。即使還沒做過前面的練習,也可以先從一筆未決退款看起。
先說清楚,這十五分鐘在等什麼
本例的受理時間,是退款操作與持久待辦在同一筆交易提交成功的時間。每次重試、換 worker 或重新排程,都沿用同一個業務操作識別值與原受理時間。若拿每次嘗試的開始時間重新計時,十分鐘前就卡住的退款會看起來只有三秒大。
「解決」則表示供應商已有可核實的終局結果,而且本地已保存對應紀錄。成功與明確失敗分開記錄;失敗不是退款成功,仍要依業務規則通知與處理。供應商僅回受理、連線逾時或結果不明,都還在未決集合裡。轉到人工清單也沒有解決退款,不能順手把它從等待統計刪掉。
這個終點不是顧客銀行帳戶已入帳,也不是 HTTP 202。如果產品還需要監控實際入帳,必須另訂可以取得證據的流程與時效,不能把本地十五分鐘承諾套在銀行處理時間上。
Prometheus 的 histogram 統計的是送入的觀測值。何時呼叫觀測、是否包含失敗,由應用的埋點決定。因此「完成時才記錄」所造成的盲點,是這個案例的埋點設計推論,不是 histogram 計算錯誤。Histograms and summaries
最小方案先讀主庫,不再加一條事件管線
系統維持模組化單體、MySQL 與少量退款 worker。加一個與退款派送分開執行的唯讀觀測程序,每三十秒查一次未決退款。它不領取工作、不改狀態,也不呼叫金流;派送器卡住時,觀測仍有機會發現問題。
一次成功查詢要產生同一份快照,至少包含以下資料:
- 未決案件總數,以及其中已轉人工的數量。
- 最早的原始受理時間,沒有案件時明確表示空集合。
- 超過十五分鐘仍未決的筆數。
- 這次成功取得完整快照的時間。
觀測範圍包含等待執行、執行中、等待重試、結果待確認與人工核對,不能直接拿佇列長度代替。有些 worker 已領走的案件不在佇列內,重試也可能只是同一案件的另一項任務。計數以業務操作為單位,避免同筆退款被算三次。
重試、接手與轉人工,不重設原受理時間。
1 · 持久未決紀錄
待執行、在途、重試、結果不明及人工核對。每筆業務操作只算一次。
2 · 唯讀完整快照
每 30 秒取得筆數、最老受理時間與超時筆數。只有完整成功才更新快照時間。
3 · 分開告警
等待達 10 分鐘,查看退款;快照超過 90 秒,修復觀測。沒有新資料不等於沒有案件。
最早受理時間與最後成功快照時間匯出為 Unix timestamp,單位是秒,由監控端用目前時間減去它們來計算年齡;空集合另用未決筆數判斷,不拿零時間戳製造巨大等待值。這樣即使出口只回舊快照,時間仍繼續增加,不會凍結在最後一次計算出的年齡。Prometheus 的時間戳建議也採這種做法。
這些值是快照,使用 gauge。Prometheus 官方也建議離線處理同時觀察進入、處理中與離開的工作,並追蹤處理進度;每組 label 都有儲存與計算成本。因此彙總指標只用有限的供應商、環境等維度,不放訂單 ID、顧客 ID 或退款 ID。告警連到受權限保護的案件清單,再依操作識別值查日誌。Instrumentation
讀主庫是這個小型系統的取捨,避免把副本延遲混入案件等待。查詢採短的唯讀一致快照,以同一個觀測時刻計算年齡,並設執行時限。實作必須檢查查詢計畫與索引,不能假設掃全表很便宜。假設未決集合上限一萬筆、每三十秒檢查一次,每分鐘兩輪,若每輪完整掃一萬筆,就是每分鐘兩萬次候選列檢查;這不等於已量到的 I/O、CPU 或查詢時間。
先針對實際的未決條件與受理時間安排索引,量正常、積壓與大量歷史已結案資料三種分布。如果資料查詢超時,就回報觀測失敗,不發布一組零筆的假正常結果。
十五分鐘的期限,要扣掉看見問題的時間
假設值班人員至少需要三分鐘確認案件並接手,而觀測與告警的設計預算如下:每三十秒取快照、每三十秒抓取指標、每三十秒評估規則,通知傳送另預留三十秒。保守相加是兩分鐘。因此若希望不晚於十五分鐘開始處理,最老未決案件達十分鐘就要提出需要立即查看的通知。10 分鐘 + 2 分鐘偵測/傳送 + 3 分鐘接手 = 15 分鐘。
這是待驗證的時間預算,實際排程延遲、查詢時間與通知壅塞都可能使它超支。三分鐘預留給開始接手,不保證能在這段時間解決退款。到十五分鐘仍未決時,必須進入人工責任清單,不等下一張圖表變紅才決定誰處理。
如果再設定持續五分鐘才觸發告警,通知就可能在期限之後才到。Prometheus 的 for 會要求條件在評估期間持續成立後才進入 firing,這段等待要算進預算。Alerting rules
第一版用「最老未決案件達十分鐘」觸發同一供應商的一組告警,訊息附未決筆數、超時筆數與觀測時間。不要每筆退款各寄一封。接手者先查供應商是否已完成、本地是否漏記,再查看外呼額度與最老案件是否被排程餓死;告警本身不授權換鍵重送。對已有人工負責人的長案,保留未決統計與後續處理時限,但用事件管理減少重複打擾。
這種通知針對需要人採取動作的業務症狀。CPU 高低與 worker 數量可以幫忙診斷,卻不是退款是否仍欠著的答案。Google SRE 也區分需要中斷人的告警與一般診斷資訊,避免只有「看起來奇怪」就叫醒人。Monitoring Distributed Systems
零筆未決,必須有新鮮證據
觀測程序本身停了,最後一筆快照可能停在「零筆」。如果出口還活著、只是不斷供應舊資料,單看 HTTP 抓取成功也抓不到這個故障。
因此首版另設一條獨立規則。距離最後成功快照超過九十秒,告警為「退款觀測失效」。九十秒也是教學設定,約容許三個三十秒週期,不是通用標準。快照時間不可在每次讀取舊快取時更新;只有完整查詢成功才更新。部署初期還沒有任何成功快照、指標消失、出口不可達,都要能進入失效狀態,不能以預設零值當作健康。
需要比較時間時,資料庫、觀測器與監控系統的時鐘要同步並檢查偏差;時間突然倒退或產生負年齡要報異常,不靜默歸零。未決告警與觀測失效告警分開,避免用「沒有新資料」解除一個尚未證明恢復的退款問題。
這也界定了本方案的能力。如果資料庫把整筆已受理操作弄丟,唯讀觀測器無法憑空找回它。前一篇還原後重新接單討論的外部核對仍然需要,健康的等待年齡不能代替完整性證據。
什麼情況值得換另一個方案?
只看完成耗時,成本低,也適合回答「已完成操作變慢多少」。它看不到未完成案件,不能單獨守住十五分鐘接手期限。端到端合成探測則能檢查一條代表路徑,卻可能剛好避開某個供應商或某筆卡單;測試金流還必須隔離真實金錢副作用。
另一種選擇是由受理、狀態改變與解決事件維護觀測投影。它可以減少主庫週期掃描,但多了事件漏送、重複、亂序、投影落後與重建的成本。只有在量到觀測查詢已影響主庫、無法在預算內完成,或確實需要跨系統整合時,才值得升級。投影仍要保存最初受理時間與最後處理進度,並定期與權威紀錄核對;多一套訊息系統不會自動讓告警更可信。
首版先用有執行時限的唯讀快照,把成本和缺口量出來。不要為了降低告警數量而重設受理時間或漏掉人工案件。
驗收要故意留下一筆永遠不完成的退款
這份驗證計畫尚未執行退款或監控整合測試。使用可控制的供應商替身,不碰真實款項:
- 放入九十筆快速解決、十筆永遠未決的退款。即使已完成耗時良好,最老年齡仍持續增加,十分鐘時進入告警條件。
- 對同一筆退款重試、換 worker、轉人工。總未決筆數不能因重試增加,原受理時間不得改變,轉人工也不離開未決集合。
- 讓供應商成功,但在本地保存前中止程序。案件仍待確認;收到重複成功通知並完成本地紀錄後,才從未決集合移除。
- 保持指標出口存活,但停止成功快照。超過九十秒要出現觀測失效;空集合的新快照與陳舊的零值必須得到不同結果。
- 量從十分鐘條件成立、規則評估到通知抵達的時間。再模擬查詢超時、抓取遺失與時鐘偏差,確認不會假裝恢復。
最後拿一筆真正的未決紀錄,請接手者從告警找到對應案件與原操作,不重新退款。如果只能看見一條紅線,卻找不到要處理的紀錄,這份告警還沒有驗收完成。
練習
假設一百筆受理退款中,九十九筆三十秒內完成,一筆已等十八分鐘。完成耗時 p99 顯示三十秒,觀測器最後一次成功快照卻是五分鐘前。你會先判定服務健康、退款卡住,還是觀測失效?要取得哪些證據才能解除告警?
參考思路
已知那筆十八分鐘未決案件需要核對,陳舊快照又表示目前完整範圍不可信,兩個問題可以同時成立。先恢復可靠觀測並核對該筆原受理時間、供應商結果與本地紀錄。只有新的完整快照和已確認的處理結果,才能分別解除觀測失效與案件告警;九十九筆快速完成不能替最後一筆作證。
