WIUNIX ACADEMY
PostgreSQL autovacuum: diagnose cleanup lag and wraparound risk
Separate dead-row estimates from XID/MXID age, find retained horizons and plan one bounded maintenance action on an existing PostgreSQL 18 database.
Make this guide yoursYOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment4 editable values
THE OUTCOME
What will you have at the end?
Separate dead-row estimates from XID/MXID age, find retained horizons and plan one bounded maintenance action on an existing PostgreSQL 18 database.
Supported operating systems and versions
Documented existing-client paths for Ubuntu 26.10/26.04/24.04, Debian 13/12, RHEL/Rocky/AlmaLinux 10.2/9.8 and Arch/Manjaro rolling, reviewed 2026-11-05. PostgreSQL 18 server/client required; no distribution installation or hands-on matrix is claimed.
- Ubuntu 26.10 — existing client
- Ubuntu 26.04 — existing client
- Ubuntu 24.04 — existing client
- Debian 13 — existing client
- Debian 12 — existing client
- RHEL 10.2 — existing client
- RHEL 9.8 — existing client
- Rocky Linux 10.2 — existing client
- Rocky Linux 9.8 — existing client
- AlmaLinux 10.2 — existing client
- AlmaLinux 9.8 — existing client
- Arch Linux rolling — existing client
- Manjaro rolling — existing client
Before you begin
- An existing PostgreSQL 18 database with confirmed identity and approved pg_read_all_stats observation access.
- An installed major-18 client, a direct stable session and existing protected libpq service/authentication definitions.
- For optional maintenance: relation-owner approval, ownership or MAINTAIN, a primary and agreed I/O/WAL/runtime limits.
Separate cleanup pressure from an age emergency
A growing dead-row estimate and an old transaction horizon are different problems. First decide whether normal cleanup is falling behind, whether a snapshot prevents cleanup, or whether freezing is becoming urgent. A recent autovacuum timestamp alone cannot answer that question.
Transaction-ID numbers occupy a finite, circular space. Old row versions must not be misinterpreted as coming from the future after those numbers cycle. Freezing records the insertion transaction of eligible old row versions so that visibility no longer depends on comparing that old normal XID. Read the ages before this maintenance becomes an emergency.
This guide observes an existing PostgreSQL 18 cluster, then offers one deliberately gated table-maintenance action. It creates no server, synthetic wraparound workload or new scheduler. The package choices describe the administration client; the server can be remote.
LET’S BUILD
Identify the actual administration client
Use an approved Linux administration host with the major-18 client already installed. On Ubuntu/Debian, the versioned postgresql-client-18 package supplies the explicit launcher below; the generic Debian wrapper can select a different installed client. On the RHEL family this path requires the PGDG postgresql18 client, not the distribution postgresql package. Arch and Manjaro use postgresql-libs and the unversioned launcher; check that its major is 18.
If the selected package or launcher is absent, stop and provision the approved client through the linked vendor repository instructions in a maintenance change. Do not initialize a cluster, add a repository or upgrade a server during an incident. Manjaro keeps its own branches; do not install an Arch package or switch branches for this guide.
Approved Linux administration host
cat /etc/os-release
id
pacman -Q postgresql-libs
pacman -Qo /usr/bin/psql
test -x /usr/bin/psql
/usr/bin/psql --versionThe selected package owns the executable and psql reports major 18. Otherwise stop before connecting.
Use the existing protected connection definitions
Ask the database owner for two existing libpq service aliases: an observation connection with pg_read_all_stats, and a separately authorized maintenance connection to the same database. The second account needs ownership or MAINTAIN on the selected table. Do not turn an observation login into a superuser for convenience.
A libpq service alias is a client connection profile, not a systemd service. Keep credentials in the approved password/certificate mechanism, outside this form and command history. For remote TCP, retain the approved CA and verify-full hostname verification. Local peer authentication, when deliberately configured, uses the existing OS identity; do not replace pg_hba rules with trust.
No PostgreSQL unit is started here: Debian cluster units, PGDG versioned units and Arch server units are not interchangeable. If local MAC or firewall policy blocks the approved client, inspect the denial with the host owner; do not disable SELinux/AppArmor or open the database publicly.
LET’S BUILD
Open a persistent observation session
On the administration host, open the observation profile. Keep this terminal for all diagnostic SQL. The connection must lead directly to a PostgreSQL backend with a stable session, rather than a transaction-pooling endpoint.
Approved Linux administration host
/usr/bin/psql -X -v ON_ERROR_STOP=1 'service=academy_vacuum_observe'psql opens the intended observation destination; failure means repair the approved connection profile before continuing.
LET’S BUILD
Confirm destination, privileges and current freeze settings
Read the connection identity before every investigation. The check accepts only server major 18 and the approved statistics role, then puts subsequent transactions into read-only mode. A replica can help with some observation, but its recovery flag means it is not a target for manual vacuum.
Keep autocommit on and do not wrap these panels in a long BEGIN block: a diagnostic session must not become another old transaction. The freeze and failsafe settings describe different stages; reaching the autovacuum trigger is not the same as reaching the wraparound shutdown boundary.
Persistent observation psql session
\conninfo
SELECT current_database(), current_user, version(), pg_is_in_recovery();
SELECT current_setting('server_version_num')::integer BETWEEN 180000 AND 189999
AND pg_has_role(current_user,'pg_read_all_stats','USAGE') AS ready \gset
\if :ready
\else
\echo 'STOP: require PostgreSQL 18 and approved pg_read_all_stats access.'
\quit
\endif
SET statement_timeout='15s';
SET lock_timeout='2s';
SET default_transaction_read_only=on;
SHOW autovacuum;
SHOW track_counts;
SHOW autovacuum_freeze_max_age;
SHOW autovacuum_multixact_freeze_max_age;
SHOW vacuum_failsafe_age;
SHOW vacuum_multixact_failsafe_age;The destination/version match the approved cluster, statistics access is present and the actual configuration is visible.
LET’S BUILD
Rank both transaction-ID and multixact ages
Read the cluster-wide database catalog. XID age counts consumed transaction IDs, not seconds or rows; a read-only transaction does not necessarily allocate a new XID. Multixact age tracks a different ID space used by shared row locks. A write-heavy cluster can consume headroom quickly even when the oldest row is not old in wall-clock time.
Compare both age series with their own configured freeze thresholds and trend them at a reasonable interval. This ordering is by XID age; explicitly inspect the multixact column too. A small value in one column does not clear the other. A database that disallows connections needs the DBA’s database-specific procedure, not a blind connection-property change.
Persistent observation psql session
SELECT clock_timestamp() AS captured_at, datname, datallowconn,
age(datfrozenxid) AS xid_age, mxid_age(datminmxid) AS mxid_age
FROM pg_database
ORDER BY age(datfrozenxid) DESC;Every database has separate XID/MXID ages and an explicit connection-availability flag.