Diagnostic Queries
Symptoms
PostgreSQL has stopped accepting commands that assign new transaction IDs to prevent data loss from transaction-ID wraparound. It reports SQLSTATE XX000 (internal_error) for write attempts.
- The database refuses new write transactions.
- A protective measure against wraparound data loss.
- Single-user VACUUM is required to recover.
What the server log shows
ERROR: database is not accepting commands to avoid wraparound data loss in database "production"
HINT: Stop the postmaster and vacuum that database in single-user mode.
Why PostgreSQL raises this — what the manual says
Section 24.1.5 Preventing Transaction ID Wraparound Failures:
“If these warnings are ignored, the system will refuse to assign new XIDs once there are fewer than three million transactions left until wraparound:”
When the oldest unfrozen XID gets dangerously close to wraparound, PostgreSQL enters a protective state that refuses new XID-assigning commands. This prevents catastrophic data loss until a VACUUM advances the frozen-xid horizon.
Common causes
- Wraparound warnings were ignored for too long.
- Autovacuum unable to freeze old XIDs (disabled, blocked, or starved).
- Long-running transactions pinning the xmin horizon.
How to fix it
- Run
VACUUMon the affected database (single-user mode if it won’t accept commands). - Remove transactions/prepared-xacts/replication slots holding back the horizon.
- Afterwards, fix autovacuum so this never recurs.
Diagnostic query
SELECT datname, age(datfrozenxid) AS xid_age FROM pg_database ORDER BY xid_age DESC;
Track this regularly; act long before the age approaches autovacuum_freeze_max_age.
Related & next steps
Reference: PostgreSQL 18 Section 25.1 “Routine Vacuuming”.
Thanks — noted. This helps keep the database accurate.