Nœud Oracle natif ouvrant trop de sessions DB simultanées en mode file d'attente avec plusieurs workers

Décrivez le problème/l’erreur/la question

Les exécutions parallèles utilisant le nœud Oracle Database natif ouvrent un grand nombre de sessions Oracle simultanées, et la base de données commence à souffrir de contention de verrous (latch contention).

Le problème fondamental est que le grand nombre de connexions Oracle ouvertes simultanément par n8n surcharge la base de données. Chaque connexion n8n correspond à une session dedicated-server sur Oracle, donc lorsque de nombreuses exécutions s’exécutent en parallèle, des dizaines de sessions sont ouvertes en même temps et l’instance commence à souffrir de contention de verrous. Mon objectif est de limiter le nombre de sessions Oracle pouvant être ouvertes simultanément afin que la base de données cesse d’être surcharge.

Je fonctionne en mode queue avec 3 conteneurs worker et N8N_WORKER_CONCURRENCY=10 (soit environ 30 exécutions simultanées).

Ma compréhension actuelle (veuillez me corriger) : en mode queue, chaque worker est un processus distinct, donc j’assume que le pool de connexions Oracle est créé par worker, ce qui signifie que Pool Max s’applique par worker et non globalement.

Questions :

  1. Le pool de connexions Oracle est-il créé par processus worker (donc Pool Max par worker), ou est-il partagé d’une manière ou d’une autre ?

  2. Lors des exécutions parallèles, le nœud extrait-il une connexion par exécution (1:1), ou par requête / par élément ?

  3. Pour réduire les sessions Oracle simultanées, quelle est l’approche recommandée : réduire N8N_WORKER_CONCURRENCY, réduire le nombre de workers, réduire Pool Max, ou une combinaison ? Lequel est le bon levier principal ?

  4. Si je réduis Pool Max, les demandes de connexion supplémentaires font-elles la queue et attendent une connexion libre au lieu de surcharger la base de données ?

  5. Ce nœud exécute-t-il node-oracledb en mode Thin ou Thick par défaut ? Si Thick, est-ce que UV_THREADPOOL_SIZE doit être configuré aux côtés de Pool Max ?

Quel est le message d’erreur (le cas échéant) ?

Aucune erreur au niveau de l’application dans n8n. Le symptôme se situe côté Oracle : des dizaines de sessions actives exécutant le même SQL_ID simultanément, bloquées sur latch: cache buffers chains et cpu runqueue. Exemple réduit :

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
(des dizaines d'autres, même SQL_ID, mêmes événements d'attente)

Partagez votre workflow

(Non pertinent pour cette question. Le problème concerne la concurrence des connexions/sessions, non un workflow spécifique.)

Partagez le résultat renvoyé par le dernier nœud

(Non applicable.)

Configuration du pool Oracle actuelle (par credential)

  • Pool Min : 0

  • Pool Max : 600

  • Pool Increment : 5

  • Pool Maximum Session Life Time : 3600

  • Pool Connection Idle Timeout : 180

  • Connection Class Name : non défini

  • Connection Timeout : 0

  • Transport Connection Timeout : 20

  • Keepalive Probe Interval : 60

Informations sur votre configuration n8n

  • Version n8n : Version 2.20.7-exp.0

  • Version du nœud Oracle Database : Oracle Database node version 1 (Latest)

  • Base de données (base de données propre de n8n) : PostgreSQL 16

  • Version d’Oracle Database (base de données cible) :

  • Mode n8n EXECUTIONS_PROCESS / queue mode : queue mode (Redis/Bull), 3 workers, N8N_WORKER_CONCURRENCY=10

  • Exécution de n8n via : Docker (auto-hébergé)

  • Système d’exploitation : RedHat

Bonjour @giovanni.tavares

Votre hypothèse est correcte. En mode queue, chaque worker est un processus séparé, donc le pool est par worker, non partagé, ce qui signifie que votre Pool Max 600 représente vraiment jusqu’à 1800 sur 3 workers.

Le nœud vérifie une connexion par exécution, pas par requête ou élément, donc chaque exécution maintient une connexion. Avec une concurrence de 10, un worker n’a jamais besoin de plus d’environ 10.

Donc Pool Max est votre principal levier : définissez-le par worker à environ votre concurrence (10-15), et le total des sessions se situe près de workers fois cela, approximativement 30-45. Quand le pool est au maximum, node-oracledb met en file d’attente les demandes de connexion jusqu’à ce qu’une se libère, plutôt que de surcharger la DB.

Il s’exécute en mode thin par défaut, donc UV_THREADPOOL_SIZE n’a d’importance que si vous activez le mode thick.

Salut @achamm

Mise à jour après plus de recherches. J’ai réduit Pool Max et j’ai commencé à obtenir NJS-076: connection request rejected. Pool queue length "queueMax" 500 reached. Les requêtes n’attendent donc pas indéfiniment dans la queue. node-oracledb met en queue les appels getConnection() en attente seulement jusqu’à queueMax (par défaut 500), et rejette tout ce qui dépasse cela.

Il s’avère que la raison du remplissage de la queue vient de mon architecture, pas du checkout par élément. Mon workflow parent récupère 1000 enregistrements et les envoie à un sous-workflow avec « Wait For Sub-Workflow Completion » désactivé. J’ai supposé que N8N_WORKER_CONCURRENCY (3 workers x 10) limiterait cela à ~30 concurrent et que le reste attendrait son tour.

Mais selon la documentation n8n, la limite de concurrence ne s’applique qu’aux exécutions de production démarrées à partir d’un nœud de déclenchement ou de webhook, et ne s’applique explicitement pas aux exécutions de sous-workflow. Le parent (une seule exécution de déclenchement) est limité, mais les 1000 exécutions de sous-workflow qu’il génère ne le sont pas. Elles sont envoyées en volume, chacune demande une connexion Oracle à peu près au même moment, la queue du pool se remplit jusqu’à 500, et le reste est rejeté avec NJS-076. Le nombre correspondant au défaut de 500 s’aligne avec cela.

Il y a donc vraiment deux queues distinctes ici : la queue d’exécution n8n (Redis/Bull) et la queue du pool node-oracledb. La limite de concurrence ne contrôle que la première, et pas pour les sous-workflows.

Questions :

  1. Est-ce le comportement attendu, que les exécutions de sous-workflow déclenchées avec « Wait For Sub-Workflow Completion » désactivé contournent complètement la limite de concurrence ? N8N_WORKER_CONCURRENCY fournit-il une limitation pour eux en mode queue, ou aucune ?

  2. Étant donné cela, quel est le modèle recommandé pour limiter l’envoi afin de ne pas surcharger le pool de connexions : garder « Wait For Sub-Workflow Completion » activé, utiliser Loop Over Items / Split in Batches dans le parent pour envoyer des lots plus petits, ou regrouper les enregistrements pour que moins d’exécutions de sous-workflow se déclenchent ?

  3. queueMax est-il configurable via les identifiants Oracle du tout ? Il ne figure pas dans les champs d’identifiants actuels, je suppose donc qu’il est fixé à 500.

Pour l’instant, mon plan est de contrôler la distribution du côté parent plutôt que de compter sur Pool Max, car réduire Pool Max déplace simplement la défaillance de « DB overwhelmed » à « pool queue rejected ». Je serais heureux d’entendre s’il y a une approche plus propre.

bienvenue dans la communauté n8n @giovanni.tavares
n8n recommande une concurrence de 5 ou plus par worker ; des valeurs très basses avec de nombreux workers peuvent épuiser le pool de connexions de la base de données. La formule approximative des sessions maximales serait : nombre_de_workers × Pool Max Configuring queue mode | n8n Docs

voici d’autres documents qui peuvent vous aider

Merci @tamy.santos. La formule workers × Pool Max confirme que le pool est par worker, ce qui correspond à ce que @achamm a dit.

La partie sur laquelle je suis toujours bloqué est que cette formule décrit le plafond des sessions établies en régime permanent, mais mon échec est différent. NJS-076 est la file d’attente des demandes de connexion (queueMax 500) qui se remplit parce que les demandes arrivent plus vite que les connexions ne se libèrent, ce n’est pas le plafond de sessions qui est dépassé. La partie délicate est que réduire Pool Max rend en fait NJS-076 plus probable (moins de connexions pour absorber la rafale), tandis que l’augmenter ramène le problème initial de surcharger la BD. Il n’y a donc pas de valeur Pool Max stable à moins que la rafale elle-même de demandes de connexion ne soit limitée.

Cette rafale provient du workflow parent qui distribue ~1000 exécutions de sous-workflow avec « Wait For Sub-Workflow Completion » désactivé, et selon la documentation, la limite de concurrence de production ne s’applique pas aux exécutions de sous-workflow, elles ne sont donc pas limitées par la concurrence des workers.

Mon plan est donc de limiter côté parent plutôt que d’ajuster Pool Max : soit garder « Wait For Sub-Workflow Completion » activé, soit utiliser Loop Over Items / Split in Batches pour distribuer par plus petits lots, soit regrouper les enregistrements pour que moins de sous-workflows se lancent.

Quelqu’un a-t-il un pattern recommandé pour limiter la distribution de sous-workflow en mode queue, étant donné que la limite de concurrence ne les couvre pas ? Cela semble être le vrai levier ici.

@giovanni.tavares
quelle est l’architecture de votre flux ?

Bien sûr, voici l’architecture :

Workflow parent

  • Le déclencheur s’exécute et récupère ~1000 enregistrements depuis l’API.

  • Un nœud Execute Workflow configuré en « exécution unique pour chaque élément » lance une exécution de sous-workflow par enregistrement, soit ~1000 exécutions de sous-workflow.

  • « Wait For Sub-Workflow Completion » est désactivé, donc le parent les lance tous sans attendre.

Sous-workflow (une exécution par enregistrement)

  • Exécute plusieurs requêtes Oracle sur la même base de données. Elles s’exécutent séquentiellement dans un flux linéaire, donc une seule exécution maintient au maximum une connexion à la fois. Le nombre de requêtes par exécution n’augmente pas les connexions concurrentes ; c’est le nombre d’exécutions de sous-workflow qui s’exécutent simultanément qui détermine la concurrence.

Infra

  • Mode Queue, Redis/Bull, 3 conteneurs workers, N8N_WORKER_CONCURRENCY=10.

  • Nœud Native Oracle Database, version 2.20.7-exp.0.

Le problème est que les ~1000 exécutions de sous-workflow ne sont pas limitées par la limite de concurrence (puisqu’elle ne s’applique pas aux exécutions de sous-workflow), donc elles frappent le pool Oracle par rafales et débordent la queue de demandes de connexion (queueMax 500), ce qui génère NJS-076.

Cela semble être un problème de limite de concurrence plutôt qu’un problème de requête Oracle. En mode file d’attente avec plusieurs workers, je supposerais que chaque worker peut créer son propre ensemble de sessions jusqu’à preuve du contraire, puis réduire la concurrence des workers ou acheminer les travaux gourmands en Oracle via un sous-flux contrôlé. Je serais prudent avec les retries ici car ils peuvent aggraver les pics de sessions. Avez-vous testé le même workflow avec un seul worker et une concurrence faible pour confirmer que le nombre de sessions baisse ?