Hi everyone,
I’m running a multi-tenant automation platform on top of n8n and have started seeing issues where a few high-volume tenants impact the experience of everyone else.
Current architecture:
Shared Queue
↓
Shared Worker Pool
↓
Shared Infrastructure
Problems I’m observing:
• A single tenant can flood the queue
• Long-running workflows delay smaller jobs
• API-heavy tenants consume disproportionate worker capacity
• Increased latency for low-volume tenants
My goals are:
• Fair resource allocation across tenants
• Prevent noisy-neighbor issues
• Maintain predictable performance
• Scale without overprovisioning infrastructure
I’m considering:
• Per-tenant queues
• Priority queues
• Worker pools by tenant tier
• Rate limiting and quotas
• Dedicated infrastructure for enterprise tenants
For teams operating multi-tenant workflow platforms at scale:
How are you preventing noisy-neighbor problems?
What is the error message (if any)?
Please share your workflow
(Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.)
Hi @Keira_Becky The most common approach is to combine rate limits, queue isolation, and worker isolation.
Shared Queue + Per-Tenant Limits + Dedicated Workers for Heavy Tenants
This prevents a single tenant from consuming all available resources.
Common strategies
Apply per-tenant rate limits and quotas
Use priority queues for different service tiers
Route high-volume tenants to dedicated queues or workers
Keep smaller tenants on shared infrastructure
For large scale tenants many teams use: Enterprise Tenant → Dedicated Queue → Dedicated Worker Pool
Make sure to Avoid
One global queue with no limits
Treating all tenants equally regardless of usage
Allowing unlimited retries or long-running jobs on shared workers
The cleanest fix for noisy-neighbor is to stop sharing the thing tenants fight over. Instead of one shared queue and worker pool with limits bolted on, run one n8n instance per tenant: its own queue, its own workers, its own database and encryption key, its own container. Noisy-neighbor stops being something you tune and becomes structurally impossible, because there’s no shared queue for a tenant to flood and no shared pool for an API-heavy tenant to starve.
The shared-queue-plus-limits route (per-tenant rate limits, priority tiers, dedicated workers for the heavy few) does work, and it’s the right call if you’re locked into one big instance. But you’re signing up to forever tune rate limits, priority weights, retry caps, and concurrency, and one mis-set value or one pathological workflow still bleeds across tenants. Fair scheduling on a shared pool is a moving target you never stop chasing.
The honest trade-off with instance-per-tenant is the reverse: near-zero blast radius, but more baseline infra sitting idle and more things to operate. N instances to deploy, back up, monitor, and upgrade. That operational cost is the real objection, and it’s the solvable one:
Cap each container. CPU and memory limits per instance (compose or k8s) so even a runaway workflow is bounded to its own tenant, not the host.
Isolate execution too. Run Code nodes in an external task-runner sidecar per instance, so JS/Python execution is contained, not just the queue.
Tier it. You don’t need one instance per tenant on day one. Small tenants can share an instance; give heavy and enterprise tenants their own. Even the shared tier is healthier sharded across a few instances than one global queue.
Put a control plane over the fleet. Past a handful of instances you need one place to see errors, health, and executions across all of them, and to deploy and back up without SSHing box to box.
The most direct lever in n8n queue mode is EXECUTIONS_CONCURRENCY_ACTIVE - set it per worker container, not globally. For the noisy-neighbor problem, the pattern that works in practice is: run 2-3 worker tiers with different concurrency caps, then route heavy tenants to their own worker group using a separate Redis queue key prefix (set via QUEUE_BULL_PREFIX per worker). Heavy-volume tenants get a dedicated worker with lower EXECUTIONS_CONCURRENCY_ACTIVE (say 5), the shared tier gets more workers but each capped at 10. Long-running workflows are the harder case - use EXECUTIONS_TIMEOUT at the instance level and EXECUTIONS_TIMEOUT_MAX per workflow to prevent any single execution from blocking a worker indefinitely. If two or three tenants consistently max out their own queue, upgrading them to a dedicated n8n instance is usually the cleaner path than trying to isolate them inside one shared cluster.
Same shape hit us on a multi-tenant n8n+Postgres platform earlier this year — LLM calls outliving Redis TTLs, one slow tenant blocking everyone.
The thing that actually stopped the bleeding: BullMQ queue prefixes per tenant tier (QUEUE_BULL_PREFIX) + separate EXECUTIONS_CONCURRENCY_ACTIVE ceilings per worker pool. Redis lock is the fast path. Then Postgres UNIQUE (tenant_id, message_id) with ON CONFLICT DO NOTHING as the durable idempotency gate — that’s the truth layer when Gemini calls outlive the Redis TTL.
Worth checking on your end: is your Redis lock TTL longer than your P99 LLM call? That’s usually where the duplicate-execution bleeding starts.
Also — if p95 LLM stays under 2s and pool_wait is flat, the contention is somewhere else. Usually QUEUE_RECOVERY_INTERVAL churn on the scheduler.