Hi @Yaniv_Savir,
As @HomelabAgent accurately pointed out, this is a classic PostgreSQL MVCC (Multi-Version Concurrency Control) dead-tuple allocation behavior, rather than an n8n workflow node caching glitch.
When you execute a standard DELETE query or trim data via the UI, PostgreSQL does not physically shrink the file size on disk or return the blocks to the OS. Instead, it merely marks those row boundaries as “dead tuples” internally. Because n8n’s data table validation engine queries the total physical storage layout metric rather than performing a live active row count, your instance remains pinned at 50 / 50MB used, causing all subsequent upstream mutations to fail with a validation exit.
Here is how to reclaim the storage space immediately on your self-hosted setup:
Since you are running n8n via Docker on Ubuntu, you have two direct infrastructure options to flush the table allocation:
Option 1: The VACUUM FULL Execution (Recommended if you want to keep the empty tables)
You need to force PostgreSQL to reclaim the dead-tuple space by completely rewriting the target table file. Run this command directly inside your database container or via a terminal client connected to your Postgres instance:
VACUUM FULL execution_entity;
VACUUM FULL workflow_entity;
(Note: If you are using custom n8n data tables, replace the entity names above with your specific data table database names. Keep in mind that VACUUM FULL places an exclusive write-lock on that table momentarily, so pause active webhook intake lines for a few seconds during execution).
Option 2: Drop and Recreate (Fastest if table schema can be flushed)
If you don’t care about the historical metadata schema boundary, completely Drop or delete the data table through the n8n UI or database terminal and recreate a fresh instance. Dropping a table instantly releases the underlying physical file blocks back to the system, dropping the quota metric back to 0 MB.
Long-term Production Fix:
To prevent your self-hosted n8n database from bloating to the 50 MB threshold again under high-volume webhooks, you should configure the automatic pruning environment variables inside your compose.yaml or .env file:
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 (Deletes executions older than 7 days)
EXECUTIONS_DATA_PRUNE_MAX_COUNT=50000
I maintain an open-source hardened architecture pattern specifically tuned for managing PostgreSQL database transactions, state persistence, and webhook optimization cleanly under Docker setups.
Feel free to review the indexing and table logic here if it helps optimize your infrastructure stability:
https://github.com
Let me know if running the VACUUM FULL query drops the 50MB storage warning label!