Queue Mode, Task Runners e pattern di ottimizzazione per deployment mid-tier scalabili

Quando un'automazione dipartimentale inizia a gestire migliaia di esecuzioni giornaliere, l'architettura monolitica di n8n mostra rapidamente i suoi limiti. Passare da un deployment standard a un'infrastruttura distribuita basata su code richiede una comprensione precisa dei trade-off architetturali e dei costi operativi. Questo articolo analizza l'implementazione pratica di Task Runners, i pattern di ottimizzazione delle risorse e le metriche essenziali per il monitoraggio, fornendo benchmark realistici e indicazioni sui limiti effettivi dell'approccio.
In una configurazione n8n standard, un singolo processo Node.js gestisce l'interfaccia utente, i webhook in ingresso, l'esecuzione dei workflow e la persistenza su database. Questo modello funziona correttamente fino a circa 50-100 esecuzioni simultanee, dopo di che emergono problematiche prevedibili.
Questi non sono limiti teorici: sono vincoli che emergono costantemente in deployment con più di 500 esecuzioni giornaliere.
La modalità Queue di n8n introduce una separazione netta tra componenti di frontend e backend attraverso un sistema di code basato su Redis (utilizzando la libreria Bull).
1. Main Instance: container dedicato all’interfaccia web e alla gestione delle API. Riceve i trigger (webhook, polling schedulato) e inserisce i job nella coda Kafka senza eseguirli direttamente.
2. Kafka: broker di messaggistica che mantiene la coda dei lavori. Gestisce la distribuzione dei job ai worker disponibili secondo una logica FIFO/partitioned, garantendo persistenza e possibilità di replay dei messaggi.
3. Task Runners (Workers): container multipli che consumano i job dalla coda Kafka, eseguono i workflow e salvano i risultati nel database condiviso.
Questa separazione permette di scalare orizzontalmente solo il layer computazionale: se il carico aumenta, si aggiungono worker; se diminuisce, si riducono. Il Main rimane un singleton.
Nota importante sui webhook: in modalità queue, i webhook vengono ricevuti dal Main ma possono richiedere configurazioni specifiche per risposte sincrone. Per workflow che devono rispondere immediatamente al client HTTP, è necessario valutare se la modalità queue con Kafka sia appropriata o se convenga mantenere esecuzioni sincrone per quei flussi specifici.
Di seguito un esempio di configurazione Docker Compose per un ambiente distribuito con monitoraggio. Questa configurazione è adatta per deployment mid-tier su singolo host o cluster Docker Swarm.
version: '3.8'
services:
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=n8n_password
- POSTGRES_DB=n8n
volumes:
- db_data:/var/lib/postgresql/data
zookeeper:
image: wurstmeister/zookeeper:3.4.6
restart: always
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka:2.13-2.8.0
restart: always
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
depends_on:
- zookeeper
n8n-main:
image: docker.n8n.io/n8nio/n8n
restart: always
ports:
- "5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=n8n_password
- EXECUTIONS_MODE=queue
- QUEUE_KAFKA_BROKER=kafka:9092
- QUEUE_KAFKA_TOPIC=n8n
- WEBHOOK_URL=https://your-domain.com
- N8N_METRICS=true
- N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL=true
depends_on:
- postgres
- kafka
n8n-worker:
image: docker.n8n.io/n8nio/n8n
restart: always
command: worker
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=n8n_password
- EXECUTIONS_MODE=queue
- QUEUE_KAFKA_BROKER=kafka:9092
- QUEUE_KAFKA_TOPIC=n8n
- N8N_METRICS=true
- EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
- NODE_OPTIONS=--max-old-space-size=2048
depends_on:
- postgres
- kafka
prometheus:
image: prom/prometheus:latest
restart: always
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
restart: always
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin_password
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
volumes:
db_data:
prometheus_data:
grafana_data:
Note sulla configurazione:
Il file prometheus.yml deve essere configurato per raccogliere metriche da tutti i container n8n. Esempio di configurazione:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'n8n-main'
static_configs:
- targets: ['n8n-main:5678']
- job_name: 'n8n-workers'
static_configs:
- targets: ['n8n-worker:5678']
Nota: i worker espongono le metriche sulla stessa porta 5678 utilizzata dal Main per l'interfaccia web.
Per un monitoraggio efficace in ambiente di produzione, è necessario tracciare almeno queste metriche:
rate(n8n_executions_total{status="error"}[5m]) /
rate(n8n_executions_total[5m]) * 100
histogram_quantile(0.95, rate(n8n_workflow_executions_duration_seconds_bucket[5m]))
Oltre alla scalabilità infrastrutturale, è essenziale progettare i workflow seguendo pattern che riducono il consumo di risorse, come:
// Pseudocodice workflow n8n
HTTP Request → Get 100k records
Set Variable: batchSize = 1000
Loop Over Items:
- Take items[i*1000 : (i+1)*1000]
- Process batch
- Write to database
- Wait 100ms
La modalità distribuita introduce complessità operativa e non è sempre la soluzione ottimale:
Contesto: un'azienda e-commerce deve sincronizzare ogni notte 50.000 prodotti da un ERP esterno verso il proprio database. Ogni prodotto richiede una chiamata API per ottenere prezzi e disponibilità.
Problematica con deployment monolitico: il singolo processo Node.js impiega 6-8 ore per completare il lavoro, saturando la RAM e bloccando altre automazioni.
Risultato: tempo di completamento ridotto a 90 minuti (parallelizzazione 5x), utilizzo RAM stabile sotto i 500MB per worker, zero downtime per altre automazioni.
Costi aggiuntivi: istanza Redis (~20€/mese su managed service), 5 worker containers (equivalenti a ~2 vCPU aggiuntive, ~40€/mese), Prometheus + Grafana (~15€/mese su infra esistente). Totale incremento mensile: ~75€.
La configurazione Docker Compose presentata è adatta per ambienti mid-tier su singolo host o piccoli cluster Docker Swarm, ma presenta limiti evidenti per deployment enterprise:
Per un deployment veramente enterprise-grade è necessario migrare a Kubernetes con:
Adottare Queue Mode in n8n rappresenta un upgrade architetturale significativo, ma non è una soluzione universale. È la risposta corretta quando il carico di lavoro supera costantemente le capacità di un singolo processo Node.js e quando il team ha le competenze per gestire l'infrastruttura distribuita.
L'implementazione richiede investimenti in termini di tempo (configurazione, monitoraggio, debugging) e costi mensili aggiuntivi (Redis, database scalabile, orchestrazione). I benefici — scalabilità orizzontale, resilienza, parallelizzazione — si manifestano chiaramente solo oltre una certa soglia di throughput.
Prima di implementare questa architettura, è essenziale misurare il carico attuale, identificare i colli di bottiglia reali (sono davvero le esecuzioni simultanee? o piuttosto query database mal ottimizzate?) e valutare se ottimizzazioni applicative (batching, caching, riduzione dei dati salvati) possano risolvere il problema a costi inferiori.
La scalabilità non è mai gratuita: ogni livello di complessità aggiunto deve essere giustificato da metriche concrete e da una chiara comprensione dei trade-off operativi.