Native Oracle-Knoten öffnet zu viele gleichzeitige DB-Sessions im Queue-Modus mit mehreren Workern

Problem/Fehler/Frage beschreiben

Parallele Ausführungen mit dem nativen Oracle-Database-Node öffnen eine große Anzahl gleichzeitiger Oracle-Sessions, und die Datenbank leidet unter Latch-Contention.

Das Kernproblem ist, dass die hohe Anzahl von Oracle-Verbindungen, die n8n gleichzeitig öffnet, die Datenbank überfordert. Jede n8n-Verbindung entspricht einer Dedicated-Server-Session in Oracle, sodass bei vielen parallelen Ausführungen Dutzende von Sessions gleichzeitig geöffnet werden und die Instanz unter Latch-Contention leidet. Mein Ziel ist es, die Anzahl der gleichzeitig offenen Oracle-Sessions zu begrenzen, damit die Datenbank nicht überfordert wird.

Ich führe im Queue-Modus mit 3 Worker-Containern und N8N_WORKER_CONCURRENCY=10 aus (also bis zu ca. 30 gleichzeitige Ausführungen).

Mein derzeitiges Verständnis (bitte korrigieren Sie mich): Im Queue-Modus ist jeder Worker ein separater Prozess, daher nehme ich an, dass der Oracle-Verbindungspool pro Worker erstellt wird, was bedeutet, dass Pool Max pro Worker und nicht global gilt.

Fragen:

  1. Wird der Oracle-Verbindungspool pro Worker-Prozess erstellt (also Pool Max pro Worker), oder wird er irgendwie geteilt?

  2. Während paralleler Ausführungen: Prüft der Node eine Verbindung pro Ausführung (1:1) aus, oder pro Query/pro Element?

  3. Um gleichzeitige Oracle-Sessions zu reduzieren, was ist der empfohlene Ansatz: N8N_WORKER_CONCURRENCY senken, die Anzahl der Worker reduzieren, Pool Max senken oder eine Kombination? Welcher ist der richtige primäre Hebel?

  4. Wenn ich Pool Max senke, werden zusätzliche Verbindungsanfragen in die Warteschlange eingereiht und warten auf eine freie Verbindung, statt die DB zu überfordern?

  5. Führt dieser Node node-oracledb standardmäßig im Thin- oder Thick-Modus aus? Falls Thick, muss UV_THREADPOOL_SIZE neben Pool Max angepasst werden?

Wie lautet die Fehlermeldung (falls vorhanden)?

Kein Fehler auf Anwendungsebene in n8n. Das Symptom liegt auf der Oracle-Seite: Dutzende aktive Sessions führen dieselbe SQL_ID gleichzeitig aus und bleiben bei latch: cache buffers chains und cpu runqueue hängen. Gekürzte Probe:

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
(Dutzende weitere, gleiche SQL_ID, gleiche Wait-Events)

Bitte teilen Sie Ihren Workflow

(Nicht relevant für diese Frage. Das Problem ist die Verbindungs-/Session-Concurrency, nicht ein bestimmter Workflow.)

Ausgabe des letzten Nodes

(Nicht zutreffend.)

Aktuelle Oracle-Pool-Konfiguration (pro Credential)

  • Pool Min: 0

  • Pool Max: 600

  • Pool Increment: 5

  • Pool Maximum Session Life Time: 3600

  • Pool Connection Idle Timeout: 180

  • Connection Class Name: nicht gesetzt

  • Connection Timeout: 0

  • Transport Connection Timeout: 20

  • Keepalive Probe Interval: 60

Informationen zu Ihrem n8n-Setup

  • n8n-Version: Version 2.20.7-exp.0

  • Oracle-Database-Node-Version: Oracle Database node version 1 (Latest)

  • Datenbank (n8n-eigene DB): PostgreSQL 16

  • Oracle-Database-Version (die Ziel-DB):

  • n8n EXECUTIONS_PROCESS / Modus: Queue-Modus (Redis/Bull), 3 Worker, N8N_WORKER_CONCURRENCY=10

  • n8n wird ausgeführt über: Docker (selbst-gehostet)

  • Betriebssystem: RedHat

Hallo @giovanni.tavares

Deine Annahme ist richtig. Im Queue-Modus ist jeder Worker ein separater Prozess, daher ist der Pool pro Worker, nicht geteilt, was bedeutet, dass dein Pool Max 600 tatsächlich bis zu 1800 über 3 Worker verteilt sind.

Der Node checkt eine Verbindung pro Ausführung aus, nicht pro Query oder Element, daher hält jede Ausführung eine Verbindung. Mit Concurrency 10 braucht ein Worker nie mehr als ~10.

Also ist Pool Max dein wichtigster Hebel: stell es pro Worker auf ungefähr deine Concurrency (10–15) ein, und die Gesamtsessions landen bei ungefähr Worker mal diesem Wert, grob 30–45. Wenn der Pool maxed ist, reiht node-oracledb Verbindungsanfragen in eine Warteschlange ein, bis eine freiwird, anstatt die DB zu überlasten.

Es läuft standardmäßig im Thin-Modus, daher ist UV_THREADPOOL_SIZE nur relevant, wenn du den Thick-Modus aktivierst.

Hallo @achamm

Update nach weiteren Nachforschungen. Ich habe Pool Max reduziert und bin auf NJS-076: connection request rejected. Pool queue length "queueMax" 500 reached gestoßen. Anfragen warten also nicht unbegrenzt in der Warteschlange. node-oracledb reiht ausstehende getConnection()-Aufrufe nur bis queueMax (Standard 500) ein und lehnt alles darüber hinaus ab.

Der Grund, warum sich die Warteschlange füllt, liegt nicht an der Checkout-Logik pro Element, sondern an meiner Architektur. Mein übergeordneter Workflow ruft 1000 Datensätze ab und sendet sie an einen Sub-Workflow mit deaktiviertem “Wait For Sub-Workflow Completion”. Ich bin davon ausgegangen, dass N8N_WORKER_CONCURRENCY (3 Worker x 10) dies auf etwa 30 gleichzeitige Prozesse begrenzen würde und der Rest würde warten.

Aber laut n8n-Dokumentation gilt das Concurrency-Limit nur für Produktionsausführungen, die von einem Trigger- oder Webhook-Knoten gestartet werden, und gilt explizit nicht für Sub-Workflow-Ausführungen. Der übergeordnete Workflow (eine Trigger-Ausführung) wird gedrosselt, aber die 1000 Sub-Workflow-Ausführungen, die er startet, werden nicht gedrosselt. Sie werden in großer Menge versendet, jede fordert etwa zur gleichen Zeit eine Oracle-Verbindung an, die Pool-Warteschlange füllt sich bis 500, und der Rest wird mit NJS-076 abgelehnt. Die Zahl, die mit dem Standard-Wert 500 übereinstimmt, unterstreicht dies.

Es gibt also wirklich zwei separate Warteschlangen: die n8n-Ausführungswarteschlange (Redis/Bull) und die node-oracledb-Pool-Warteschlange. Das Concurrency-Limit kontrolliert nur die erste, und nicht für Sub-Workflows.

Fragen:

  1. Ist das das erwartete Verhalten, dass Sub-Workflow-Ausführungen, die mit deaktiviertem “Wait For Sub-Workflow Completion” ausgelöst werden, das Concurrency-Limit vollständig umgehen? Bietet N8N_WORKER_CONCURRENCY im Queue-Modus irgendeine Drosselung für sie oder überhaupt keine?

  2. Welches Muster wird empfohlen, um die Versendung zu drosseln, sodass ich den Verbindungspool nicht überflute: “Wait For Sub-Workflow Completion” aktivieren, Loop Over Items / Split in Batches im übergeordneten Workflow verwenden, um kleinere Batches zu senden, oder Datensätze so gruppieren, dass weniger Sub-Workflow-Ausführungen stattfinden?

  3. Ist queueMax durch die Oracle-Anmeldedaten überhaupt konfigurierbar? Es ist nicht in den aktuellen Anmeldedatenfeldern vorhanden, also gehe ich davon aus, dass es auf 500 festgelegt ist.

Mein Plan ist vorerst, den Fan-out auf der übergeordneten Seite zu steuern, anstatt mich auf Pool Max zu verlassen, da das Reduzieren von Pool Max nur den Fehler von “DB überlastet” zu “Pool-Warteschlange abgelehnt” verschiebt. Gerne höre ich, wenn es einen saubereren Ansatz gibt.

Willkommen in der n8n-Community @giovanni.tavares
n8n empfiehlt eine Nebenläufigkeit von 5 oder mehr pro Worker; sehr niedrige Werte mit vielen Workern können den Verbindungspool der Datenbank erschöpfen. Die ungefähre Formel für maximale Sitzungen wäre: anzahl_der_worker × Pool Max Configuring queue mode | n8n Docs

Hier sind weitere Dokumente, die dir helfen können

Danke @tamy.santos. Die Formel workers × Pool Max bestätigt, dass der Pool pro Worker existiert, was dem entspricht, was @achamm gesagt hat.

Woran ich immer noch hänge, ist, dass diese Formel die Obergrenze der etablierten Sessions im stabilen Zustand beschreibt, aber mein Fehler ist anders. NJS-076 ist die Connection-Request-Warteschlange (queueMax 500), die sich füllt, weil Anfragen schneller ankommen als Connections freigegeben werden, nicht weil die Session-Obergrenze überschritten wird. Das Knifflige ist, dass das Senken von Pool Max NJS-076 eigentlich wahrscheinlicher macht (weniger Connections für die Spitzenlast), während das Erhöhen das ursprüngliche Problem zurückbringt, die Datenbank zu überlasten. Es gibt also keinen stabilen Pool Max-Wert, es sei denn, die Spitzenlast der Connection-Anfragen selbst wird drosselt.

Diese Spitzenlast kommt daher, dass der übergeordnete Workflow ~1000 Sub-Workflow-Ausführungen mit “Wait For Sub-Workflow Completion” deaktiviert versendet, und laut der Dokumentation gilt das Production Concurrency Limit nicht für Sub-Workflow-Ausführungen, sodass sie nicht auf die Worker-Concurrency begrenzt sind.

Mein Plan ist daher, auf der übergeordneten Seite zu drosseln, statt Pool Max zu optimieren: entweder “Wait For Sub-Workflow Completion” aktivieren, oder Loop Over Items / Split in Batches verwenden, um in kleineren Batches zu verteilen, oder Datensätze gruppieren, sodass weniger Sub-Workflows gestartet werden.

Hat jemand ein empfohlenes Muster zum Drosseln der Sub-Workflow-Verarbeitung im Queue-Modus, da das Concurrency Limit diese nicht abdeckt? Das scheint hier der eigentliche Hebel zu sein.

@giovanni.tavares
Wie ist die Architektur deines Workflows?

Sicher, hier ist die Architektur:

Parent-Workflow

  • Trigger läuft und ruft ~1000 Datensätze von der API ab.

  • Ein Execute Workflow-Node, der auf „einmal pro Element ausführen

Das sieht eher nach einem Parallelitätslimit-Problem als nach einem Oracle-Query-Problem aus. Mit Queue-Modus und mehreren Workern würde ich davon ausgehen, dass jeder Worker seinen eigenen Satz von Sessions erstellen kann, bis das Gegenteil bewiesen ist. Dann könntest du die Worker-Parallelität reduzieren oder Oracle-intensive Arbeiten über einen kontrollierten Subflow leiten. Ich würde vorsichtig mit Wiederholungen sein, da diese den Session-Anstieg verschlimmern können. Hast du den gleichen Workflow bereits mit einem Worker und niedriger Parallelität getestet, um zu bestätigen, dass die Session-Anzahl sinkt?