O que o workflow faz
Um workflow principal se divide em 5 ramificações paralelas. Cada ramificação chama um sub-workflow, que por sua vez chama um sub-workflow de nível inferior por item. O nível mais baixo é puramente lógica de decisão — nós IF e nós Set, nenhuma chamada HTTP externa, nenhuma consulta ao banco de dados. O volume total de itens por execução completa está na faixa de 10.000+ execuções em todas as ramificações.
O sintoma
No Histórico de Execução, as execuções parecem ficar presas no estado “em execução” por até 24 horas. Mas quando finalmente são concluídas, a duração de execução registrada mostra ~200ms. Então o trabalho real leva 200ms — os outros 23h 59m 59s é tempo gasto esperando em algum lugar no n8n antes ou depois da execução.
Isso nos diz que a própria lógica é rápida. O gargalo não é o código do workflow.
O que achamos que está acontecendo
Com 10.000+ execuções de sub-workflow, o n8n está escrevendo um registro de execução no PostgreSQL para cada uma delas. No modo normal (não-fila) essas gravações são serializadas através do mesmo event loop do Node.js. Quando o registro “iniciado” ou “concluído” de uma execução posterior finalmente é persistido, o tempo real avançou bastante — portanto os timestamps do histórico de execução não refletem mais o tempo real de parede.
A figura de 24h não é tempo de processamento real. É lag de gravação em BD.
Perguntas
Configurações de salvamento de execução — EXECUTIONS_DATA_SAVE_ON_SUCCESS=none (ou a configuração equivalente em Configurações → Workflows) é a alavanca certa aqui? Desabilitar salvas de execução bem-sucedidas nos sub-workflows eliminaria completamente a fila de gravação em BD?
Salvamento de execução por workflow — Você pode desabilitar a persistência de execução em workflows individuais em vez de globalmente? Nosso sub-workflow de nível mais baixo é disparado milhares de vezes por execução e não precisamos que seu histórico seja salvo — apenas nos importa o resultado de nível superior.
Ajuste de PostgreSQL — Existem configurações específicas de Postgres (tamanho do pool de conexões, ajuste de autovacuum, configurações de write-ahead log) que outros usaram neste volume?
Limite de concorrência do modo normal — Em que ponto o modo normal se serializa completamente? Vimos alguma execução concorrente para N pequeno, mas serialização completa em torno de N=200+. Existe um limiar documentado ou configuração n8n para isso?
Trade-off do modo de fila — Entendemos que o modo de fila + um nó worker dedicado ajudaria. Mas há algo significativo que possamos extrair do modo normal primeiro antes de seguir esse caminho?
Qual é a mensagem de erro (se houver)?
Por favor, compartilhe seu workflow
(Selecione os nós na sua tela e use os atalhos de teclado CMD+C/CTRL+C e CMD+V/CTRL+V para copiar e colar o workflow.)
Para resolver o gargalo, desabilite salvamentos de execução bem-sucedidos globalmente com EXECUTIONS_DATA_SAVE_ON_SUCCESS=none e por workflow usando “Save” → None nas Configurações de Workflow, e também desative “Save execution progress” no nível do workflow. Configurar isso por workflow significa que seus sub-workflows de baixo nível podem ser executados milhares de vezes sem criar registros no banco de dados, mantendo ainda a visibilidade do sucesso/falha do workflow pai.
Para ajuste de PostgreSQL, aumente o tamanho do pool de conexões (por exemplo, DB_POSTGRESDB_POOL_SIZE=10 em vez do padrão 2) e configure timeouts apropriadamente. Controle a concorrência em modo normal globalmente com N8N_CONCURRENCY_PRODUCTION_LIMIT (defina como -1 para desabilitar). Você suspeitou de um ponto de serialização em torno de N=200, provavelmente devido ao alcance dos limites de escrita do PostgreSQL ao usar o tamanho padrão baixo do pool de conexões.
O modo de fila seria a solução mais limpa porque escreve o timestamp startedAt na fila imediatamente sem bloquear. No entanto, antes de seguir essa rota, aplique primeiro as alterações de configuração no nível do workflow e globais—se isso resolver o atraso de timestamp de 24 horas, você pode não precisar da complexidade extra do modo de fila. Se essas otimizações em modo normal não ajudarem, o modo de fila com workers dedicados separará verdadeiramente o monitoramento de execução do processamento.
Aquele atraso de 24 horas é de fato causado por gargalos de serialização do PostgreSQL ao escrever 10.000+ registros de execução individuais sincronamente no banco de dados a cada iteração de sub-workflow. Você pode eliminar essa sobrecarga definindo sua instância para modo de fila com Redis e desabilitando salvamentos de execução para estados bem-sucedidos nesses workflows filho via variáveis de ambiente ou configurações de workflow. Me avise se você precisar das variáveis de ambiente exatas ou ajustes no Docker compose para dividir suas filas de worker e posso compartilhar a configuração aqui mesmo.
Obrigado! Seria muito útil se você pudesse compartilhar as variáveis de ambiente exatas e a configuração do Docker Compose.
Também, tenho uma pergunta sobre workers. Quando dizemos um worker, queremos dizer um processo separado rodando na mesma máquina que a instância principal do n8n, ou uma máquina completamente separada? Minha preocupação é que se estamos apenas executando vários processos de worker na mesma máquina, ainda podemos esbarrar nos mesmos gargalos de CPU e recursos, então estou me perguntando quanto de melhoria de desempenho isso realmente proporcionaria.
Essa é uma pergunta fantástica sobre workers, e toca na arquitetura fundamental do Node.js. Mesma Máquina vs. Máquina Separada (A Restrição Single-Thread do Node.js)
Um worker pode ser qualquer um dos dois, mas começar com múltiplos workers na mesma máquina geralmente oferece um aumento de throughput imediato de 3x–5x. Aqui está o porquê:
Node.js é single-threaded: Um único processo n8n padrão só consegue utilizar 1 núcleo de CPU, não importa o quão potente seja seu VPS ou servidor bare-metal. Se você tiver uma CPU com 8 núcleos, 7 desses núcleos ficarão praticamente ociosos enquanto seu único processo se afoga na serialização de execução.
Multi-Worker no Mesmo Host: Executar 3–4 containers de worker no mesmo host inicia runtimes Node.js independentes. Isso permite que o n8n sature todos os núcleos de CPU em paralelo.
Quando migrar para máquinas separadas (Escalamento Horizontal): Você só precisa de VMs separadas quando a RAM inteira do host ou o CPU agregado se aproximar de 75–80%. Como Redis e PostgreSQL desacoplam a execução do estado, workers em máquinas separadas simplesmente apontam para seu IP centralizado do Redis/Postgres.
A Configuração Docker Compose para Produção (Modo Fila)
Aqui está um padrão docker-compose.yml testado em batalha com Modo Fila Redis e Offloading de Banco de Execução configurados:
Duas Notas Críticas para seus Workflows Fan-Out:
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none: Essa única variável impede que o n8n realize uma escrita pesada de todo o payload JSON na tabela execution_entity após a conclusão. Se você está fazendo fan-out de 10.000 sub-workflows, isso sozinho remove milhões de IOPS desnecessárias do PostgreSQL. (Erros ainda serão registrados para que você possa solucionar problemas de falhas).
Sobrescrita de Configurações de Workflow: Você também pode alternar isso por workflow: abra seu sub-workflow filho → Workflow Settings (ícone de engrenagem no canto superior direito) → defina Save Successful Production Executions como No.
Teste essa configuração de compose com replicas: 2 ou 3 e me avise se sua fila de execução drena imediatamente!
Oi @Raviraj_kale, eu testaria primeiro reduzindo o número de sub-execuções. Em uma cópia, envie os mesmos 100 itens para o IF/Set child usando Mode → Run once with all items, verifique as saídas e compare o tempo de wall-clock do pai com a versão por item.
Algumas correções no conselho acima:
Você pode desabilitar Save successful production executions e Save execution progress apenas nos filhos, mantendo o histórico do pai. EXECUTIONS_DATA_SAVE_ON_SUCCESS=none controla dados salvos; não elimina a criação de execução ou todas as escritas do banco de dados.
N8N_CONCURRENCY_PRODUCTION_LIMIT não se aplica a execuções de sub-workflow. Não há um limite de serialização documentado em 200; também verifique Execution order, porque v1 completa ramificações de canvas por vez.
Workers são processos separados, no mesmo EC2 ou hosts separados. Chamadas aninhadas padrão permanecem no processo do pai, então workers extras não distribuirão automaticamente um fan-out ou garantirão uma melhoria de 3x–5x.
Para PostgreSQL, capture waits de pg_stat_activity e CPU/I/O durante o mesmo teste antes de alterar configurações de pool/WAL/autovacuum. Orce conexões de banco de dados entre o processo principal e cada worker.
Para a discrepância de 24h/200ms, você poderia compartilhar sua versão exata do n8n, configurações do Execute Sub-workflow e createdAt, startedAt, stoppedAt de um par pai/filho, junto com os tempos de início/fim observados?
Caso interessante. Eu separaria o tempo real de execução do fluxo de trabalho do tempo de persistência do estado de execução antes de concluir que PostgreSQL é o gargalo.
Uma área que vale a pena medir é quanto dado de execução cada sub-fluxo de trabalho aninhado gera e com que frequência esses registros são escritos.
Em meus próprios testes de confiabilidade de webhook, me concentrei em requisições concorrentes, idempotência com suporte a banco de dados e prevenção de duplicatas. Isso reforçou a importância de medir o comportamento do banco de dados sob concorrência em vez de olhar apenas para o tempo de execução do nó.
Também compararia o comportamento com o salvamento de execução bem-sucedida desabilitado para a carga de trabalho relevante, se a configuração de implantação permitir, mantendo os dados de execução de nível superior necessários para depuração.
O modo de fila pode ajudar com a distribuição de carga de trabalho, mas eu primeiro verificaria se o atraso está no agendamento de execução, persistência de banco de dados ou atualizações de status na interface.
Discrepâncias massivas no tempo de execução em fan-outs de sub-workflows geralmente indicam gargalos de serialização de fila ou contenção de locks de banco de dados no PostgreSQL. Quando centenas de execuções filhas tentam gravar seus estados de conclusão simultaneamente, os registros de execução travam em estados pendentes.
Definir EXECUTIONS_DATA_PRUNE=true e ajustar o tamanho do pool de conexões do PostgreSQL geralmente elimina o atraso artificial na duração da execução imediatamente.
Muito provavelmente é o salvamento da execução, mais cada sub-workflow competindo pelo único processo. Desative o salvamento de execuções bem-sucedidas e o progresso nos sub-workflows, e limpe dados antigos agressivamente. Melhor ainda, agrupe itens em uma única chamada de sub-workflow usando Split In Batches em vez de uma chamada por item. Para esse volume, o modo de fila com workers é o conserto adequado.
@Raviraj_kale um worker é apenas outro processo n8n iniciado com n8n worker. Ele pode rodar na mesma máquina ou em uma diferente, só precisa alcançar o mesmo Postgres e Redis e usar a mesma N8N_ENCRYPTION_KEY da instância principal.
Em uma máquina, workers extras só ajudam se você tiver núcleos de CPU ociosos. Cada processo n8n é Node.js single-threaded, então agora sua instância principal está fazendo tudo em aproximadamente um núcleo. Dois ou três workers em uma VM de 4 núcleos usarão os outros núcleos. Se a máquina já está no máximo, mais workers nela não ajudará e você precisa de outra máquina.
Uma coisa que importa muito para sua configuração: em queue mode, uma chamada Execute Workflow que espera pela conclusão executa o sub-workflow dentro do mesmo worker que o pai. Não se espalha entre workers. Então suas 10k execuções filhas ainda caem em qualquer worker que pegou a execução pai.
O maior ganho geralmente é reduzir o número de execuções. No nó Execute Workflow que chama o sub-workflow de nível mais baixo, defina Mode como “Run once with all items” em vez de “Run once for each item”, e deixe esse sub-workflow lidar com o lote inteiro. Nós IF e Set funcionam da mesma forma em 1 item ou 10.000, e você vai de milhares de registros de execução para um punhado. Inserir essa lógica de decisão em seu pai também funciona.
Postgres é deixado de fora já que você já o executa. Comece com docker compose up -d --scale n8n-worker=2.
Duas pegadinhas. Se sua instância atual nunca teve N8N_ENCRYPTION_KEY definida, copie a chave de /home/node/.n8n/config para o env primeiro, senão os workers não conseguem descriptografar suas credenciais. Também mantenha principal e workers na mesma versão de imagem exata.