Native Oracle node opening too many concurrent DB sessions in queue mode with multiple workers

Describe the problem/error/question

Parallel executions using the native Oracle Database node open a large number of concurrent Oracle sessions, and the database starts suffering from latch contention.

The core problem is that the high number of Oracle connections opened simultaneously by n8n is overwhelming the database. Each n8n connection maps to a dedicated-server session on Oracle, so when many executions run in parallel, dozens of sessions are opened at the same time and the instance starts suffering from latch contention. My goal is to cap how many Oracle sessions can be open at the same time so the database stops being overwhelmed.

I’m running in queue mode with 3 worker containers and N8N_WORKER_CONCURRENCY=10 (so up to ~30 concurrent executions).

My current understanding (please correct me): in queue mode each worker is a separate process, so I assume the Oracle connection pool is created per worker, meaning Pool Max applies per worker and not globally.

Questions:

  1. Is the Oracle connection pool created per worker process (so Pool Max is per worker), or is it shared somehow?

  2. During parallel executions, does the node check out one connection per execution (1:1), or per query / per item?

  3. To reduce concurrent Oracle sessions, what is the recommended approach: lowering N8N_WORKER_CONCURRENCY, reducing the number of workers, lowering Pool Max, or a combination? Which one is the right primary lever?

  4. If I lower Pool Max, do extra connection requests queue and wait for a free connection instead of overwhelming the DB?

  5. Does this node run node-oracledb in Thin or Thick mode by default? If Thick, does UV_THREADPOOL_SIZE need to be tuned alongside Pool Max?

What is the error message (if any)?

No application-level error in n8n. The symptom is on the Oracle side: dozens of active sessions running the same SQL_ID concurrently, stuck on latch: cache buffers chains and cpu runqueue. Trimmed sample:

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
(dozens more, same SQL_ID, same wait events)

Please share your workflow

(Not relevant to this question. The issue is connection/session concurrency, not a specific workflow.)

Share the output returned by the last node

(Not applicable.)

Current Oracle pool config (per credential)

  • Pool Min: 0

  • Pool Max: 600

  • Pool Increment: 5

  • Pool Maximum Session Life Time: 3600

  • Pool Connection Idle Timeout: 180

  • Connection Class Name: not set

  • Connection Timeout: 0

  • Transport Connection Timeout: 20

  • Keepalive Probe Interval: 60

Information on your n8n setup

  • n8n version: Version 2.20.7-exp.0

  • Oracle Database node version: Oracle Database node version 1 (Latest)

  • Database (n8n’s own DB): PostgreSQL 16

  • Oracle Database version (the target DB):

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

  • Running n8n via: Docker (self-hosted)

  • Operating system: RedHat

Hi @giovanni.tavares

Your assumption is right. In queue mode each worker is a separate process, so the pool is per worker, not shared, which means your Pool Max 600 is really up to 1800 across 3 workers.

The node checks out one connection per execution, not per query or item, so each execution holds one connection. With concurrency 10, a worker never needs more than ~10.

So Pool Max is your primary lever: set it per worker to around your concurrency (10-15), and total sessions land near workers times that, roughly 30-45. When the pool is maxed, node-oracledb queues connection requests until one frees, rather than overwhelming the DB.

It runs thin mode by default, so UV_THREADPOOL_SIZE only matters if you enable thick mode.

Hi @achamm

Update after more digging. I lowered Pool Max and started getting NJS-076: connection request rejected. Pool queue length "queueMax" 500 reached. So requests are not waiting in the queue indefinitely. node-oracledb queues pending getConnection() calls only up to queueMax (default 500), and rejects anything beyond that.

The reason the queue fills up turns out to be my architecture, not per-item checkout. My parent workflow fetches 1000 records and dispatches them to a sub-workflow with “Wait For Sub-Workflow Completion” disabled. I assumed N8N_WORKER_CONCURRENCY (3 workers x 10) would cap this at ~30 concurrent and the rest would wait their turn.

But per the n8n docs, the concurrency limit applies only to production executions started from a trigger or webhook node, and explicitly does not apply to sub-workflow executions. So the parent (one trigger execution) is throttled, but the 1000 sub-workflow executions it spawns are not. They get dispatched in volume, each one requests an Oracle connection at roughly the same time, the pool queue fills to 500, and the rest are rejected with NJS-076. The number matching the 500 default lines up with this.

So there are really two separate queues here: the n8n execution queue (Redis/Bull) and the node-oracledb pool queue. The concurrency limit only gates the first, and not for sub-workflows.

Questions:

  1. Is this the expected behavior, that sub-workflow executions triggered with “Wait For Sub-Workflow Completion” disabled bypass the concurrency limit entirely? Does N8N_WORKER_CONCURRENCY provide any throttling for them in queue mode, or none at all?

  2. Given that, what is the recommended pattern to throttle the dispatch so I don’t flood the connection pool: keeping “Wait For Sub-Workflow Completion” on, using Loop Over Items / Split in Batches in the parent to send smaller batches, or grouping records so fewer sub-workflow executions fire?

  3. Is queueMax configurable through the Oracle credential at all? It is not in the current credential fields, so I assume it is fixed at 500.

For now my plan is to control the fan-out on the parent side rather than rely on Pool Max, since lowering Pool Max only shifts the failure from “DB overwhelmed” to “pool queue rejected”. Happy to hear if there is a cleaner approach.

Welcome to the n8n community @giovanni.tavares
n8n recommends concurrency of 5 or more per worker; very low values with many workers can exhaust the database connection pool. The approximate formula for maximum sessions would be: number_of_workers × Pool Max Configuring queue mode | n8n Docs

Here are some other docs that can help you

Thanks @tamy.santos. The workers × Pool Max formula confirms the pool is per worker, which matches what @achamm said.

The part I am still stuck on is that this formula describes the ceiling of established sessions in steady state, but my failure is different. NJS-076 is the connection request queue (queueMax 500) filling up because requests arrive faster than connections free up, not the session ceiling being exceeded. The tricky part is that lowering Pool Max actually makes NJS-076 more likely (fewer connections to absorb the burst), while raising it brings back the original problem of overwhelming the DB. So there is no stable Pool Max value unless the burst of connection requests itself is throttled.

That burst comes from the parent workflow dispatching ~1000 sub-workflow executions with “Wait For Sub-Workflow Completion” off, and per the docs the production concurrency limit does not apply to sub-workflow executions, so they are not capped at the worker concurrency.

So my plan is to throttle on the parent side rather than tune Pool Max: either keep “Wait For Sub-Workflow Completion” on, or use Loop Over Items / Split in Batches to dispatch in smaller batches, or group records so fewer sub-workflows fire.

Does anyone have a recommended pattern for throttling sub-workflow dispatch in queue mode, given the concurrency limit does not cover them? That seems to be the real lever here.

@giovanni.tavares
What’s the architecture of your workflow?

Sure, here’s the architecture:

Parent workflow

  • Trigger runs and fetches ~1000 records from API.

  • An Execute Workflow node set to “run once for each item” dispatches one sub-workflow execution per record, so ~1000 sub-workflow executions.

  • “Wait For Sub-Workflow Completion” is disabled, so the parent fires them all without waiting.

Sub-workflow (one execution per record)

  • Runs several Oracle queries against the same database. They run sequentially in a linear flow, so a single execution holds at most one connection at a time. The number of queries per execution does not multiply concurrent connections; what drives concurrency is how many sub-workflow executions run at the same time.

Infra

  • Queue mode, Redis/Bull, 3 worker containers, N8N_WORKER_CONCURRENCY=10.

  • Native Oracle Database node, version 2.20.7-exp.0.

The issue is that the ~1000 sub-workflow executions are not capped by the concurrency limit (since it does not apply to sub-workflow executions), so they hit the Oracle pool in bursts and overflow the connection request queue (queueMax 500), giving NJS-076.

This looks like a concurrency limit problem more than an Oracle query problem. With queue mode and multiple workers, I’d assume each worker can create its own set of sessions until proven otherwise, then lower worker concurrency or route Oracle-heavy work through a controlled subflow. I’d be careful with retries here because they can make the session spike worse. Have you tested the same workflow with one worker and low concurrency to confirm the session count drops?