n8n 工作流重試時發生重複處理

嗨
我有一個 n8n 工作流程,它接收 webhook 事件,然後在 PostgreSQL 中建立記錄並呼叫外部 API。
問題是,外部 API 有時會在實際處理請求後超時。n8n 將超時視為失敗並重試執行,這可能導致同一操作被處理兩次。
簡化的流程是:
Webhook
↓
驗證資料
↓
PostgreSQL
↓
外部 API
↓
更新狀態
我考慮使用基於事件 ID 的冪等金鑰,並在 PostgreSQL 中儲存處理狀態。
例如:INSERT INTO processed_events (event_id, status)
VALUES ($1, ‘processing’)
ON CONFLICT (event_id) DO NOTHING;
如果 API 成功但 n8n 在更新資料庫之前當機,你會如何處理這種情況?
你會在重試前檢查外部 API 是否存在現有操作嗎?
是否有更好的 n8n 模式來安全地使此工作流程可重試?

描述問題/錯誤/問題

錯誤訊息是什麼(如有)?

請分享你的工作流程

(在畫布上選擇節點,使用鍵盤快捷鍵 CMD+C/CTRL+C 和 CMD+V/CTRL+V 複製並貼上工作流程。)

分享最後一個節點傳回的輸出

有關你的 n8n 設定的資訊

  • n8n 版本:
  • 資料庫(預設值:SQLite):
  • n8n EXECUTIONS_PROCESS 設定(預設值:own、main):
  • 透過以下方式執行 n8n(Docker、npm、n8n cloud、桌面應用程式):
  • 作業系統:

嘿 @Decoure_Ryan 我會使用由 PostgreSQL 唯一約束支持的冪等性密鑰,但我不會只依賴資料庫狀態。

重要的部分是相同的 event_id 始終產生相同的操作密鑰:event-123-payment

然後工作流可以遵循類似以下的步驟

Webhook
↓
Create/Check Idempotency Key
↓
PostgreSQL
↓
Check External API Status
↓
Process Only If Needed
↓
Update Status


如果 n8n 在 API 成功但在 PostgreSQL 更新之前崩潰,重試會看到現有的 event_id 並使用該冪等性密鑰檢查外部 API。

如果 API 確認已經成功,n8n 可以簡單地更新 PostgreSQL 並繼續,而不是建立另一個操作。

我也會讓 PostgreSQL 約束強制唯一性:CREATE UNIQUE INDEX idx_processed_events_event_id
ON processed_events(event_id);

@Niffzy 有正確的方向,而確定性的鍵是重要的部分。我還要補充兩點,因為該流程中仍然存在競態條件,其中之一決定了你是否根本需要狀態檢查。

檢查-然後-動作競態條件

在這個序列中:

檢查外部 API 狀態 → 只有在需要時才處理

如果兩個重試在彼此接近的時間內到達,兩個都可以在任一個處理之前通過狀態檢查,然後兩個都會處理。唯一索引會阻止第二行,但不會阻止第二個 API 呼叫。在超時重試中,兩個嘗試通常相隔只有幾秒,這正好是時間窗口。

關閉它的方法是以原子方式聲明工作,使用租約,這樣崩潰無法永久佔用該事件:

INSERT INTO processed_events (event_id, status, claimed_at, attempts)
VALUES ($1, 'processing', now(), 1)
ON CONFLICT (event_id) DO UPDATE
   SET claimed_at = now(),
       attempts   = processed_events.attempts + 1
 WHERE processed_events.status = 'processing'
   AND processed_events.claimed_at < now() - interval '5 minutes'
RETURNING *;

返回一行意味著你擁有這項工作。沒有返回意味著它要麼已經完成,要麼有人在積極處理它,然後你停止。這是一個陳述式,所以它是原子性的,兩個並發重試不能都贏得聲明。

租約也修復了你原始版本中的一些東西。普通的 ON CONFLICT DO NOTHING 會將行標記為「處理中」並永不釋放它,因此你提到的確切崩潰會導致該行永遠停留在「處理中」。重試會遇到衝突,什麼都不做,事件被默默丟棄。這通常比重複更糟,因為沒有任何東西會提醒你。將間隔設定為舒適地長於你最壞的現實 API 延遲。

你可能根本不需要狀態檢查

如果外部 API 接受 Idempotency-Key 標頭,請發送你的確定性鍵並完全跳過狀態檢查。他們那邊進行去重,並把原始結果交給你,這在他們那邊以原子方式進行,是你這邊的檢查無法達到的方式,在成功路徑上花費零成本,而不是在每個事件上的額外往返。在構建額外步驟之前值得閱讀他們的文件。保持狀態檢查作為不支持它的 API 的備用方案。

n8n 一側的兩件事

關閉 API 節點上的 retryOnFail。節點級重試在同一執行內重新執行,並將樂意繞過你已經包裝在節點周圍的邏輯。

還值得確認你實際看到的是哪種重試,因為兩者在數據中看起來相同,但有不同的修復。打開你的執行列表。如果重複是幾秒間隔的單獨執行,具有相同的有效負載,那是調用者在重試,修復是讓 Webhook 節點立即回應並在之後執行慢速工作,這樣超時就無法再啟動第二次運行。如果它是一次執行寫入兩次,那是節點級重試。值得先排除,因為如果事實證明是調用者在重試,那麼所有狀態機制都在治療症狀。

最後一件事

即使有上述所有內容,至少一次傳遞加上崩潰也意味著某些行將在飛行中途停滯。一個小的計劃工作流程可以拾起仍然「處理中」超過租約期限的任何東西,並將其與 API 協調。沒有它,那些是你永遠聽不到的。