Nó Oracle nativo abrindo muitas sessões de BD simultâneas em modo de fila com múltiplos workers

Descreva o problema/erro/pergunta

Executões paralelas usando o nó nativo Oracle Database abrem um grande número de sessões Oracle simultâneas, e o banco de dados começa a sofrer com contenção de latch.

O problema central é que o alto número de conexões Oracle abertas simultaneamente pelo n8n está sobrecarregando o banco de dados. Cada conexão do n8n mapeia para uma sessão dedicated-server no Oracle, então quando muitas execuções são executadas em paralelo, dezenas de sessões são abertas ao mesmo tempo e a instância começa a sofrer com contenção de latch. Meu objetivo é limitar quantas sessões Oracle podem estar abertas ao mesmo tempo para que o banco de dados pare de ser sobrecarregado.

Estou executando em modo queue com 3 contêineres worker e N8N_WORKER_CONCURRENCY=10 (portanto até ~30 execuções simultâneas).

Meu entendimento atual (por favor, corrija-me): em modo queue cada worker é um processo separado, então presumo que o connection pool do Oracle é criado por worker, significando que Pool Max se aplica por worker e não globalmente.

Perguntas:

  1. O connection pool do Oracle é criado por processo worker (então Pool Max é por worker), ou é compartilhado de alguma forma?

  2. Durante execuções paralelas, o nó verifica uma conexão por execução (1:1), ou por query / por item?

  3. Para reduzir sessões Oracle simultâneas, qual é a abordagem recomendada: diminuir N8N_WORKER_CONCURRENCY, reduzir o número de workers, diminuir Pool Max, ou uma combinação? Qual é a alavanca primária correta?

  4. Se eu diminuir Pool Max, as solicitações de conexão extras ficam em fila e esperam por uma conexão livre em vez de sobrecarregar o BD?

  5. Este nó executa node-oracledb em modo Thin ou Thick por padrão? Se Thick, UV_THREADPOOL_SIZE precisa ser ajustado junto com Pool Max?

Qual é a mensagem de erro (se houver)?

Nenhum erro no nível da aplicação no n8n. O sintoma está no lado do Oracle: dezenas de sessões ativas executando o mesmo SQL_ID simultaneamente, travadas em latch: cache buffers chains e cpu runqueue. Amostra resumida:

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
(dezenas mais, mesmo SQL_ID, mesmos eventos de espera)

Por favor, compartilhe seu workflow

(Não relevante para esta pergunta. O problema é concorrência de conexão/sessão, não um workflow específico.)

Compartilhe o resultado retornado pelo último nó

(Não aplicável.)

Configuração atual do pool Oracle (por credencial)

  • Pool Min: 0

  • Pool Max: 600

  • Pool Increment: 5

  • Pool Maximum Session Life Time: 3600

  • Pool Connection Idle Timeout: 180

  • Connection Class Name: não definido

  • Connection Timeout: 0

  • Transport Connection Timeout: 20

  • Keepalive Probe Interval: 60

Informações sobre sua configuração n8n

  • Versão do n8n: Version 2.20.7-exp.0

  • Versão do nó Oracle Database: Oracle Database node version 1 (Latest)

  • Banco de dados (banco de dados próprio do n8n): PostgreSQL 16

  • Versão do Oracle Database (o BD de destino):

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

  • Executando n8n via: Docker (auto-hospedado)

  • Sistema operacional: RedHat

Oi @giovanni.tavares

Sua suposição está correta. Em modo fila, cada worker é um processo separado, então o pool é por worker, não compartilhado, o que significa que seu Pool Max 600 é realmente até 1800 entre 3 workers.

O node obtém uma conexão por execução, não por query ou item, então cada execução mantém uma conexão. Com concorrência 10, um worker nunca precisa de mais de ~10.

Então Pool Max é sua alavanca principal: defina por worker para cerca de sua concorrência (10-15), e as sessões totais chegam perto de workers vezes isso, aproximadamente 30-45. Quando o pool está no máximo, node-oracledb coloca em fila as solicitações de conexão até que uma se libere, em vez de sobrecarregar o BD.

Ele executa em thin mode por padrão, então UV_THREADPOOL_SIZE só importa se você ativar thick mode.

Oi @achamm

Atualização após investigação mais aprofundada. Reduzi Pool Max e comecei a receber NJS-076: connection request rejected. Pool queue length "queueMax" 500 reached. Então as requisições não ficam esperando na fila indefinidamente. node-oracledb coloca chamadas getConnection() pendentes em fila apenas até queueMax (padrão 500), e rejeita qualquer coisa além disso.

O motivo pelo qual a fila enche acabou sendo minha arquitetura, não checkout por item. Meu workflow pai busca 1000 registros e os despacha para um sub-workflow com “Wait For Sub-Workflow Completion” desabilitado. Eu assumia que N8N_WORKER_CONCURRENCY (3 workers x 10) limitaria isso a ~30 concurrent e o resto esperaria sua vez.

Mas de acordo com a documentação do n8n, o limite de concorrência se aplica apenas a execuções em produção iniciadas a partir de um nó trigger ou webhook, e explicitamente não se aplica a execuções de sub-workflow. O pai (uma execução trigger) é limitado, mas as 1000 execuções de sub-workflow que ele gera não são. Elas são despachadas em volume, cada uma requisita uma conexão Oracle aproximadamente no mesmo tempo, a fila do pool enche para 500, e o resto é rejeitado com NJS-076. O número correspondendo ao padrão 500 se alinha com isso.

Então existem realmente duas filas separadas aqui: a fila de execução do n8n (Redis/Bull) e a fila do pool node-oracledb. O limite de concorrência apenas controla a primeira, e não para sub-workflows.

Perguntas:

  1. Este é o comportamento esperado, que execuções de sub-workflow acionadas com “Wait For Sub-Workflow Completion” desabilitado contornam completamente o limite de concorrência? N8N_WORKER_CONCURRENCY fornece qualquer limitação para elas em modo fila, ou nenhuma?

  2. Dado isso, qual é o padrão recomendado para limitar o despacho de forma que eu não inunde o pool de conexões: manter “Wait For Sub-Workflow Completion” ativado, usar Loop Over Items / Split in Batches no pai para enviar lotes menores, ou agrupar registros para que menos execuções de sub-workflow disparem?

  3. queueMax é configurável através da credencial Oracle de alguma forma? Não está nos campos de credencial atuais, então assumo que é fixo em 500.

Por enquanto meu plano é controlar o fan-out no lado do pai em vez de confiar em Pool Max, já que reduzir Pool Max apenas desloca a falha de “DB sobrecarregado” para “pool queue rejeitado”. Fico feliz em ouvir se há uma abordagem mais limpa.

bem vindo à comunidade n8n @giovanni.tavares
n8n recomenda concorrência de 5 ou mais por worker; valores muito baixos com muitos workers podem esgotar o pool de conexões do banco. A fórmula aproximada de sessões máximas seria: número_de_workers × Pool Max Configuring queue mode | n8n Docs

segue outros docs que podem te apoiar

Obrigado @tamy.santos. A fórmula workers × Pool Max confirma que o pool é por worker, o que corresponde ao que @achamm disse.

O que ainda estou preso é que essa fórmula descreve o limite máximo de sessões estabelecidas em estado estável, mas minha falha é diferente. O NJS-076 é a fila de requisição de conexão (queueMax 500) preenchendo porque as requisições chegam mais rápido do que as conexões se liberam, não porque o limite de sessão seja excedido. A parte complicada é que reduzir Pool Max na verdade torna o NJS-076 mais provável (menos conexões para absorver o pico), enquanto aumentá-lo traz de volta o problema original de sobrecarregar o BD. Então não há um valor de Pool Max estável a menos que o próprio pico de requisições de conexão seja acelerado.

Esse pico vem do workflow pai despachando ~1000 execuções de sub-workflow com “Wait For Sub-Workflow Completion” desligado, e de acordo com a documentação o limite de concorrência de produção não se aplica a execuções de sub-workflow, então elas não são limitadas pela concorrência do worker.

Então meu plano é acelerar no lado pai em vez de ajustar Pool Max: ou manter “Wait For Sub-Workflow Completion” ligado, ou usar Loop Over Items / Split in Batches para despachar em lotes menores, ou agrupar registros para que menos sub-workflows sejam acionados.

Alguém tem um padrão recomendado para acelerar o despacho de sub-workflow em modo de fila, dado que o limite de concorrência não os cobre? Esse parece ser o verdadeiro ponto de alavancagem aqui.

@giovanni.tavares
qual a arquitetura do seu fluxo ?

Claro, aqui está a arquitetura:

Workflow pai

  • O trigger é executado e busca ~1000 registros da API.

  • Um nó Execute Workflow configurado para “executar uma vez para cada item” dispara uma execução de sub-workflow por registro, então ~1000 execuções de sub-workflow.

  • “Wait For Sub-Workflow Completion” está desativado, então o pai dispara todos sem esperar.

Sub-workflow (uma execução por registro)

  • Executa várias consultas Oracle contra o mesmo banco de dados. Elas são executadas sequencialmente em um fluxo linear, então uma única execução mantém no máximo uma conexão por vez. O número de consultas por execução não multiplica conexões concorrentes; o que impulsiona a concorrência é quantas execuções de sub-workflow rodam ao mesmo tempo.

Infraestrutura

  • Modo de fila, Redis/Bull, 3 contêineres de worker, N8N_WORKER_CONCURRENCY=10.

  • Nó nativo Oracle Database, versão 2.20.7-exp.0.

O problema é que as ~1000 execuções de sub-workflow não são limitadas pelo limite de concorrência (já que ele não se aplica a execuções de sub-workflow), então elas atingem o pool Oracle em rajadas e transbordam a fila de solicitações de conexão (queueMax 500), gerando NJS-076.

Isso parece ser mais um problema de limite de concorrência do que um problema de consulta Oracle. Com modo de fila e múltiplos workers, eu presumiria que cada worker pode criar seu próprio conjunto de sessões até prova em contrário, depois reduza a concorrência dos workers ou roteie o trabalho pesado do Oracle através de um subfluxo controlado. Eu teria cuidado com tentativas aqui porque elas podem piorar o pico de sessões. Você testou o mesmo fluxo de trabalho com um worker e baixa concorrência para confirmar que a contagem de sessões diminui?