佇列變短,可能是工作完成,也可能是訊息正在處理、移入死信或已過期。 要判斷非同步流程是否履行承諾,必須把傳遞進度與業務結果一起觀察。
本文承接重播、投影與歷史資料。沿用匯出服務,先訂出可觀察的完成條件,再選指標與故障歷程;不把監控畫面上的綠燈當成正確性的證明。
查證日期:2026-11-04。依當日 AWS 與 OpenTelemetry 官方文件及 Kafka 4.1 文件整理。數值、告警條件與下列故障歷程均為自創概念推演,未部署 broker、執行負載或故障注入。
先為三種進度建立不同證據
對 job-42,產品承諾是受理後可查詢成功結果或明確的終止原因。搜尋與通知則各有自己的下游契約。要診斷「使用者沒有看到匯出」,至少分開看三層:
| 層次 | 本例紀錄 | 能回答的問題 |
|---|---|---|
| 受理與待發 | 工作建立時間、Outbox 未發布筆數及最早等待時間 | 工作是否保存?是否卡在交給 broker 之前? |
| 傳遞與消費 | 可讀積壓、處理中、重試與死信、各訂閱進度 | 哪一段還沒前進?是否只在反覆接收同一事件? |
| 業務完成 | 工作終態、結果識別、各下游處理結果與未知狀態 | 檔案是否可用?搜尋是否可查?通知是否仍需核對? |
如果 Outbox 卡住,broker 可能完全沒有積壓;如果 worker 收到後就過早刪除訊息,queue 也可能很乾淨。兩種情況都不能從「queue 為零」推出所有已受理工作成功。
每層都需要自己的時間與識別。受理到成功的端到端延遲(end-to-end latency)由業務受理與終態紀錄量測;broker 內等待年齡只涵蓋其中一段。仍未完成的工作不能從成功延遲統計中無聲消失,應另外統計未終止數量、等待年齡與超過承諾期限的比例。
訊息數、年齡與完成率要一起看
積壓(backlog)表示尚待處理的工作;不同系統的指標可能只涵蓋「可見」或「尚未提交」的部分,使用前必須確認定義。
SQS 的多項 CloudWatch 數值是近似值。ApproximateNumberOfMessagesVisible、ApproximateNumberOfMessagesNotVisible 分別提供可接收與處理中訊息的線索;單看前者下降,不能判斷後者是否卡住。接收與刪除次數也可能包含重複,不能直接當成唯一業務完成數。參考 SQS:Available CloudWatch metrics。
同頁也說明 standard queue 的反覆失敗訊息會影響最老訊息年齡的呈現;因此年齡改善不必然表示壞訊息已修好。仍應獨立觀察死信,並保留應用的原始受理時間。不能以 DLQ 的指標年齡取代整項工作的等待時間。
Kafka 的落後量(lag)要說明比較的是哪兩個位置。例如本例以「每個 partition 可讀尾端位置與該 group 已提交位置的差」作操作線索;它不是通用的秒數,也不自動代表業務未完成筆數。錯誤地提早提交會讓這個差看似健康,外部效果卻仍遺漏;同一段位置差,處理成本也可能不同。位置與效果的界線見 Kafka 4.1:Design,Consumer position、Message Delivery Semantics。
官方監控名稱也不能直接套用上面的自訂定義。Kafka 4.1 的 records-lag-max 依目前讀取位置(current offset)而不是已提交位置計算;它可能在 worker pool 尚未完成效果時下降。選指標時先記下量測端點,再對照應用完成紀錄。參考 Kafka 4.1:Monitoring,consumer-fetch-manager-metrics。
用明列假設估算能否排空
假設系統已有 600 個待處理工作,新工作每秒增加 8 個,worker 每秒能完成 10 個。所有工作成本相同,沒有重試、額外流入或容量變動,淨排空速度是每秒 2 個,約需 600 ÷ (10 − 8) = 300 秒。
若故障恢復後只是回到每秒完成 8 個,舊積壓就不會下降;若重試又增加流量,情況可能更差。這只是自訂算例,不是實測吞吐,也不能直接用平均值承諾每位使用者的最長等待。
AWS 的積壓處理經驗強調背壓、工作優先順序與恢復策略;queue 可以延長恢復時間,不能只依平常吞吐規劃。參考 AWS:Avoiding insurmountable queue backlogs。擴大 consumer 之前仍要檢查資料庫、檔案儲存與外部通知能否承受,避免把積壓移到下一層。
用識別串起歷程,不把追蹤當成去重
本例的紀錄至少關聯意圖 export-intent-7、工作 job-42、命令 cmd-42、事件的 source + id、訂閱/consumer 名稱、處理嘗試及結果。每次投遞另有自己的追蹤資訊,不能讓新的 trace ID 取代既有事件身分。
OpenTelemetry 的 context propagation 用來跨程序傳遞追蹤關聯,讓不同服務的活動可被串連。它不提供業務交易、去重或授權保證;缺少 span 也可能來自取樣、未插樁或資料遺失,不能直接推論工作沒有執行。參考 OpenTelemetry:Context propagation。
以 job_id 查詢個案適合放在紀錄或追蹤中;長期指標標籤則應控制基數,按服務、訂閱與錯誤類別彙總。本文不要求把每個工作與事件 ID 都變成一組獨立時間序列。
先寫判定規則,再設計故障歷程
以下是待實作系統可採用的驗證規格,並非本次已執行的測試。每個案例都要記錄故障點、持久狀態、重送身分、恢復動作與最後的可觀察結果。
| 概念故障歷程 | 觀察與應符合的條件 | 可發現的錯誤 |
|---|---|---|
broker 接受 evt-90,relay 尚未保存發布進度便停止 | 恢復後可能重送同事件;適用本地交易的 consumer 效果不再累加 | 每次重送改新事件 ID;Inbox 與效果分開提交 |
| worker A 暫停到可見性期限之後,B 接手,再讓 A 恢復 | 本例受控結果更新應拒絕舊處理世代;檢查真正的結果紀錄 | 只用 timeout 推論 A 已停止;舊 worker 覆蓋新結果 |
| 某事件格式不受支援,重試後進入隔離 | 保留原事件與錯誤;有負責人修復,正常訂閱可繼續 | 無限立即重試;移入 DLQ 後直接視為業務成功 |
| 搜尋 consumer 暫停,通知仍正常 | 各自進度分開;搜尋恢復後在保存範圍內追上 | 不同責任誤放同一競爭訂閱;共用進度 |
| 新投影重建時仍有即時事件與資料刪除 | 切換後沒有狀態倒退,也沒有復活已刪資料 | 只檢查歷史批次完成,未涵蓋追趕與刪除 |
「最後完成了」只能檢查進展,還要查不該發生的結果:同意圖出現兩個工作、已完成狀態倒退、跨租戶讀取或重寄歷史通知。外部服務若無法查明是否已執行,正確結果可能是保留 unknown 並進入核對,而非硬填成功或失敗。
告警必須有處置對象
可以分開設計「Outbox 等待過久」「某訂閱年齡上升」「死信出現」「工作超過終態期限」「外部效果未知未核對」等告警。門檻來自產品時間承諾與量測,本文不替所有系統設定固定秒數。
每則告警應指出負責人、查詢入口、是否限制新流入、如何安全重試及何時停止。處置後應回查工作與副作用;把 queue 清空、刪掉死信或重設 offset,都不能自行證明需求已滿足。
下一篇把上述證據與前文機制組成匯出完成後的多下游處理,逐步說明哪些保證成立、哪些仍需核對。
相關文件
- 訊息佇列與事件驅動架構知識索引:完整主線。
- 過載、時間預算與背壓:流入、併發與有限等待。
- 發布確認與消費確認:不同進度訊號的邊界。