Oracle native node 在隊列模式下多個 worker 開啟過多並行 DB 連線

描述問題/錯誤/問題

使用原生 Oracle Database 節點進行並行執行會開啟大量並行 Oracle 連線,導致資料庫開始遭受 latch contention。

核心問題是 n8n 同時開啟的大量 Oracle 連線使資料庫不堪重負。每個 n8n 連線對應 Oracle 上的一個專用伺服器連線,因此當許多執行並行執行時,數十個連線同時開啟,資料庫實例開始遭受 latch contention。我的目標是限制同時可開啟多少個 Oracle 連線,以便資料庫不再被淹沒。

我在佇列模式下執行,有 3 個工作容器,N8N_WORKER_CONCURRENCY=10(最多約 30 個並行執行)。

我目前的理解(請更正我):在佇列模式中,每個工作程式是一個獨立的進程,因此我假設 Oracle 連線池是按工作程式建立的,這意味著 Pool Max 是按工作程式應用,而不是全域。

問題:

  1. Oracle 連線池是按工作程式進程建立的(所以 Pool Max 是按工作程式),還是以某種方式共享的?

  2. 在並行執行期間,節點是否按執行檢出一個連線(1:1),還是按查詢/按項目檢出?

  3. 為了減少並行 Oracle 連線,建議做法是降低 N8N_WORKER_CONCURRENCY、減少工作程式數量、降低 Pool Max,還是組合使用?哪一個是正確的主要槓桿?

  4. 如果我降低 Pool Max,額外的連線請求是否會排隊等待空閒連線,而不是使資料庫不堪重負?

  5. 此節點預設以 Thin 還是 Thick 模式執行 node-oracledb?如果是 Thick,是否需要在 Pool Max 之外調整 UV_THREADPOOL_SIZE

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

n8n 中沒有應用級錯誤。症狀在 Oracle 端表現為:數十個活躍連線執行相同的 SQL_ID 並行,卡在 latch: cache buffers chainscpu runqueue。簡化示例:

SID  SERIAL   USER     PROG  TYPE  STATE  WAIT_CLASS  EVENT
...  3332220  APPUSER  node  DED   CPU    Other       cpu runqueue
...  3334370  APPUSER  node  DED   CPU    Concurrency latch: cache buffers chains
...  3331979  APPUSER  node  DED   CPU    Other       latch free
...  3332223  APPUSER  node  DED   CPU    Other       PGA memory operation
(數十個更多,相同的 SQL_ID,相同的等待事件)

請分享您的工作流程

(與本問題無關。問題在於連線/連線 concurrency,而非特定工作流程。)

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

(不適用。)

目前 Oracle 連線池配置(每個憑證)

  • Pool Min: 0

  • Pool Max: 600

  • Pool Increment: 5

  • Pool Maximum Session Life Time: 3600

  • Pool Connection Idle Timeout: 180

  • Connection Class Name: 未設定

  • Connection Timeout: 0

  • Transport Connection Timeout: 20

  • Keepalive Probe Interval: 60

關於您的 n8n 設定的資訊

  • n8n 版本:Version 2.20.7-exp.0

  • Oracle Database 節點版本:Oracle Database 節點版本 1(最新)

  • 資料庫(n8n 本身的資料庫):PostgreSQL 16

  • Oracle Database 版本(目標資料庫):

  • n8n EXECUTIONS_PROCESS / 模式:佇列模式(Redis/Bull),3 個工作程式,N8N_WORKER_CONCURRENCY=10

  • 透過以下方式執行 n8n:Docker(自架)

  • 作業系統:RedHat

@giovanni.tavares

你的假設是對的。在佇列模式中,每個背景工作程式是一個獨立的程序,所以連接池是每個背景工作程式專用的,而不是共享的,這意味著你的 Pool Max 600 實際上在 3 個背景工作程式中最高可達 1800。

節點在每次執行時簽出一個連接,而不是每個查詢或項目,所以每次執行都會持有一個連接。在並行數為 10 的情況下,背景工作程式永遠不需要超過約 10 個。

所以 Pool Max 是你的主要控制桿:將其設定為每個背景工作程式大約等於你的並行數(10-15),這樣總會話數大約是背景工作程式數乘以那個數字,粗略為 30-45。當連接池達到最大時,node-oracledb 會將連接請求排隊,直到有連接釋放,而不是對資料庫造成過度負擔。

它預設執行精簡模式,所以 UV_THREADPOOL_SIZE 只有在你啟用厚模式時才會起作用。

@achamm

深入挖掘後的更新。我降低了 Pool Max,開始看到 NJS-076: connection request rejected. Pool queue length "queueMax" 500 reached 的錯誤。所以請求並不會無限期地在隊列中等待。node-oracledb 只會將待處理的 getConnection() 呼叫排隊到 queueMax(預設 500),超過該數量的任何東西都會被拒絕。

隊列填滿的原因結果是我的架構,而不是個別項目的檢出。我的父工作流程會抓取 1000 筆記錄,並將它們分派到禁用「等待子工作流程完成」的子工作流程。我假設 N8N_WORKER_CONCURRENCY(3 個工作者 x 10)會將其限制在大約 30 個並發,其餘的則輪流等待。

但根據 n8n 文件,並發限制僅適用於從觸發程式或 webhook 節點啟動的生產執行,並明確不適用於子工作流程執行。父工作流程(一次觸發執行)受到限制,但它產生的 1000 個子工作流程執行則不受限制。它們會大量分派,每個都大約在同一時間請求 Oracle 連線,池隊列填滿到 500,其餘的則被 NJS-076 拒絕。該數字與 500 預設值相符,這與此分析一致。

所以這裡實際上有兩個獨立的隊列:n8n 執行隊列(Redis/Bull)和 node-oracledb 池隊列。並發限制僅限制第一個,子工作流程則不適用。

問題:

  1. 禁用「等待子工作流程完成」觸發的子工作流程執行完全繞過並發限制是預期的行為嗎?N8N_WORKER_CONCURRENCY 在佇列模式中是否為它們提供任何限制,或根本沒有?

  2. 鑑於此,建議的模式是什麼來限制分派,以免淹沒連線池:保持「等待子工作流程完成」開啟、在父工作流程中使用循環項目/分批拆分來發送較小的批次,或分組記錄以便較少的子工作流程執行觸發?

  3. queueMax 是否可以透過 Oracle 認證進行配置?它不在目前的認證欄位中,所以我假設它固定為 500。

目前我的計劃是在父側控制展開,而不是依賴 Pool Max,因為降低 Pool Max 只是將故障從「資料庫不堪重負」轉移到「池隊列被拒」。如果有更簡潔的做法,我很樂意聽取意見。

歡迎加入 n8n 社群 @giovanni.tavares
n8n 建議每個 worker 的併發數為 5 或以上;過低的值搭配許多 worker 可能會耗盡資料庫的連線池。最大工作階段數的近似公式為:worker_數量 × Pool Max Configuring queue mode | n8n Docs

以下是其他可能對你有幫助的文件

感謝 @tamy.santosworkers × Pool Max 公式確認了連接池是按 worker,這與 @achamm 所說的相符。

我仍然卡住的部分是,這個公式描述的是穩定狀態下已建立的會話上限,但我遇到的失敗是不同的。NJS-076 是連接請求隊列(queueMax 500)被填滿,因為請求到達速度快於連接釋放速度,而不是達到會話上限。棘手的是,降低 Pool Max 實際上會使 NJS-076 更容易出現(更少的連接來吸收突發流量),而提高它會帶回原始問題,即對資料庫造成過載。所以除非連接請求的突發本身被限流,否則不存在穩定的 Pool Max 值。

這個突發來自父工作流程分派約 1000 個子工作流程執行,且「等待子工作流程完成」設定為關閉,根據文件,生產併發限制不適用於子工作流程執行,所以它們不受 worker 併發限制。

因此我的計畫是在父端進行限流,而不是調整 Pool Max:要麼保持「等待子工作流程完成」開啟,要麼使用「循環項目」/「按批次分割」以較小的批次分派,或者分組記錄以便更少的子工作流程觸發。

有人對佇列模式中子工作流程分派的限流有推薦的模式嗎,考慮到併發限制不適用於它們?那似乎是這裡真正的槓桿。

@giovanni.tavares
你的流程架構是什麼?

當然可以,以下是架構:

父工作流程

  • 觸發器執行並從 API 擷取 ~1000 筆記錄。

  • 一個設定為「每個項目執行一次」的執行工作流程節點會針對每筆記錄分派一個子工作流程執行,所以約 ~1000 個子工作流程執行。

  • 「等待子工作流程完成」已停用,所以父工作流程會全部執行而不等待。

子工作流程(每筆記錄一個執行)

  • 針對同一個資料庫執行多個 Oracle 查詢。它們在線性流程中依序執行,所以單個執行最多一次只佔用一個連線。每個執行的查詢數不會增加並行連線;驅動並行性的是有多少個子工作流程執行同時執行。

基礎設施

  • 佇列模式、Redis/Bull、3 個背景工作容器、N8N_WORKER_CONCURRENCY=10

  • 原生 Oracle Database 節點,版本 2.20.7-exp.0。

問題在於 ~1000 個子工作流程執行並未受並行限制的限制(因為它不適用於子工作流程執行),所以它們會以爆發方式衝擊 Oracle 池並溢出連線請求佇列(queueMax 500),導致 NJS-076。

這看起來更像是一個並發限制問題,而不是 Oracle 查詢問題。在隊列模式和多個工作進程的情況下,我會假設每個工作進程可以創建自己的一組會話,直到被證明否則,然後降低工作進程並發或將 Oracle 繁重的工作通過受控的子流程路由。我會對重試要小心謹慎,因為它們會使會話峰值更糟。你是否用一個工作進程和低並發測試過相同的工作流程,以確認會話計數下降了?