PostgreSQL : database is not accepting commands to avoid wraparound data loss
ERROR: database is not accepting commands to avoid wraparound data loss in database "appdb"
HINT: Stop the postmaster and vacuum that database in single-user mode.
C'est le frein d'urgence du MVCC de PostgreSQL — et si vous lisez ceci pendant l'urgence : la solution est de retirer ce qui bloque vacuum puis de laisser VACUUM geler les tables les plus anciennes. Notez que le classique HINT sur le mode mono-utilisateur est largement considéré comme un conseil dépassé sur les versions modernes ; un VACUUM normal fait généralement le travail mieux et plus sûrement.
Ce que signifie cette erreur
Les identifiants de transaction (XID) sont des valeurs 32 bits attribuées à partir d'un compteur qui boucle en cercle. Pour que la visibilité des lignes reste correcte, aucune ligne vivante ne peut être plus vieille qu'environ 2 milliards de transactions — donc VACUUM gèle continuellement les vieilles lignes, les marquant visibles-pour-toujours et récupérant leur âge. Si le gel prend suffisamment de retard, PostgreSQL avertit d'abord (database "appdb" must be vacuumed within N transactions), puis cesse entièrement d'accepter les commandes modifiant des données, une marge de sécurité avant que le wraparound réel ne corrompe la visibilité.
L'intuition critique : ce n'est pratiquement jamais « vacuum était trop lent » à lui seul — quelque chose retenait l'horizon de gel en arrière. Trouvez-le d'abord ; un VACUUM en course contre un horizon immobile n'accomplit rien.
Causes fréquentes
- Une transaction ouverte très ancienne épinglant l'horizon (une session
idle in transactiondepuis des jours ou des semaines). - Transactions préparées orphelines — créées par la validation en deux phases, oubliées à jamais, invisibles à une inspection superficielle.
- Slots de réplication obsolètes (avec feedback) retenant l'horizon pour le compte d'un réplica qui n'existe plus.
- Autovacuum désactivé ou chroniquement affamé sur d'énormes tables.
Comment la diagnostiquer
-- Quel âge a la base / les pires tables :
SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC;
SELECT relname, age(relfrozenxid) FROM pg_class
WHERE relkind = 'r' ORDER BY 2 DESC LIMIT 20;
-- Les trois coupables habituels de l'horizon bloqué :
SELECT pid, now() - xact_start AS age, state, query
FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 5;
SELECT * FROM pg_prepared_xacts;
SELECT slot_name, active, xmin FROM pg_replication_slots;
Comment la corriger
- Retirez les bloqueurs de l'horizon : terminez les transactions anciennes, faites
COMMIT PREPARED/ROLLBACK PREPAREDsur les transactions en deux phases orphelines, supprimez les slots de réplication morts. - Vacuumez d'abord les pires coupables : exécutez
VACUUM (VERBOSE)sur les tables au plus grand âge derelfrozenxid(ou sur toute la base si le temps le permet). Une fois les bloqueurs partis, les âges chutent et la base recommence à accepter les écritures dès qu'elle repasse sous le seuil de sécurité. - Ensuite, rendez cela structurel : surveillez
age(datfrozenxid)avec des alertes bien en dessous du plafond des 2 milliards, gardez autovacuum activé et correctement doté en ressources, et alertez sur les transactions préparées et les slots inactifs — les détenteurs silencieux de l'horizon.
