<figure class="wp-block-image aligncenter"><img src="https://flowgenius.in/wp-content/uploads/2026/01/n8n-execution-history-time-bomb.png" alt="Step by Step Guide to solve n8n execution history time bomb" /> <figcaption style="text-align: center;">Step by Step Guide to solve n8n execution history time bomb</p>
<hr />
</figcaption></figure>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Who this is for: </strong>Ops engineers, platform admins, and developers who run n8n in production and need reliable performance. <em>If you’ve been running n8n for a month or more, the execution table can start to swell without you noticing. <strong>We cover this in detail in the </strong>n8n Production Readiness & Scalability Risks Guide.</em></p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">Quick Diagnosis</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">If the n8n UI lags, API calls time‑out, or backups miss their windows, an unchecked <code>execution_entity</code> table is often the hidden cause. A targeted cleanup plus a retention policy will neutralize the issue.</p>
<blockquote style="margin: 2em 0; padding-left: 1em; border-left: 4px solid #ddd;">
<p style="margin-bottom: 0; line-height: 1.9;"><em>We’ve seen this pop up in environments where the default retention isn’t overridden.</em></p>
</blockquote>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">1. The Hidden Cost of Unchecked Execution Logs</h2>
<p><strong>If you encounter any </strong><a href="/n8n-production-readiness-checklist">n8n production readiness checklist </a><strong>resolve them before continuing with the setup.</strong></p>
<p style="margin-bottom: 2em; line-height: 1.9;">Here’s a quick look at what the numbers mean in a typical deployment.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;">Metric</th>
<th style="padding: 13px; border: 1px solid #ddd;">Typical Value (No Retention)</th>
<th style="padding: 13px; border: 1px solid #ddd;">Impact When Ignored</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Rows in <code>execution_entity</code> (per 1 M runs)</td>
<td style="padding: 13px; border: 1px solid #ddd;">1 M +</td>
<td style="padding: 13px; border: 1px solid #ddd;">↑ DB size → slower index scans</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Disk usage (PostgreSQL)</td>
<td style="padding: 13px; border: 1px solid #ddd;">1 GB per 250 k rows</td>
<td style="padding: 13px; border: 1px solid #ddd;">Disk‑I/O bottleneck, backup windows explode</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Query latency for <code>SELECT * FROM execution_entity</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">10 ms → 2 s</td>
<td style="padding: 13px; border: 1px solid #ddd;">UI freezes, webhook timeouts</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Memory pressure on n8n worker</td>
<td style="padding: 13px; border: 1px solid #ddd;">200 MB</td>
<td style="padding: 13px; border: 1px solid #ddd;">Crash loops under load</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;"><em>Why it’s a “silent” bomb:</em> n8n stores every run as a JSON blob. The UI only shows the latest 100 rows, so growth stays invisible until the database starts scanning massive tables for routine pagination and audit queries.</p>
<blockquote style="margin: 2em 0; padding-left: 1em; border-left: 4px solid #ddd;">
<p style="margin-bottom: 0; line-height: 1.9;"><strong>EEFA note:</strong> Deleting rows without a proper backup can break compliance audits. Always snapshot the DB before bulk deletions.</p>
</blockquote>
<p style="margin-bottom: 2em; line-height: 1.9;"><em>In production, the growth is usually invisible until a backup starts failing.</em></p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">2. Symptom Checklist – When Execution History Is Killing Performance</h2>
<p><strong>If you encounter any </strong><a href="/common-n8n-architecture-mistakes">common n8n architecture mistakes </a><strong>resolve them before continuing with the setup.</strong></p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;"> Symptom</th>
<th style="padding: 13px; border: 1px solid #ddd;">🔎 How to Verify</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">UI hangs on the “Executions” tab</td>
<td style="padding: 13px; border: 1px solid #ddd;">Chrome DevTools → Network → <code>GET /executions</code> → response > 1 s</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Webhook endpoints return 504</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>curl -I https://your-n8n.com/webhook/...</code> → latency > 3 s</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Database CPU spikes at midnight (cron)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>SELECT pid, query, state FROM pg_stat_activity WHERE state='active';</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Backup jobs exceed SLA</td>
<td style="padding: 13px; border: 1px solid #ddd;">Compare current backup size with previous week</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Disk space alerts from monitoring</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>df -h /var/lib/postgresql/data</code></td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">These checks help you confirm the table is the bottleneck. If <strong>two or more</strong> of these appear, execution history is likely the culprit.</p>
<p style="margin-bottom: 2em; line-height: 1.9;"><em>Most teams notice the UI lag before the webhook timeouts.</em></p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">3. Root Causes – Data Growth Patterns & Mis‑Configured Retention</h2>
<p><strong>If you encounter any </strong><a href="/hidden-cost-of-cheap-n8n-hosting">hidden cost of cheap n8n hosting </a><strong>resolve them before continuing with the setup.</strong></p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;">Cause</th>
<th style="padding: 13px; border: 1px solid #ddd;">Description</th>
<th style="padding: 13px; border: 1px solid #ddd;">Typical Mis‑configuration</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">No retention policy</td>
<td style="padding: 13px; border: 1px solid #ddd;">All runs kept forever</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>N8N_EXECUTIONS_MODE=default</code> (no <code>N8N_EXECUTIONS_MAX_AGE</code>)</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Long‑running workflows</td>
<td style="padding: 13px; border: 1px solid #ddd;">Each step stores a large JSON payload</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>storeData: true</code> on every node without need</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">High‑frequency external triggers</td>
<td style="padding: 13px; border: 1px solid #ddd;">Hundreds of webhook calls per minute</td>
<td style="padding: 13px; border: 1px solid #ddd;">No rate‑limiting, no deduplication</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Autovacuum disabled</td>
<td style="padding: 13px; border: 1px solid #ddd;">B‑tree indexes never cleaned</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>autovacuum = off</code> in <code>postgresql.conf</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Missing composite index</td>
<td style="padding: 13px; border: 1px solid #ddd;">Queries filter on <code>status</code> & <code>startedAt</code></td>
<td style="padding: 13px; border: 1px solid #ddd;">No index on <code>(status, started_at)</code></td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">Often the missing piece is a simple env var.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">4. Quantifying Impact: Real‑World Queries & Benchmarks</h2>
<p style="margin-bottom: 2em; line-height: 1.9;">Run these queries to identify the biggest contributors.</p>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Count rows per workflow (top 5 offenders)</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">SELECT workflowId, COUNT(*) AS runs
FROM execution_entity
GROUP BY workflowId
ORDER BY runs DESC
LIMIT 5;
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Estimate table bloat – size columns</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">SELECT
relname AS table_name,
pg_total_relation_size(relid) AS total_size,
pg_relation_size(relid) AS data_size
FROM pg_catalog.pg_statio_user_tables
WHERE relname = 'execution_entity';
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Estimate table bloat – toast and index</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">SELECT
pg_total_relation_size(relid) - pg_relation_size(relid) AS toast_and_index
FROM pg_catalog.pg_statio_user_tables
WHERE relname = 'execution_entity';
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Benchmark (local Docker, 8 vCPU, 16 GB RAM):</strong></p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;">Row count</th>
<th style="padding: 13px; border: 1px solid #ddd;">Avg SELECT latency (ms)</th>
<th style="padding: 13px; border: 1px solid #ddd;">DELETE 10 k rows latency (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">100 k</td>
<td style="padding: 13px; border: 1px solid #ddd;">12</td>
<td style="padding: 13px; border: 1px solid #ddd;">45</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">1 M</td>
<td style="padding: 13px; border: 1px solid #ddd;">110</td>
<td style="padding: 13px; border: 1px solid #ddd;">210</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">5 M</td>
<td style="padding: 13px; border: 1px solid #ddd;">620</td>
<td style="padding: 13px; border: 1px solid #ddd;">1 200</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">When SELECT latency climbs past a few hundred milliseconds, the UI feels sluggish. Latency above <strong>200 ms</strong> degrades the user experience noticeably.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">5. Defusing the Bomb – Proactive Retention & Automated Cleanup</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">5.1 One‑Command Defuse (Safe, Idempotent)</h3>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Step 1 – Export a DB snapshot (PostgreSQL example)</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">pg_dump -U n8n -Fc -f /tmp/n8n_snapshot_$(date +%F).dump n8n_db
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Step 2 – Delete executions older than 30 days</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">psql -U n8n -d n8n_db -c "
DELETE FROM execution_entity
WHERE started_at < NOW() - INTERVAL '30 days';
VACUUM (FULL, ANALYZE) execution_entity;
"
</pre>
<blockquote style="margin: 2em 0; padding-left: 1em; border-left: 4px solid #ddd;">
<p style="margin-bottom: 0; line-height: 1.9;"><strong>EEFA note:</strong> Run the <code>DELETE</code> inside a transaction if you need atomicity. For massive tables, batch in 10 k‑row chunks to avoid lock contention.<br />
*At this point, deleting old rows is usually faster than trying to rewrite indexes.*</p>
</blockquote>
<h3 style="margin-bottom: 45px; line-height: 1.3;">5.2 Recommended Retention Policy Settings</h3>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;">Setting</th>
<th style="padding: 13px; border: 1px solid #ddd;">What It Keeps</th>
<th style="padding: 13px; border: 1px solid #ddd;">Recommended Value</th>
<th style="padding: 13px; border: 1px solid #ddd;">Why</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">N8N_EXECUTIONS_MAX_AGE</td>
<td style="padding: 13px; border: 1px solid #ddd;">Age‑based purge</td>
<td style="padding: 13px; border: 1px solid #ddd;">30d</td>
<td style="padding: 13px; border: 1px solid #ddd;">Balances audit needs vs. storage</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">N8N_EXECUTIONS_MAX_COUNT</td>
<td style="padding: 13px; border: 1px solid #ddd;">Row‑count cap per workflow</td>
<td style="padding: 13px; border: 1px solid #ddd;">10,000</td>
<td style="padding: 13px; border: 1px solid #ddd;">Prevents runaway workflows</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">N8N_EXECUTIONS_DATA_PRUNE</td>
<td style="padding: 13px; border: 1px solid #ddd;">Strip node data after run</td>
<td style="padding: 13px; border: 1px solid #ddd;">true</td>
<td style="padding: 13px; border: 1px solid #ddd;">Cuts JSON size by ~70 %</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">N8N_EXECUTIONS_PRUNE_INTERVAL</td>
<td style="padding: 13px; border: 1px solid #ddd;">Cron schedule for auto‑prune</td>
<td style="padding: 13px; border: 1px solid #ddd;">0 3 * * *</td>
<td style="padding: 13px; border: 1px solid #ddd;">Off‑peak cleanup</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;">A 30‑day window works for most audit requirements; adjust if you need longer.</p>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Add these to your <code>.env</code> and restart n8n:</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">N8N_EXECUTIONS_MAX_AGE=30d
N8N_EXECUTIONS_MAX_COUNT=10000
N8N_EXECUTIONS_DATA_PRUNE=true
N8N_EXECUTIONS_PRUNE_INTERVAL=0 3 * * *
</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">5.3 Cron‑Based Cleanup Script (Node.js)</h3>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Connect to the database</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">const { Client } = require('pg');
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Delete in 10 k‑row batches</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">const batchSize = 10000;
let deleted;
do {
const res = await client.query(
`DELETE FROM execution_entity
WHERE started_at < NOW() - INTERVAL '30 days'
LIMIT $1
RETURNING id;`,
[batchSize]
);
deleted = res.rowCount;
if (deleted) console.log(`🧨 Deleted ${deleted} rows`);
} while (deleted === batchSize);
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Run vacuum and close the connection</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">await client.query('VACUUM (ANALYZE) execution_entity;');
await client.end();
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">The script runs in batches to avoid long locks. Save as <code>cleanup.js</code> and schedule with systemd:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">[Unit]
Description=n8n Execution History Cleanup
[Service]
Type=oneshot
ExecStart=/usr/bin/node /opt/n8n/cleanup.js
[Install]
WantedBy=multi-user.target
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Enable a daily timer at 02:30 AM:</p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">systemctl enable --now n8n-cleanup.timer
</pre>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">6. Scaling n8n with External Databases – Best‑Practice Checklist</h2>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;">Checklist Item</th>
<th style="padding: 13px; border: 1px solid #ddd;">How to Verify</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Use PostgreSQL ≥ 13 (or MySQL 8)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>SELECT version();</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Enable aggressive <code>autovacuum</code> thresholds</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>SHOW autovacuum;</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Create composite index on <code>(status, started_at)</code></td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>\d execution_entity</code> → confirm index</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Partition <code>execution_entity</code> by month (optional for > 10 M rows)</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>SELECT * FROM pg_partitions WHERE tablename='execution_entity';</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Set <code>max_connections</code> high enough for webhook spikes</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>SHOW max_connections;</code></td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">Run regular <code>ANALYZE</code> after bulk deletes</td>
<td style="padding: 13px; border: 1px solid #ddd;"><code>SELECT last_analyze FROM pg_stat_user_tables WHERE relname='execution_entity';</code></td>
</tr>
</tbody>
</table>
<blockquote style="margin: 2em 0; padding-left: 1em; border-left: 4px solid #ddd;">
<p style="margin-bottom: 0; line-height: 1.9;"><strong>EEFA warning:</strong> Partitioning adds complexity to backup/restore procedures. Test <code>pg_dump</code> on a partitioned table before production rollout.<br />
*Enabling aggressive autovacuum is a cheap win.*</p>
</blockquote>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Sample Partitioning Script (PostgreSQL)</h3>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Create monthly partitions for the next year – part 1</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">DO $$
DECLARE
d DATE := DATE_TRUNC('month', CURRENT_DATE);
BEGIN
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Create monthly partitions – part 2</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;"> FOR i IN 0..12 LOOP
EXECUTE format('
CREATE TABLE IF NOT EXISTS execution_entity_%s PARTITION OF execution_entity
FOR VALUES FROM (%L) TO (%L);',
to_char(d + (i || '' month'')::interval, ''YYYYMM''),
(d + (i || '' month'')::interval),
(d + ((i+1) || '' month'')::interval)
);
END LOOP;
END $$;
</pre>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">7. Monitoring & Alerting – Catch the Bomb Early</h2>
<h3 style="margin-bottom: 45px; line-height: 1.3;">7.1 Prometheus Exporter Metrics to Watch</h3>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 2em;">
<thead>
<tr>
<th style="padding: 13px; border: 1px solid #ddd;">Metric</th>
<th style="padding: 13px; border: 1px solid #ddd;">Alert Threshold</th>
<th style="padding: 13px; border: 1px solid #ddd;">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">n8n_execution_total</td>
<td style="padding: 13px; border: 1px solid #ddd;">> 500 k per day</td>
<td style="padding: 13px; border: 1px solid #ddd;">Sudden surge may indicate runaway loop</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">n8n_db_execution_table_size_bytes</td>
<td style="padding: 13px; border: 1px solid #ddd;">> 2 GB (PostgreSQL)</td>
<td style="padding: 13px; border: 1px solid #ddd;">Table bloat warning</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">n8n_db_query_latency_seconds{query=”SELECT * FROM execution_entity”}</td>
<td style="padding: 13px; border: 1px solid #ddd;">> 0.5 s</td>
<td style="padding: 13px; border: 1px solid #ddd;">UI slowdown precursor</td>
</tr>
<tr>
<td style="padding: 13px; border: 1px solid #ddd;">node_filesystem_avail_bytes{mountpoint=”/var/lib/postgresql/data”}</td>
<td style="padding: 13px; border: 1px solid #ddd;">< 5 GB</td>
<td style="padding: 13px; border: 1px solid #ddd;">Imminent out‑of‑space</td>
</tr>
</tbody>
</table>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Prometheus alert rule</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">- alert: N8NExecutionHistoryBloat
expr: n8n_db_execution_table_size_bytes > 2 * 1024 * 1024 * 1024
for: 5m
labels:
severity: warning
annotations:
summary: "Execution history table > 2 GB"
description: "The n8n execution_entity table has grown beyond 2 GB. Run the cleanup script."
</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">7.2 Grafana Dashboard Snippet – Panel 1 (Size)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">{
"type": "graph",
"title": "Execution Table Size (GB)",
"targets": [{ "expr": "n8n_db_execution_table_size_bytes / (1024*1024*1024)" }]
}
</pre>
<h3 style="margin-bottom: 45px; line-height: 1.3;">Grafana Dashboard Snippet – Panel 2 (Top Workflows)</h3>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">{
"type": "table",
"title": "Top 5 Workflows by Run Count (30 d)",
"targets": [{
"rawSql": "SELECT workflow_id, COUNT(*) AS runs FROM execution_entity WHERE started_at > NOW() - INTERVAL '30 days' GROUP BY workflow_id ORDER BY runs DESC LIMIT 5;"
}]
}
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;">Set up alerts early so you can act before the table fills up.</p>
<hr style="margin: 55px 0;" />
<h2 style="margin-bottom: 45px; line-height: 1.3;">8. Conclusion</h2>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Problem:</strong> n8n’s execution history stores every run as a JSON row. Without retention, the table silently grows, causing DB bloat, slow queries, webhook timeouts, and backup failures.</p>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>One‑line Fix:</strong></p>
<pre style="background: #fafafa; padding: 20px; border: 1px solid #e0e0e0; overflow: auto; margin-bottom: 2em;">psql -U n8n -d n8n_db -c "DELETE FROM execution_entity WHERE started_at < NOW() - INTERVAL '30 days'; VACUUM (FULL, ANALYZE) execution_entity;"
</pre>
<p style="margin-bottom: 2em; line-height: 1.9;"><strong>Preventive Steps</strong></p>
<ol style="margin-bottom: 2em; line-height: 1.9; margin-left: 2em;">
<li>Set <code>N8N_EXECUTIONS_MAX_AGE=30d</code> (or a value that fits your audit window).</li>
<li>Enable <code>N8N_EXECUTIONS_DATA_PRUNE=true</code> to strip node data.</li>
<li>Schedule the Node.js cleanup script (or raw SQL) via systemd or cron.</li>
<li>Add a composite index <code>(status, started_at)</code> and keep <code>autovacuum</code> enabled.</li>
<li>Monitor <code>n8n_db_execution_table_size_bytes</code> and alert when it exceeds <strong>2 GB</strong>.</li>
</ol>
<p style="margin-bottom: 2em; line-height: 1.9;"><em>In practice, we run the cleanup nightly and have not seen the issue resurface.</em></p>
<hr style="margin: 55px 0;" />
<p style="margin-bottom: 2em; line-height: 1.9;"><em>All commands assume a PostgreSQL backend; adjust syntax for MySQL/MariaDB accordingly.</em></p>
Step by Step Guide to solve n8n execution history time bomb
Who this is for: Ops engineers, platform admins, and developers who run n8n in production and need reliable performance. If you’ve been running n8n for a month or more, the execution table can start to swell without you noticing. We cover this in detail in the n8n Production Readiness & Scalability Risks Guide.
Quick Diagnosis
If the n8n UI lags, API calls time‑out, or backups miss their windows, an unchecked execution_entity table is often the hidden cause. A targeted cleanup plus a retention policy will neutralize the issue.
We’ve seen this pop up in environments where the default retention isn’t overridden.
Here’s a quick look at what the numbers mean in a typical deployment.
Metric
Typical Value (No Retention)
Impact When Ignored
Rows in execution_entity (per 1 M runs)
1 M +
↑ DB size → slower index scans
Disk usage (PostgreSQL)
1 GB per 250 k rows
Disk‑I/O bottleneck, backup windows explode
Query latency for SELECT * FROM execution_entity
10 ms → 2 s
UI freezes, webhook timeouts
Memory pressure on n8n worker
200 MB
Crash loops under load
Why it’s a “silent” bomb: n8n stores every run as a JSON blob. The UI only shows the latest 100 rows, so growth stays invisible until the database starts scanning massive tables for routine pagination and audit queries.
EEFA note: Deleting rows without a proper backup can break compliance audits. Always snapshot the DB before bulk deletions.
In production, the growth is usually invisible until a backup starts failing.
2. Symptom Checklist – When Execution History Is Killing Performance
Run these queries to identify the biggest contributors.
Count rows per workflow (top 5 offenders)
SELECT workflowId, COUNT(*) AS runs
FROM execution_entity
GROUP BY workflowId
ORDER BY runs DESC
LIMIT 5;
Estimate table bloat – size columns
SELECT
relname AS table_name,
pg_total_relation_size(relid) AS total_size,
pg_relation_size(relid) AS data_size
FROM pg_catalog.pg_statio_user_tables
WHERE relname = 'execution_entity';
Estimate table bloat – toast and index
SELECT
pg_total_relation_size(relid) - pg_relation_size(relid) AS toast_and_index
FROM pg_catalog.pg_statio_user_tables
WHERE relname = 'execution_entity';
Benchmark (local Docker, 8 vCPU, 16 GB RAM):
Row count
Avg SELECT latency (ms)
DELETE 10 k rows latency (ms)
100 k
12
45
1 M
110
210
5 M
620
1 200
When SELECT latency climbs past a few hundred milliseconds, the UI feels sluggish. Latency above 200 ms degrades the user experience noticeably.
5. Defusing the Bomb – Proactive Retention & Automated Cleanup
5.1 One‑Command Defuse (Safe, Idempotent)
Step 1 – Export a DB snapshot (PostgreSQL example)
EEFA note: Run the DELETE inside a transaction if you need atomicity. For massive tables, batch in 10 k‑row chunks to avoid lock contention.
*At this point, deleting old rows is usually faster than trying to rewrite indexes.*
5.2 Recommended Retention Policy Settings
Setting
What It Keeps
Recommended Value
Why
N8N_EXECUTIONS_MAX_AGE
Age‑based purge
30d
Balances audit needs vs. storage
N8N_EXECUTIONS_MAX_COUNT
Row‑count cap per workflow
10,000
Prevents runaway workflows
N8N_EXECUTIONS_DATA_PRUNE
Strip node data after run
true
Cuts JSON size by ~70 %
N8N_EXECUTIONS_PRUNE_INTERVAL
Cron schedule for auto‑prune
0 3 * * *
Off‑peak cleanup
A 30‑day window works for most audit requirements; adjust if you need longer.
6. Scaling n8n with External Databases – Best‑Practice Checklist
Checklist Item
How to Verify
Use PostgreSQL ≥ 13 (or MySQL 8)
SELECT version();
Enable aggressive autovacuum thresholds
SHOW autovacuum;
Create composite index on (status, started_at)
\d execution_entity → confirm index
Partition execution_entity by month (optional for > 10 M rows)
SELECT * FROM pg_partitions WHERE tablename='execution_entity';
Set max_connections high enough for webhook spikes
SHOW max_connections;
Run regular ANALYZE after bulk deletes
SELECT last_analyze FROM pg_stat_user_tables WHERE relname='execution_entity';
EEFA warning: Partitioning adds complexity to backup/restore procedures. Test pg_dump on a partitioned table before production rollout.
*Enabling aggressive autovacuum is a cheap win.*
Sample Partitioning Script (PostgreSQL)
Create monthly partitions for the next year – part 1
DO $$
DECLARE
d DATE := DATE_TRUNC('month', CURRENT_DATE);
BEGIN
Create monthly partitions – part 2
FOR i IN 0..12 LOOP
EXECUTE format('
CREATE TABLE IF NOT EXISTS execution_entity_%s PARTITION OF execution_entity
FOR VALUES FROM (%L) TO (%L);',
to_char(d + (i || '' month'')::interval, ''YYYYMM''),
(d + (i || '' month'')::interval),
(d + ((i+1) || '' month'')::interval)
);
END LOOP;
END $$;
7. Monitoring & Alerting – Catch the Bomb Early
7.1 Prometheus Exporter Metrics to Watch
Metric
Alert Threshold
Description
n8n_execution_total
> 500 k per day
Sudden surge may indicate runaway loop
n8n_db_execution_table_size_bytes
> 2 GB (PostgreSQL)
Table bloat warning
n8n_db_query_latency_seconds{query=”SELECT * FROM execution_entity”}
{
"type": "table",
"title": "Top 5 Workflows by Run Count (30 d)",
"targets": [{
"rawSql": "SELECT workflow_id, COUNT(*) AS runs FROM execution_entity WHERE started_at > NOW() - INTERVAL '30 days' GROUP BY workflow_id ORDER BY runs DESC LIMIT 5;"
}]
}
Set up alerts early so you can act before the table fills up.
8. Conclusion
Problem: n8n’s execution history stores every run as a JSON row. Without retention, the table silently grows, causing DB bloat, slow queries, webhook timeouts, and backup failures.