在高頻循環執行中解析大型嵌套陣列時出現 Code node 記憶體不足錯誤

嗨各位,我目前透過 Docker 運行自架的 n8n 實例,並用它來建置高度自動化的資料管線。工作流程會從外部沙箱環境擷取密集的動態系統執行遙測字串批次,將原始資料傳入程式碼節點進行正規表達式篩選,然後嘗試迴圈遍歷項目以更新外部資料庫。這個設計在小型測試負載下運作良好,但在處理生產級別的資料量時,我遇到了一個關鍵的效能瓶頸。

主要問題是,每當工作流程遇到大型、多兆字節的巢狀陣列結構時,n8n 容器就會拋出記憶體不足錯誤並完全崩潰。這會在整個設定中引發一連串的連鎖問題:主動執行追蹤中斷、佇列堆積重試、本地 SQLite 資料庫完全鎖定,直到我透過 SSH 手動強制重啟容器。我試圖確定內部 JavaScript 程式碼節點是否因為在單一區塊中處理整個負載而無法在執行中釋放記憶體,或者 n8n 的執行資料歷史記錄設定是否只是導致容器的 RAM 使用超過其配置的限制。

我一直在試驗從 免費下載 取得的自訂記憶體處理範本,看看在資料到達解析引擎之前明確將傳入的 JSON 陣列分割成較小的區塊是否能防止系統卡住。不幸的是,即使將資料分成微批次,持續的背景迴圈仍會導致緩慢的記憶體洩漏,最終在數小時的穩定自動化後殺死工作流程執行緒。我試圖確定是否最好透過 Webhook 節點將大量資料解析機制完全卸載至外部微服務,或者是否有環境變數可以在我的組合檔案中設定,以強制在主動工作流程迴圈之間進行積極垃圾回收。

如果各位中有人設計過大容量資料迴圈或在自架節點內管理易失的指令碼產生陣列而不觸發容器逾時或記憶體尖峰的經驗,你們是如何最佳化資料緩衝區的?我會非常感謝任何工作流程結構建議或效能配置,以保持此管線的穩定性。

Hi @Ivonne566 歡迎!
在整個運行過程中,Code 節點在同一個 V8 堆中保存每個已處理的塊才是洩漏的原因,而不是執行歷史記錄。在一個 Code 節點執行中進行手動分塊不會在迭代之間釋放該記憶體,只有結束執行才會,而且沒有環境變數可以強制在迴圈中間進行垃圾回收。使用 Loop Over Items 節點來批處理陣列,然後透過 Execute Workflow 將每個批次交給子工作流程。每個批次都是自己的執行,在下一個批次開始之前會結束並釋放記憶體,而父工作流程只保存返回的小結果。
作為您重新設計的臨時解決方案,請提高 V8 堆:

NODE_OPTIONS=--max-old-space-size=<SIZE_IN_MB>

這只是提高了上限,當有效載荷增長超過該上限時,相同執行的洩漏仍會返回。

如果崩潰停止後 SQLite 鎖定持續發生,請改用 PostgreSQL,SQLite 不是為重試積壓所產生的並發寫入而設計的。

針對你問題的後半部分(「執行資料歷史記錄設定是否只是在佔用容器的 RAM 使用量」),由於還沒有人回答:

執行歷史記錄主要對你的資料庫造成壓力,而不是對 worker heap。當執行正在進行時,流經工作流程的項目會保留在記憶體中,無論任何儲存設定如何 — 那就是 Anshul 提到的 V8 heap 限制。但當執行完成時,n8n 會將整個執行負載寫入資料庫。在 SQLite 上使用多 MB 的嵌套陣列時,這就是導致資料庫檔案膨脹的原因,並使你描述的鎖定競爭和佇列積壓惡化。

幾個值得檢查的設定:

  • EXECUTIONS_DATA_SAVE_ON_SUCCESS=none(並保持 EXECUTIONS_DATA_SAVE_ON_ERROR=all 以便你仍然可以調試失敗)— 停止為每次成功執行寫入龐大的負載。
  • 修剪預設為啟用,但請檢查 EXECUTIONS_DATA_MAX_AGE(預設 336 小時 = 14 天)。根據你的負載大小,縮短它可以讓 SQLite 保持得更小。EXECUTIONS_DATA_PRUNE_MAX_COUNT(預設 10,000)是配套的上限。
  • 除非你真的需要崩潰恢復,否則保持 EXECUTIONS_DATA_SAVE_ON_PROGRESS 關閉 — 在執行中途儲存進度會使資料庫寫入增加好幾倍。

以上都無法修復 OOM 本身 — Anshul 的 Loop Over Items + 子工作流程批處理才是真正的修復方案。但這樣可以消除資料庫造成的複合壓力,如果你如建議遷移到 Postgres,相同的設定也能保持該資料庫的健康。

感謝您向我們報告這個問題,我們已經建立了 CV-15 作為內部開發工單來進行調查。

嗨 @Ivonne566,
你處理的列數是多少?以及結構(欄位)+ 每列的平均大小?