<figure class="wp-block-image aligncenter"><img src="https://flowgenius.in/wp-content/uploads/2026/01/why-n8n-execution-time-increases-over-time.png" alt="Step by Step Guide to solve why n8n execution time increases over time" /><figcaption style="text-align: center;">Step by Step Guide to solve why n8n execution time increases over time</p>
<hr />
</figcaption></figure>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Who this is for:</strong> n8n maintainers and DevOps engineers who see a single workflow start fast and then drift to > 30 seconds per run. We cover this in detail in the <a href="https://flowgenius.in/n8n-degradation-and-stability-issues/">n8n Performance Degradation & Stability Issues Guide.</a></p>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">Quick Diagnosis</h2>
<ol style="margin-bottom: 1.8em; line-height: 1.9;">
<li>Open the workflow’s <strong>Execution History</strong> → <strong>Duration</strong> chart.</li>
<li>Enable <strong>“Clear Execution Data”</strong> after each run (or set a retention limit).</li>
<li>Insert a <strong>Set</strong> node to truncate large arrays/objects before downstream processing.</li>
</ol>
<p style="margin-bottom: 2em; line-height: 1.9;">If the upward slope stops, you’ve likely solved the problem.</p>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">1. Execution‑Time Anatomy</h2>
<p>If you encounter any <a href="/n8n-workflows-slow-after-weeks-in-production-root-cause-analysis">n8n workflows slow after weeks in production root cause analysis </a>resolve them before continuing with the setup.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Phase</th>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Typical Time (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><strong>Trigger</strong> – receives event (HTTP, Cron, Webhook)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">5‑20</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;"><strong>Node Processing</strong> – runs internal logic</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">10‑200 per node</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Data Serialization – stores intermediate JSON</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">5‑30</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Execution Persistence – writes full execution record</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">10‑100</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Cleanup – garbage collection & DB cleanup</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">5‑15</td>
</tr>
</tbody>
</table>
<blockquote style="border-left: 4px solid #e0e0e0; padding-left: 1em; margin: 1.5em 0; font-style: italic;">
<p style="margin-bottom: 0; line-height: 1.9;"><strong>EEFA note</strong> – n8n persists <em>every</em> execution in the DB. An ever‑growing <code>execution_entity</code> table makes each INSERT/SELECT slower, eventually inflating total duration.</p>
</blockquote>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">2. Real‑World Root Causes</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Root Cause</th>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Symptom</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Unbounded Execution History</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Duration chart climbs, DB size ↑</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Large Payloads / Untrimmed Data</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">“Set” or “Function” nodes log huge objects</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Memory Leaks in Custom Functions</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Execution spikes after dozens of runs</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Inefficient Looping in “Function” Nodes</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">2‑3 × slowdown after 10 runs</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">External API Rate‑Limiting / Retries</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Random spikes, then steady rise</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Improper Use of “Wait” / “Delay” Nodes</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Fixed 5 s delay becomes 30 s due to backlog</td>
</tr>
</tbody>
</table>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">3. Investigation Checklist</h2>
<p>If you encounter any <a href="/n8n-slows-down-even-with-low-cpu-usage">n8n slows down even with low cpu usage </a>resolve them before continuing with the setup.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.1 Capture Baseline</h3>
<p style="margin-bottom: 2em; line-height: 1.9;">Export the last 10 executions (CSV) and note <code>duration_ms</code>.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.2 Inspect DB Size</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;"># PostgreSQL example
SELECT pg_total_relation_size('public.execution_entity') AS size_bytes;
</pre>
<blockquote style="border-left: 4px solid #e0e0e0; padding-left: 1em; margin: 1.5em 0; font-style: italic;">
<p style="margin-bottom: 0; line-height: 1.9;"><strong>Tip</strong> – > 500 MB on modest traffic signals history bloat.</p>
</blockquote>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.3 Identify Bloated Nodes</h3>
<p style="margin-bottom: 2em; line-height: 1.9;">Open the CSV, sort by <code>node_execution_time</code>, and flag the top offenders.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.4 Log Payload Size</h3>
<p style="margin-bottom: 2em; line-height: 1.9;">Add a temporary <strong>Function</strong> node <strong>before</strong> the heavy node:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">// Log payload size in bytes
const size = Buffer.byteLength(JSON.stringify($json), 'utf8');
console.log(`Payload size: ${size} bytes`);
return $json;
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">If size exceeds ~50 KB, consider pruning.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.5 Monitor V8 GC Pauses (Docker)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">docker run -e NODE_OPTIONS="--trace-gc" n8nio/n8n
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Look for <code>Mark‑Compact</code> pauses > 50 ms.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">3.6 Review External Calls</h3>
<p style="margin-bottom: 2em; line-height: 1.9;">Check the <strong>HTTP Request</strong> node’s “Response Time” field; compare against expected API latency.</p>
<h4 style="margin-bottom: 45px; line-height: 1.3;">Checklist Summary</h4>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Done</th>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">☐</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Baseline captured – Export recent execution durations</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">☐</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">DB size checked – Keep < 200 MB for low‑traffic setups</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">☐</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Bloated node identified – Pinpoint node(s) with highest runtime</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">☐</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Payload size logged – Verify JSON < 50 KB before heavy processing</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">☐</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">GC pauses inspected – No > 30 ms V8 pauses</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">☐</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">API latency verified – No > 2 × expected response time</td>
</tr>
</tbody>
</table>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">4. Proven Fixes & Configuration Tweaks</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.1 Trim Execution History</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;"># n8n environment variables
EXECUTIONS_PROCESS=main
EXECUTIONS_DATA_SAVE_MAX_DAYS=7 # keep 7 days of data
EXECUTIONS_DATA_SAVE_MAX_EXECUTIONS=1000 # cap total rows
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Why it works</strong> – Limits INSERT volume, keeping index scans fast.<br />
<strong>EEFA warning</strong> – <code>MAX_DAYS=0</code> disables history entirely; you lose audit trails.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.2 Prune Data with “Set” / “Remove” Nodes</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">{
"type": "n8n-nodes-base.set",
"parameters": {
"keepOnlySet": [
"id",
"status",
"timestamp"
]
}
}
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Only essential fields survive downstream, cutting serialization time by up to 70 %.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.3 Refactor Heavy “Function” Logic</h3>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Before (O(N²) nested loops)</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">let result = [];
for (let i = 0; i < items.length; i++) {
for (let j = 0; j < items.length; j++) {
if (items[i].value === items[j].value) result.push(items[i]);
}
}
return result;
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>After (Hash‑Map O(N))</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">const map = new Map();
items.forEach(item => map.set(item.id, item));
return Array.from(map.values());
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Typical reduction: 200 ms → 15 ms on 10 k rows.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.4 Batch External Calls</h3>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Step 1 – Aggregate IDs (Function node)</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">// Collect IDs for a single API call
const ids = items.map(i => i.json.id);
return [{ json: { ids } }];
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Step 2 – HTTP Request node</strong> – send <code>ids</code> as a JSON array in the body.</p>
<p style="margin-bottom: 2em; line-height: 1.9;">Benefit – Cuts network round‑trips, reducing overall latency by 30‑50 %.</p>
<h3 style="margin-bottom: 45px; line-height: 1.3;">4.5 Enable Node‑Level Caching (n8n ≥ 1.2)</h3>
<p style="margin-bottom: 2em; line-height: 1.9;">In the node’s **Advanced** tab:</p>
<ul style="margin-bottom: 1.8em; line-height: 1.9;">
<li>Cache: <code>true</code></li>
<li>Cache TTL (seconds): <code>300</code></li>
</ul>
<p style="margin-bottom: 2em; line-height: 1.9;">Use for idempotent API calls where data changes rarely.</p>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">5. Ongoing Monitoring & Alerting</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 1.8em;">
<thead>
<tr>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Metric</th>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Target</th>
<th style="border: 1px solid #e0e0e0; padding: 13px; text-align: left;">Alert Threshold</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">Avg Execution Duration (last 100 runs)</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">≤ 500 ms</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">> 1 s → Slack/Email</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">DB Size</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">≤ 200 MB</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">> 300 MB → PagerDuty</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">GC Pause ></td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">30 ms</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">> 50 ms → Ops dashboard</td>
</tr>
<tr>
<td style="border: 1px solid #e0e0e0; padding: 13px;">External API Latency</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">≤ 2 s</td>
<td style="border: 1px solid #e0e0e0; padding: 13px;">> 5 s → Incident</td>
</tr>
</tbody>
</table>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Prometheus Export (n8n built‑in metrics)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">scrape_configs:
- job_name: 'n8n'
static_configs:
- targets: ['localhost:5678']
metrics_path: '/metrics'
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Key metrics: <code>n8n_execution_duration_seconds</code>, <code>n8n_db_size_bytes</code>.</p>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">6. Production Checklist</h2>
<ul style="margin-bottom: 1.8em; line-height: 1.9;">
<li>Set <code>EXECUTIONS_DATA_SAVE_MAX_DAYS</code> / <code>MAX_EXECUTIONS</code>.</li>
<li>Add “Set” nodes to prune payloads > 50 KB.</li>
<li>Refactor custom JavaScript to linear/sub‑linear complexity.</li>
<li>Batch external API calls wherever possible.</li>
<li>Enable node‑level caching for read‑only lookups.</li>
<li>Schedule a nightly DB vacuum (PostgreSQL) or <code>VACUUM FULL</code>.</li>
<li>Monitor the four key metrics above and configure alerts.</li>
</ul>
<hr style="margin: 50px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">Conclusion</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">Execution‑time bloat in n8n is almost always traceable to <strong>unbounded history</strong>, <strong>oversized payloads</strong>, or <strong>inefficient custom logic</strong>. By capping execution retention, pruning data early, refactoring heavy loops, batching external calls, and enabling node‑level caching, you halt the upward drift and keep automations snappy at scale. Continuous monitoring of duration, DB size, GC pauses, and API latency ensures the fix remains effective in production.</p>
Step by Step Guide to solve why n8n execution time increases over time
Who this is for: n8n maintainers and DevOps engineers who see a single workflow start fast and then drift to > 30 seconds per run. We cover this in detail in the n8n Performance Degradation & Stability Issues Guide.
Quick Diagnosis
Open the workflow’s Execution History → Duration chart.
Enable “Clear Execution Data” after each run (or set a retention limit).
Insert a Set node to truncate large arrays/objects before downstream processing.
If the upward slope stops, you’ve likely solved the problem.
Execution Persistence – writes full execution record
10‑100
Cleanup – garbage collection & DB cleanup
5‑15
EEFA note – n8n persists every execution in the DB. An ever‑growing execution_entity table makes each INSERT/SELECT slower, eventually inflating total duration.
DB size checked – Keep < 200 MB for low‑traffic setups
☐
Bloated node identified – Pinpoint node(s) with highest runtime
☐
Payload size logged – Verify JSON < 50 KB before heavy processing
☐
GC pauses inspected – No > 30 ms V8 pauses
☐
API latency verified – No > 2 × expected response time
4. Proven Fixes & Configuration Tweaks
4.1 Trim Execution History
# n8n environment variables
EXECUTIONS_PROCESS=main
EXECUTIONS_DATA_SAVE_MAX_DAYS=7 # keep 7 days of data
EXECUTIONS_DATA_SAVE_MAX_EXECUTIONS=1000 # cap total rows
Why it works – Limits INSERT volume, keeping index scans fast. EEFA warning – MAX_DAYS=0 disables history entirely; you lose audit trails.
Only essential fields survive downstream, cutting serialization time by up to 70 %.
4.3 Refactor Heavy “Function” Logic
Before (O(N²) nested loops)
let result = [];
for (let i = 0; i < items.length; i++) {
for (let j = 0; j < items.length; j++) {
if (items[i].value === items[j].value) result.push(items[i]);
}
}
return result;
Set EXECUTIONS_DATA_SAVE_MAX_DAYS / MAX_EXECUTIONS.
Add “Set” nodes to prune payloads > 50 KB.
Refactor custom JavaScript to linear/sub‑linear complexity.
Batch external API calls wherever possible.
Enable node‑level caching for read‑only lookups.
Schedule a nightly DB vacuum (PostgreSQL) or VACUUM FULL.
Monitor the four key metrics above and configure alerts.
Conclusion
Execution‑time bloat in n8n is almost always traceable to unbounded history, oversized payloads, or inefficient custom logic. By capping execution retention, pruning data early, refactoring heavy loops, batching external calls, and enabling node‑level caching, you halt the upward drift and keep automations snappy at scale. Continuous monitoring of duration, DB size, GC pauses, and API latency ensures the fix remains effective in production.