佇列工作處理程序可觀測性
佇列深度表示有多少工作正在等待。佇列等待說明了每個作業的積壓成本。執行持續時間表示工作人員執行的時間。最終結果和重試次數表明工作最終是否成功。
您需要所有四個訊號來區分健康的流量突發和落後的佇列、緩慢的依賴項、重試風暴或消失的工作執行緒。
尾部百分位數揭示了平均水平可以消除的緩慢工作。
先決條件
- Telemetry API 鑰匙
- 公開佇列、開始和最終結果邊界的工作人員
- 穩定的邏輯作業識別符號和低基數作業名稱
- 每個關鍵作業型別的預期完成視窗
分別對快照和結果進行建模
使用兩種事件形狀,因為它們回答不同的問題:
| 活動 | 穀物 | 使用 |
|---|---|---|
queue_snapshot |
一個取樣時間一個佇列 | 當前深度、可用工人、最長等待年齡 |
background_job_completed |
每個邏輯作業有一個最終結果 | 等待、執行持續時間、重試、成功、永久失敗 |
快照是取樣狀態,因此不要隨時間求和其佇列深度。最終結果是已完成的工作,因此它無法找到已開始並消失的工作。當需要檢測停滯作業時,新增輕量級 job_started 和 job_finished 生命週期事件。
定義終端事件契約
使用 後台作業完成架構 作為起點:
| 領域 | 含義 |
|---|---|
event_id |
用於重複資料刪除的唯一終端事件 |
job_id |
跨嘗試共享穩定的邏輯作業識別符號 |
job_name |
有界作業型別,絕不是動態負載或 ID |
queue_name |
執行作業的佇列 |
account_id |
受結果影響的假名帳戶 |
attempt_count |
包括終端嘗試在內的總嘗試次數 |
queue_wait_ms |
排隊到第一次執行 |
duration_ms |
終端嘗試執行持續時間 |
status |
success 或永久 error |
release |
Worker 或應用程式版本 |
將作業參數、憑據、電子郵件地址、文件內容和原始異常文字排除在事件之外。如果操作員需要故障類別,請使用有界 error_type。
儀器結果邊界
安裝並初始化JavaScript SDK:
npm install telemetry-sh
import telemetry from "telemetry-sh";
telemetry.init("YOUR_API_KEY");
使用相同的時鐘測量排隊、首次啟動和終端完成。成功後發出一行終端行或重試耗盡:
async function processJob(job) {
const startedAtMs = Date.now();
let terminalAttemptStartedAtMs = startedAtMs;
let attemptCount = 0;
try {
const result = await runWithRetry(async () => {
attemptCount += 1;
terminalAttemptStartedAtMs = Date.now();
return performJob(job);
});
await telemetry.log("background_job_completed", {
event_id: crypto.randomUUID(),
job_id: job.id,
job_name: job.name,
queue_name: job.queue,
account_id: job.accountId,
attempt_count: attemptCount,
queue_wait_ms: startedAtMs - job.enqueuedAtMs,
duration_ms: Date.now() - terminalAttemptStartedAtMs,
total_elapsed_ms: Date.now() - startedAtMs,
status: "success",
release: process.env.APP_RELEASE ?? "unknown"
});
return result;
} catch (error) {
await telemetry.log("background_job_completed", {
event_id: crypto.randomUUID(),
job_id: job.id,
job_name: job.name,
queue_name: job.queue,
account_id: job.accountId,
attempt_count: attemptCount,
queue_wait_ms: startedAtMs - job.enqueuedAtMs,
duration_ms: Date.now() - terminalAttemptStartedAtMs,
total_elapsed_ms: Date.now() - startedAtMs,
status: "error",
error_type: classifyJobError(error),
release: process.env.APP_RELEASE ?? "unknown"
});
throw error;
}
}
此範例將終端嘗試保留在 duration_ms 中,並將完整重試策略掛起時間保留在 total_elapsed_ms 中。如果您的重試庫以不同的方式報告這些邊界,請調整計時器,同時保留兩個記錄的含義。
遙測呼叫應遵循作業的持久狀態更改。決定您的應用程式如何處理遙測傳輸失敗,而不會將已經成功的作業變成其業務副作用的重試。當重新傳送相同的終端事件時,在遙測傳送重試期間保留 event_id。
佇列狀態範例
按固定時間間隔從佇列的權威狀態收集快照:
async function recordQueueSnapshot(queue) {
const state = await queue.inspect();
await telemetry.log("queue_snapshot", {
event_id: crypto.randomUUID(),
queue_name: queue.name,
depth: state.waitingCount,
active_workers: state.activeWorkers,
oldest_wait_ms: state.oldestEnqueuedAtMs
? Date.now() - state.oldestEnqueuedAtMs
: 0,
release: process.env.APP_RELEASE ?? "unknown"
});
}
保持間隔足夠頻繁,以檢測有意義的積壓,但不要太頻繁,以免相同的樣本主導事件量。將零深度記錄為零;不要省略它。
將佇列等待與執行時分開
此查詢將典型等待和尾部等待與尾部執行持續時間進行比較:
SELECT
job_name,
COUNT(*) AS jobs,
approx_percentile_cont(queue_wait_ms, 0.50) AS p50_wait_ms,
approx_percentile_cont(queue_wait_ms, 0.95) AS p95_wait_ms,
approx_percentile_cont(duration_ms, 0.95) AS p95_run_ms
FROM background_job_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY job_name
HAVING COUNT(*) >= 20
ORDER BY p95_wait_ms DESC;
正常執行時間下的高 p95 等待指向容量、排程、優先順序或突發處理。執行時間長,正常等待點指向作業程式碼或依賴項。兩者同時上升可能意味著緩慢的工作正在消耗工人的能力並造成積壓。
測量重試和終端故障
由於終止事件記錄了一個邏輯作業,因此 attempt_count > 1 表示該作業在至少一次重試後恢復:
SELECT
job_name,
COUNT(*) AS jobs,
SUM(CASE WHEN attempt_count > 1 THEN 1 ELSE 0 END) AS retried_jobs,
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS permanent_failures,
100.0 * SUM(CASE WHEN attempt_count > 1 THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0) AS retried_job_rate_pct,
100.0 * SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0) AS permanent_failure_rate_pct
FROM background_job_completed
WHERE timestamp_utc >= now() - INTERVAL '7 days'
GROUP BY job_name
ORDER BY permanent_failure_rate_pct DESC, retried_job_rate_pct DESC;
如果您每次嘗試發出一行,請使用 attempt 欄位和穩定的 job_id;分母和解釋會有所不同。在每個查詢旁邊記錄行粒度,這樣重試就不會意外地被計為單獨的客戶作業。
檢測停滯和丟失的工作
僅終端資料表無法區分長時間執行的作業和工作執行緒崩潰後丟失的作業。發出 job_started 加 job_completed 或 job_failed 與相同的 job_id,然後使用左連線查詢沒有終止事件的開始。
該閾值必須是特定於工作的或源自預期完成視窗。長時間執行的作業可能需要心跳事件。為事件傳遞延遲新增一個較短的寬限期,以便最新行不會產生誤報。
使用完整的 停滯的後台作業查詢 而不是將佇列深度視為特定作業被卡住的證據。
考慮死信、優先事項和關閉
當重試策略用盡且作業進入死信佇列時,記錄不同的終端類別或事件。軌跡回放作為新的操作動作與原job_id聯動;不要默默地改寫原來的失敗。
當工作人員不可互換時,按佇列或優先順序對容量訊號進行分段。健康的批次佇列可以隱藏總體平均值中阻塞的關鍵佇列。
在部署和關閉期間:
- 在解僱工人之前停止接受新工作;
- 允許記錄的換油間隔;
- 區分故意重新排隊和執行失敗;
- 保留邏輯
job_id並遞增嘗試狀態; - 驗證每個啟動的作業最終都會產生一個終止事件。
建置儀表板
後台作業儀表板範例 提供 SQL、合成結果和解釋。生產環境插樁板應包括:
- 當前佇列深度和佇列中最早的等待年齡;
- p50 和 p95 按作業名稱排隊等待;
- p95 按作業名稱執行持續時間;
- 重試作業率和永久失敗率;
- 當前停滯的作業具有安全相關識別符號;
- 按版本劃分的數量,以便可以將部署與更改進行比較。
使用完整的時間段來了解趨勢。在對費率進行排名之前設定最小交易量規則。僅當工作流程至關重要時,安靜佇列中的單個失敗作業才具有操作重要性;否則,僅按百分比計算,它的排名不應超過大容量回歸。
對回應發出警示,而不僅僅是閾值
將每個警示與所有者和操作聯絡起來:
| 條件 | 可能的問題 | 第一反應 |
|---|---|---|
| 深度和最久等待時間上升 | 需求是否超過容量? | 檢查到達率、工人、優先順序和依賴性健康狀況 |
| 執行時間增加而等待穩定 | 作業程式碼或依賴項是否會減慢速度? | 比較版本和錯誤類別 |
| 重試率上升 | 瞬態故障放大有用嗎? | 檢查有界錯誤型別和提供者健康狀況 |
| 永久性故障增加 | 恢復力竭了嗎? | 識別受影響的帳戶和死信狀態 |
| Start沒有終止事件 | 是否有工人墜毀或儀器失蹤? | 檢查worker、心跳和作業狀態 |
避免對一個不完整的儲存桶進行分頁。需要持續的違規或嚴重的終端故障,並使閾值與工作流程的預期完成時間保持一致。
生產前驗證
執行一個裝置:
- 第一次嘗試成功;
- 重試成功;
- 永久性故障和死信條目;
- 重複事件交付;
job_started後工人崩潰;- 有心跳的長時間執行的工作;
- 正常耗盡的佇列突發;
- 部署關閉並重新排隊。
確認邏輯作業計數、嘗試計數、佇列等待、執行持續時間、丟失的終端事件和受影響的帳戶計數。還要驗證遙測故障是否無法重播非冪等業務操作。