Hey everyone
I’m scaling a multi-tenant n8n platform and starting to realize that basic logs are no longer enough.
Current architecture:Load Balancer
↓
Multiple n8n Workers
↓
PostgreSQL + Redis + External APIs
As the number of tenants and workflows grows, I’m finding it difficult to answer questions like:
• Which tenant is generating the most load?
• Why did a specific workflow fail?
• Where are the bottlenecks in the system?
• Which external APIs are causing latency?
• How do I detect problems before customers notice?
I’m considering adding:
• Centralized logging
• Metrics collection
• Distributed tracing
• Per-tenant dashboards
• Alerting and anomaly detection
Example metrics:tenant_id
workflow_id
execution_time
error_rate
queue_depth
API_latency
What observability stack are you using?
Which metrics have been the most valuable?
Describe the problem/error/question
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.)
Hey @Greg_John As your platform grows, it becomes harder to understand what’s happening just by looking at logs. That’s why having good observability is so important.
A typical production setup includes:
Centralized logging
Metrics collection
Alerts
Tracing (if needed)
The most useful things to monitor are:
• tenant_id
• workflow_id
• Execution time
• Error rate
• Queue depth
• External API response time
Adding tenant_id and workflow_id to your logs and metrics makes it much easier to find and troubleshoot issues for a specific customer or workflow.
It’s also a good idea to set up alerts for:
High error rates
Queue backlogs
Worker failures
Slow external APIs
Unexpected spikes in traffic
One mistake to avoid is relying only on application logs or only monitoring your infrastructure. You need visibility into both your workflows and the systems running them.
In short, centralizing your logs and metrics, tagging them properly, and setting up dashboards and alerts will help you spot problems early and keep your platform running smoothly as it grows.
Hi @Greg_John
n8n ships its own Prometheus endpoint, so the collection layer is a config change, not something you build. Set this on the mains and the workers:
Queue depth comes from n8n_scaling_mode_queue_jobs_waiting and n8n_scaling_mode_queue_jobs_active. n8n reads those from Bull and exposes them on the mains only, so point the scrape job at the mains for queue state and at the workers for execution and node timings. Workflow ID is the finest label n8n emits, there is no tenant dimension, so do the workflow-to-tenant join in Prometheus relabeling or a recording rule and build the tenant views on top of that series. Keep /metrics on the internal network, it exposes operational detail about the instance.
Thanks, this is really helpful. I like the point about tagging everything with tenant_id and workflow_id—that would make troubleshooting much easier in a multi-tenant setup. I also agree that monitoring workflows is just as important as monitoring the infrastructure. Appreciate you sharing this.
Thanks for the insight! That’s a useful perspective and gives me a few ideas to improve my setup. I’ll look into that approach and see how it fits my architecture.
Most of this thread is generic observability advice. The n8n-specific parts are where the answers to your five questions actually live, so:
Queue depth and per-tenant load are already exposed — you don’t have to build them. n8n ships a Prometheus endpoint that’s off by default: N8N_METRICS=true gives you /metrics on the main. The flags that matter for your case are N8N_METRICS_INCLUDE_QUEUE_METRICS (that’s your queue_depth), N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL and N8N_METRICS_INCLUDE_NODE_TYPE_LABEL (per-workflow and per-node-type series, which is how “which tenant is generating the most load” and “which external API is slow” become one PromQL query instead of a logging project). Scrape it into whatever you already run. Watch cardinality if you have a lot of workflows — the workflow-id label is what makes it useful and also what makes it expensive.
Your error_rate metric will lie to you, and this is the one that bites at multi-tenant scale. An n8n execution finishes with status success in plenty of cases where nothing actually happened: a node that returns zero items just passes zero items downstream and everything after it silently no-ops; an IF with no matching branch; the done output of a Split In Batches that nobody wired up. The execution is green, the error rate stays flat, and the tenant’s data simply didn’t move. So don’t only monitor for errors — assert on outcomes. Emit an item count at the end of each tenant workflow and alert on processed == 0 when zero is not a legitimate result. Silent success is the failure mode that reaches your customers before it reaches your dashboard.
“Detect problems before customers notice” needs a dead-man’s switch, not an alert. The Error Trigger / Error Workflow is the right per-tenant alerting hook — set it per workflow, and put enough in the payload to be actionable ($execution.id, the workflow name, the failing node, and the last node’s data; an alert that just says “workflow failed” costs you a debugging session every single time). But note what it structurally cannot do: the Error Trigger never fires for an execution that never started. A wedged schedule trigger, a stuck worker, a workflow someone deactivated — all of them produce silence, and silence looks exactly like “everything is fine.” The fix is inverted: have each tenant workflow ping a watchdog on completion, and alert when the ping is missing. That one check catches the whole class of failure your logs cannot see by construction.
Your bottleneck is probably execution_entity. At multi-tenant volume the execution data table is what makes Postgres slow, and it grows quietly. EXECUTIONS_DATA_PRUNE=true with a real EXECUTIONS_DATA_MAX_AGE and EXECUTIONS_DATA_PRUNE_MAX_COUNT, and for high-volume tenants consider EXECUTIONS_DATA_SAVE_ON_SUCCESS=none — keeping full success payloads for every run is a large cost for data you will never open. Prune before you optimise queries; a lot of “n8n is slow” turns out to be this.
One thing worth deciding early since you’re multi-tenant: that execution table holds your tenants’ actual payloads. If any of them are in the EU, the execution DB is a processing location, and retention there is a compliance question, not just a disk one. Much cheaper to set the policy now than to explain it later.