Free Space Held by Earlier Versions (Eneo 2.3)
Eneo 2.2.0 up to and including 2.3.0-rc.3 kept the search data of every replaced version of a web page or document. Later releases remove it as soon as a version is replaced. This guide removes what the older releases left behind, once per installation.
TL;DR
- The problem. When a recrawl or a new upload replaced a page or file, Eneo
kept the old version’s chunks and embeddings. Nothing searched them, but they
counted toward the storage quota. On websites that are recrawled often, the
quota could fill up so that changed pages were no longer saved (crawl
problems show
TENANT_QUOTA_EXCEEDED). - The fix. From 2.3.0-rc.4, Eneo removes this data when it replaces a version. A new installation of 2.3 never needs this guide.
- What you do. Run
status, thenrun, vacuum two tables, and runstatusagain. Eneo keeps running, and you can stop and restart the cleanup at any time. Text, originals and citations of earlier versions stay. - How long. About 16 minutes for 347,000 replaced versions on a test workstation. A production server can be much slower, so on a large installation try it on a restored copy first.
Before you start
-
Use a worker image with the current command. 2.3.0-rc.4 has an earlier version whose final count can time out on a large database and hide the result. In the current one,
--helphas a line starting withExit codes::docker compose --env-file .env --env-file release.env \ run --rm --no-deps worker \ python -m eneo.info_blobs.replaced_versions --helpIf
Exit codes:is missing, the command started an older worker image: check that you pass the same environment files as when you deployed. -
Watch WAL if you keep it. Removing rows writes WAL. If you keep WAL for a replica or an archive, check that it has room.
-
No PostgreSQL settings to change. The command turns off parallel query itself, so it works with the default 64 MB
/dev/shm. If you setmax_parallel_workers_per_gather = 0because of shared memory errors, keep it until thedbservice hasshm_size: 1gb.
Run the cleanup
Run the commands on the Docker host, in the directory with your
docker-compose.yml. Each starts a one-off container from the worker’s image
and configuration, so pass the same environment files as when you deploy
(--env-file .env --env-file release.env, see
One version for the application).
Without release.env, docker compose run can start the previous release’s
image. Already in a shell in the worker container, for example Portainer’s
console? Use the commands for inside the
container instead.
See what is there
docker compose --env-file .env --env-file release.env \
run --rm --no-deps -T worker \
python -m eneo.info_blobs.replaced_versions status > replaced-status.json
cat replaced-status.json{
"versions_read": 367000,
"remaining_observed": 347000,
"last_scanned_id": "ffffc952-ee6e-45aa-a436-b4709a1630b3",
"pass_complete": true,
"detail": "Replaced versions still hold search data. Release it with run."
}versions_read is the number of replaced versions and remaining_observed
how many still hold search data. Nothing is left only when status exits with
0 and shows "pass_complete": true and "remaining_observed": 0. If it
stopped early ("outcome": "inconclusive"), the count means nothing; run it
again.
Start the cleanup
nohup docker compose --env-file .env --env-file release.env \
run --rm --no-deps -T --name eneo-replaced-versions worker \
python -m eneo.info_blobs.replaced_versions run \
> replaced-run.json 2> replaced-run.log &It runs in the background and keeps running when you log out.
Start it exactly like this. Started directly with docker compose exec, it
keeps running unseen when the client stops.
Follow or stop it
tail -f replaced-run.log2026-10-27T19:16:03+00:00 read 94500 replaced versions, released 83113 (1.00 GB), skipped 6 busy, through 428bb271-65a8-4440-a2fd-10c74fb7e88bread counts up to versions_read and released up to remaining_observed
from status. Ctrl-C stops following, not the cleanup. To stop the cleanup:
docker stop eneo-replaced-versionsIt still writes its result. The batch that was running may or may not have been saved, so the numbers can be one batch low. Running it again is safe.
Read the result
When the cleanup has finished, replaced-run.json holds the result:
cat replaced-run.json| Result | Exit code | Meaning | What to do |
|---|---|---|---|
pass_complete: true and skipped_busy: 0 | 0 | Every replaced version was read and released. | Vacuum and check. |
pass_complete: true and skipped_busy > 0 | 2 | Some versions were skipped because another transaction held them, for example a running crawl. | Run it again later. |
"outcome": "incomplete" | 3 | It stopped before the end. | See Stopped early, then run it again. |
released_versions and released_bytes count what was released, also after a
stop.
Vacuum and check
Let PostgreSQL reuse the freed space:
docker compose --env-file .env --env-file release.env \
exec -T db psql -U postgres -d eneo \
-c "VACUUM (ANALYZE, PARALLEL 0) info_blob_chunks;" \
-c "VACUUM (ANALYZE, PARALLEL 0) info_blobs;"Keep PARALLEL 0: a plain VACUUM processes indexes in parallel and fails with
could not resize shared memory segment on the default 64 MB /dev/shm.
VACUUM runs alongside normal use. Autovacuum would do the same work later,
throttled to spare the database.
Then run status again as in the first step and check that nothing is left.
Before the vacuum, status has to read past every removed row and can be much
slower.
Inside the worker container
In a shell in the worker container, run the same steps without docker. Work
in /tmp, because the worker cannot write to its own directory:
cd /tmp
python -m eneo.info_blobs.replaced_versions status > replaced-status.json
cat replaced-status.json
nohup python -m eneo.info_blobs.replaced_versions run \
> replaced-run.json 2> replaced-run.log &
echo $! > replaced-run.pid
tail -f replaced-run.log # follow
kill -TERM "$(cat replaced-run.pid)" # stop
cat replaced-run.json # result, when it has finishedIt keeps running when you close the shell. Restarting or recreating the worker container stops it; run it again afterwards. Vacuum from the host as in the last step.
On Podman, Kubernetes or another platform
Run the same command once in a one-off container with this release’s backend
image and the worker’s environment, and stop it with SIGTERM, for example
podman stop. Do not start it with podman-compose run -d: that container
keeps the worker’s restart policy, so Podman starts the cleanup again every
time it finishes. On Kubernetes, use a Job with restartPolicy: Never and
backoffLimit: 0; kubectl logs -f shows progress and then the result, and
kubectl delete job stops it like docker stop.
Quota and disk afterwards
Quota usage drops by the search data of history created by Eneo 2.2 or later.
Web pages crawled by Eneo 2.1 recorded only their text as their size, so
removing their chunks frees database space without lowering quota usage. Quota
usage can therefore drop by less than released_bytes.
PostgreSQL reuses the freed space but does not return it to the operating system. Shrinking the files needs a table rewrite in a maintenance window; see Reclaim disk space.
Troubleshooting
Stopped early
The end of replaced-run.log names the cause:
lock timeout: another job, such as a migration, held the table for more than 2 seconds. The cleanup gives up instead of waiting. Run it again later.statement timeout: one batch took more than 60 seconds, so the database is overloaded. Run it again at a quieter time.CancelledError: it was stopped, withdocker stoporkill -TERM.
Container name already in use
Docker reports The container name ... is already in use: an earlier cleanup is still running or was not removed.
docker ps -a --filter name=eneo-replaced-versions shows which. Let a running
one finish, or remove an exited one with docker rm eneo-replaced-versions.
Started another way
- Directly with
docker compose exec worker python ...: it can keep running inside the worker container without output. Let it finish and check withstatus, or restart the worker container to stop it. - With
podman-compose run -d: stop and remove it withpodman stop eneo-replaced-versionsandpodman rm eneo-replaced-versions, then start it as shown above.
Is this one-time work?
Yes. Since 2.3.0-rc.4 nothing new builds up. Running the cleanup again finds nothing to release and changes nothing.