處理順序錯亂的 webhook 事件

我正在構建一個 n8n 工作流,它從外部服務接收 webhook 事件。相同的記錄可能會產生多個事件,但由於重試或網路延遲,有時它們會以錯誤的順序到達。

範例:訂單已更新 → 狀態 = 已出貨
訂單已更新 → 狀態 = 已交付
但 n8n 先收到已交付,然後才收到已出貨。

我目前的 PostgreSQL 更新只是:

UPDATE orders
SET status = $2,
    updated_at = NOW()
WHERE id = $1;

你會建議使用事件時間戳、序列號或版本欄位,以防止較舊的事件覆寫較新的資料嗎?
你會如何在 PostgreSQL 中安全地實現這一點?

描述問題/錯誤/問題

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

請分享你的工作流

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

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

有關你的 n8n 設定的資訊

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

@Gabby_Timothy 只要 API 提供序號或版本號,就應該使用它。這比只依賴時間戳記安全得多。

UPDATE orders
SET
status = $2,
version = $3,
updated_at = NOW()
WHERE id = $1
AND version < $3;

如果較舊的事件稍後才到達,version < $3 的條件會防止它覆寫較新的狀態。

如果 API 只提供時間戳記,你可以使用:UPDATE orders
SET status = $2,
updated_at = $3
WHERE id = $1
AND updated_at < $3;

如果你需要審計或重放功能,也要分別保存傳入的事件。

如果我使用 version 欄位來防止較舊的事件覆蓋較新的事件,你會如何處理幾乎同時到達的兩個 webhook 事件?

僅使用 version < $3 是否足夠,還是我也應該使用 PostgreSQL 交易或列級鎖定來確保在並發 n8n 執行下完全安全?@Keira_Becky

@Gabby_Timothy version < $3 通常足以防止較舊的事件覆寫較新的事件,因為 PostgreSQL 會以原子方式評估該條件。

UPDATE orders
SET
status = $2,
version = $3,
updated_at = NOW()
WHERE id = $1
AND version < $3;

如果兩個 n8n 執行在幾乎同一時間針對同一訂單進行操作,PostgreSQL 會按順序處理列的更新。一旦寫入較高的版本號,較舊的更新就不再滿足 version < $3 的條件。

只有在需要在同一筆交易中執行多個相依的資料庫操作時,才需要新增 FOR UPDATE。

嗨 @Gabby_Timothy
當受保護的 UPDATE 在列尚未存在時不符合任何列,Postgres 報告 UPDATE 0 且沒有錯誤,事件就消失了。無序交付使得這種情況很常見,因為建立記錄的事件可能在更新記錄的事件之後才到達。將 insert 和保護合併為一個陳述式:

INSERT INTO orders (id, status, version, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status, version = EXCLUDED.version, updated_at = NOW()
WHERE orders.version < EXCLUDED.version
RETURNING *;

id 需要主鍵或唯一索引才能讓 ON CONFLICT 指向目標。缺失的列會被插入,較新的事件會被應用,過期的事件會被跳過,嚴格的 < 也會吞掉相同版本的確切重放。當 DO UPDATE 執行時,Postgres 會鎖定衝突列,並在並發情況下保證原子 insert-or-update 結果,所以這對你的同時到達情況也成立。RETURNING 只會返回陳述式寫入的列,過期事件會返回空值,所以下一個節點可以根據是否應用了任何內容進行分支。
在 Postgres 節點中,用查詢參數執行它,並將查詢批處理設定為獨立。單一查詢會將所有傳入項目折疊為一個陳述式,同一批中的兩個相同訂單事件會因為「ON CONFLICT DO UPDATE command cannot affect row a second time」而失敗。