🔝 Retour au Sommaire
Le Transaction Wraparound (ou épuisement des XID) est l'un des problèmes les plus graves que PostgreSQL puisse rencontrer. C'est comme une bombe à retardement qui, si elle n'est pas désamorcée, peut arrêter complètement votre base de données.
La bonne nouvelle ? Ce problème est :
- ✅ Totalement prévisible : PostgreSQL vous avertit bien à l'avance
- ✅ Facilement évitable : Avec une maintenance appropriée
- ✅ Automatiquement géré : Dans la plupart des configurations modernes
Ce chapitre va vous expliquer ce qu'est le wraparound, pourquoi il se produit, comment le détecter avant qu'il soit trop tard, et comment le prévenir définitivement.
Dans PostgreSQL, chaque transaction reçoit un identifiant unique appelé XID (Transaction ID). C'est un simple nombre entier.
-- Voir votre XID actuel
SELECT txid_current();
-- Résultat possible : 1234567Les XID sont au cœur du système MVCC (Multiversion Concurrency Control) de PostgreSQL. Ils permettent de savoir :
- Quelles versions de données sont visibles pour quelle transaction
- Quelle transaction a créé ou modifié une ligne
- Si une transaction était en cours avant ou après une autre
Analogie : Imaginez une bibliothèque où chaque visiteur reçoit un numéro d'entrée. Ce numéro détermine quels livres (versions de données) il peut voir.
Chaque ligne de vos tables stocke deux XID invisibles :
-- Voir les colonnes système cachées d'une ligne
SELECT
xmin, -- XID de la transaction qui a créé la ligne
xmax, -- XID de la transaction qui l'a supprimée (0 si toujours visible)
ctid, -- Position physique
*
FROM users
WHERE id = 1; Exemple de résultat :
xmin | xmax | ctid | id | username | email
---------+------+-------+----+----------+------------------
1002345 | 0 | (0,1) | 1 | alice | alice@example.com
Interprétation :
xmin = 1002345: Ligne créée par la transaction 1002345xmax = 0: Ligne toujours vivante (pas supprimée)
PostgreSQL utilise un entier non signé de 32 bits pour les XID. Cela signifie :
Valeurs possibles : 0 à 4 294 967 295 (environ 4,3 milliards)
Après avoir utilisé tous les XID disponibles, PostgreSQL recommence à zéro. C'est le wraparound (retour à zéro).
Transaction 4 294 967 295 (dernier XID)
Transaction 0 (retour au début - wraparound!)
Transaction 1
Transaction 2
...
Sans précaution, le wraparound créerait une catastrophe logique :
Scénario catastrophe :
- Transaction XID = 100 crée une ligne (xmin = 100)
- Wraparound se produit
- Transaction XID = 100 (nouveau tour) tente de lire les données
- PostgreSQL pense que cette ligne a été créée dans le futur !
- La ligne devient invisible alors qu'elle devrait être visible
Résultat : Vos données disparaissent mystérieusement ! 😱
Pour éviter le problème du wraparound, PostgreSQL "gèle" (freeze) les anciennes transactions. Cela signifie :
Avant le gel :
Ligne : xmin = 1002345 (créée par transaction 1002345)
Après le gel :
Ligne : xmin = 2 (valeur spéciale "FrozenTransactionId")
La valeur spéciale 2 (ou FrozenTransactionId) signifie : "Cette ligne est visible pour toutes les transactions, peu importe le wraparound".
Le gel est effectué par le processus VACUUM :
-- VACUUM gèle les anciennes lignes
VACUUM users;
-- VACUUM FREEZE force le gel immédiat
VACUUM FREEZE users;Ce que VACUUM fait :
- Parcourt toutes les lignes de la table
- Pour chaque ligne "ancienne" (xmin ancien)
- Remplace xmin par FrozenTransactionId (2)
- Marque la ligne comme "gelée" pour toujours
PostgreSQL a plusieurs paramètres qui contrôlent quand et comment le gel se produit.
Définition : Âge minimum (en transactions) avant qu'une ligne puisse être gelée.
SHOW vacuum_freeze_min_age;
-- Valeur par défaut : 50000000 (50 millions de transactions)Signification : VACUUM ne gèlera une ligne que si son xmin a au moins 50 millions de transactions d'écart avec le XID actuel.
Pourquoi pas geler immédiatement ?
- Geler coûte en I/O (écriture sur disque)
- Les lignes récentes peuvent encore être modifiées
- On attend que les lignes soient "stables"
Définition : Âge d'une table (en transactions) qui déclenche un VACUUM complet pour geler.
SHOW vacuum_freeze_table_age;
-- Valeur par défaut : 150000000 (150 millions)Signification : Quand la plus ancienne transaction non gelée d'une table atteint 150 millions de transactions d'âge, VACUUM parcourt toute la table pour geler agressivement.
Définition : Âge maximum absolu avant qu'un VACUUM d'urgence soit forcé.
SHOW autovacuum_freeze_max_age;
-- Valeur par défaut : 200000000 (200 millions)Signification : À 200 millions de transactions d'âge, PostgreSQL force un VACUUM automatique même si autovacuum est désactivé. C'est la dernière ligne de défense.
autovacuum_freeze_max_age, PostgreSQL lancera un VACUUM prioritaire qui peut impacter les performances.
Définition : Similaire pour les MultiXacts (transactions concurrentes sur la même ligne).
SHOW autovacuum_multixact_freeze_max_age;
-- Valeur par défaut : 400000000 (400 millions)-- Âge de la base de données (en transactions)
SELECT
datname,
age(datfrozenxid) AS age_in_transactions,
datfrozenxid
FROM pg_database
ORDER BY age(datfrozenxid) DESC; Exemple de résultat :
datname | age_in_transactions | datfrozenxid
----------+---------------------+--------------
mydb | 95000000 | 1005000
postgres | 2000000 | 1098000
template1| 1500000 | 1098500
Interprétation :
age_in_transactions: Nombre de transactions depuis le dernier gel complet- Plus l'âge est élevé, plus on se rapproche du danger
- ALERTE si > 1 milliard (25% de la limite de 4 milliards)
SELECT
schemaname,
tablename,
age(relfrozenxid) AS age_in_transactions,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_catalog.pg_tables t
JOIN pg_catalog.pg_class c ON c.relname = t.tablename
ORDER BY age(relfrozenxid) DESC
LIMIT 20; Exemple de résultat :
schemaname | tablename | age_in_transactions | size
------------+-----------+---------------------+---------
public | logs | 180000000 | 250 GB
public | events | 170000000 | 180 GB
public | users | 50000000 | 15 GB
Interprétation :
- La table
logsapproche dangereusement deautovacuum_freeze_max_age(200M) - Elle a besoin d'un VACUUM urgent
SELECT
datname,
age(datfrozenxid) AS age_in_transactions,
round(100.0 * age(datfrozenxid) / 2000000000, 2) AS percent_towards_wraparound
FROM pg_database
ORDER BY age(datfrozenxid) DESC; Exemple de résultat :
datname | age_in_transactions | percent_towards_wraparound
----------+---------------------+---------------------------
mydb | 180000000 | 9.00
postgres | 2000000 | 0.10
Seuils d'alerte :
- < 5% : Tout va bien ✅
- 5-10% : Surveiller 👀
- 10-20% : Attention, planifier maintenance
⚠️ - > 20% : URGENT, action immédiate requise 🚨
PostgreSQL émet des warnings dans les logs :
WARNING: database "mydb" must be vacuumed within 11000000 transactions
HINT: To avoid a database shutdown, execute a database-wide VACUUM in "mydb".
Vérifier les logs :
sudo tail -f /var/log/postgresql/postgresql-*.log | grep -i "wraparound\|must be vacuumed"Voici les étapes progressives vers le wraparound :
0 --------------------------- 200M ------------------------- 2B ---------------------------- 4B
| | | |
Transactions créées autovacuum_freeze_max_age Wraparound Warning Shutdown!
(VACUUM forcé) (PostgreSQL alerte) (Arrêt pour protection)
✅ Tout va bien
- Autovacuum fonctionne normalement
- Le gel se fait automatiquement
- Autovacuum se déclenche en mode agressif
- Performance peut être affectée
- Action : Investiguer pourquoi VACUUM n'a pas fonctionné
🚨 DANGER CRITIQUE
- PostgreSQL émet des warnings constants
- Base de données en mode "survie"
- Action : VACUUM manuel d'urgence
💀 CATASTROPHE
- PostgreSQL refuse toutes les nouvelles transactions
- Seules les opérations VACUUM sont autorisées
- Database en lecture seule forcée
Message d'erreur :
ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT: Stop the postmaster and vacuum that database in single-user mode.
-- Vérifier globalement
SHOW autovacuum;
-- Résultat attendu : on
-- Vérifier pour une table spécifique
SELECT
schemaname,
tablename,
(reloptions::text LIKE '%autovacuum_enabled=off%') AS autovacuum_disabled
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema'); Si autovacuum est désactivé sur une table :
-- Réactiver autovacuum pour une table
ALTER TABLE my_table SET (autovacuum_enabled = true);
-- Ou supprimer le paramètre pour utiliser la config globale
ALTER TABLE my_table RESET (autovacuum_enabled);-- Dans postgresql.conf
# Activer autovacuum (OBLIGATOIRE)
autovacuum = on
# Nombre de workers (augmenter pour grandes bases)
autovacuum_max_workers = 6
# Fréquence de vérification (par défaut : 1 minute)
autovacuum_naptime = 1min
# Nouveauté PG 18 : Ajustement dynamique des workers
autovacuum_worker_slots = 10
# Seuils de déclenchement (par défaut souvent suffisant)
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2
# Nouveauté PG 18 : Seuil max pour grandes tables
autovacuum_vacuum_max_threshold = 1000000Créez une vue pour monitorer facilement :
CREATE OR REPLACE VIEW v_wraparound_monitoring AS
SELECT
datname,
age(datfrozenxid) AS age_in_transactions,
round(100.0 * age(datfrozenxid) / 2000000000, 2) AS percent_towards_wraparound,
CASE
WHEN age(datfrozenxid) < 100000000 THEN 'OK'
WHEN age(datfrozenxid) < 200000000 THEN 'WATCH'
WHEN age(datfrozenxid) < 1000000000 THEN 'WARNING'
ELSE 'CRITICAL'
END AS status,
2000000000 - age(datfrozenxid) AS transactions_until_emergency
FROM pg_database
ORDER BY age(datfrozenxid) DESC;
-- Consulter facilement
SELECT * FROM v_wraparound_monitoring;Configurer des alertes :
-- Créer une fonction d'alerte
CREATE OR REPLACE FUNCTION check_wraparound_danger()
RETURNS TABLE(datname name, age bigint, percent numeric, status text) AS $$
BEGIN
RETURN QUERY
SELECT
d.datname,
age(d.datfrozenxid)::bigint AS age,
round(100.0 * age(d.datfrozenxid) / 2000000000, 2) AS percent,
CASE
WHEN age(d.datfrozenxid) > 1000000000 THEN 'CRITICAL'
WHEN age(d.datfrozenxid) > 200000000 THEN 'WARNING'
ELSE 'OK'
END AS status
FROM pg_database d
WHERE age(d.datfrozenxid) > 200000000;
END;
$$ LANGUAGE plpgsql;
-- Planifier avec pg_cron
SELECT cron.schedule(
'check-wraparound',
'0 */4 * * *', -- Toutes les 4 heures
'SELECT * FROM check_wraparound_danger()'
);Pour les très grandes tables :
-- Identifier les tables problématiques
SELECT
schemaname || '.' || tablename AS full_table_name,
age(relfrozenxid) AS age_in_transactions,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size,
last_vacuum,
last_autovacuum
FROM pg_catalog.pg_tables t
JOIN pg_catalog.pg_class c ON c.relname = t.tablename
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
AND age(relfrozenxid) > 150000000
ORDER BY age(relfrozenxid) DESC;
-- VACUUM manuel avec monitoring
\timing on
VACUUM (VERBOSE, ANALYZE) large_table;Option pour tables très volumineuses :
-- VACUUM par morceaux (attention : technique avancée)
-- Augmenter temporairement maintenance_work_mem
SET maintenance_work_mem = '4GB';
VACUUM (VERBOSE, ANALYZE) huge_table;
RESET maintenance_work_mem; Si VACUUM impacte vos performances :
-- Utiliser pg_cron pour planifier la nuit
SELECT cron.schedule(
'vacuum-large-tables',
'0 2 * * *', -- 2h du matin tous les jours
$$VACUUM (VERBOSE, ANALYZE) logs$$
);
SELECT cron.schedule(
'vacuum-events',
'0 3 * * *',
$$VACUUM (VERBOSE, ANALYZE) events$$
);Alternative avec crontab système :
# Éditer crontab
crontab -e
# Ajouter
0 2 * * * /usr/bin/psql -U postgres -d mydb -c "VACUUM (VERBOSE) logs;" >> /var/log/vacuum.log 2>&1Si VACUUM est trop agressif et impacte les performances :
-- Dans postgresql.conf
# Limiter l'impact I/O de VACUUM (valeurs plus basses = moins agressif)
vacuum_cost_delay = 10ms # Pause entre opérations (0 = aucune pause)
vacuum_cost_page_hit = 1 # Coût page en cache
vacuum_cost_page_miss = 10 # Coût page lue depuis disque
vacuum_cost_page_dirty = 20 # Coût page écrite
vacuum_cost_limit = 200 # Limite avant pause
# Pour autovacuum spécifiquement
autovacuum_vacuum_cost_delay = 2ms
autovacuum_vacuum_cost_limit = 200 Augmenter la vitesse pour maintenance urgente :
-- Augmenter temporairement pour VACUUM manuel rapide
SET vacuum_cost_delay = 0; -- Aucune pause, pleine vitesse
VACUUM logs;
RESET vacuum_cost_delay; -- Identifier la table en danger
SELECT
schemaname || '.' || tablename AS table_name,
age(relfrozenxid) AS age,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_catalog.pg_tables t
JOIN pg_catalog.pg_class c ON c.relname = t.tablename
WHERE age(relfrozenxid) > 180000000
ORDER BY age(relfrozenxid) DESC
LIMIT 5; -- Option 1 : VACUUM standard (recommandé)
VACUUM (VERBOSE, ANALYZE) problematic_table;
-- Option 2 : VACUUM FREEZE (plus agressif, gèle tout)
VACUUM (FREEZE, VERBOSE, ANALYZE) problematic_table;Surveiller la progression :
-- Dans une autre session, monitorer
SELECT
pid,
phase,
heap_blks_total,
heap_blks_scanned,
heap_blks_vacuumed,
round(100.0 * heap_blks_scanned / NULLIF(heap_blks_total, 0), 2) AS percent_complete
FROM pg_stat_progress_vacuum
WHERE relid = 'problematic_table'::regclass; Si VACUUM complet prend trop de temps, considérez le partitionnement :
-- Nouvelle table partitionnée
CREATE TABLE logs_new (
id BIGSERIAL,
message TEXT,
created_at TIMESTAMP
) PARTITION BY RANGE (created_at);
-- Créer des partitions
CREATE TABLE logs_2024_01 PARTITION OF logs_new
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE logs_2024_02 PARTITION OF logs_new
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- ...
-- Migrer progressivement avec INSERT ... SELECT
-- En plusieurs transactions pour ne pas bloquer
BEGIN;
INSERT INTO logs_new
SELECT * FROM logs
WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02';
COMMIT;
-- Répéter par jour ou semaineSi PostgreSQL refuse les nouvelles transactions :
ERROR: database is not accepting commands to avoid wraparound data loss
C'est une protection, pas une destruction. PostgreSQL protège vos données.
# 1. Arrêter PostgreSQL
sudo systemctl stop postgresql
# 2. Démarrer en mode single-user
sudo -u postgres postgres --single -D /var/lib/postgresql/data mydb
# 3. Dans le mode single-user, exécuter VACUUM
VACUUM FREEZE;
# 4. Quitter (Ctrl+D)
# 5. Redémarrer normalement
sudo systemctl start postgresql-- Une fois la base redémarrée
VACUUM (FREEZE, VERBOSE) ANALYZE;
-- Pour chaque base de données
\c mydb
VACUUM (FREEZE, VERBOSE) ANALYZE;
\c otherdb
VACUUM (FREEZE, VERBOSE) ANALYZE;SELECT
datname,
age(datfrozenxid) AS age,
round(100.0 * age(datfrozenxid) / 2000000000, 2) AS percent
FROM pg_database
ORDER BY age DESC; L'âge devrait être redescendu à quelques millions de transactions.
PostgreSQL 18 apporte plusieurs améliorations pour VACUUM :
-- Nouveauté PG 18 : Statistiques détaillées dans pg_stat_all_tables
SELECT
schemaname,
tablename,
last_vacuum,
last_autovacuum,
vacuum_count,
autovacuum_count,
n_tup_ins, -- Lignes insérées
n_tup_upd, -- Lignes mises à jour
n_tup_del, -- Lignes supprimées
n_live_tup, -- Lignes vivantes
n_dead_tup, -- Lignes mortes (à nettoyer)
last_analyze,
last_autoanalyze
FROM pg_stat_all_tables
WHERE schemaname = 'public'
ORDER BY n_dead_tup DESC; -- Nouveauté PG 18 : Paramètres dynamiques
-- Dans postgresql.conf
# Workers ajustables dynamiquement
autovacuum_worker_slots = 10
# Seuil maximum pour éviter autovacuum trop fréquent sur énormes tables
autovacuum_vacuum_max_threshold = 1000000-- Suivre le WAL généré (indication de l'activité)
SELECT
datname,
sum(wal_bytes) AS total_wal_bytes,
pg_size_pretty(sum(wal_bytes)) AS total_wal_size
FROM pg_stat_database
GROUP BY datname
ORDER BY sum(wal_bytes) DESC; #!/bin/bash
# wraparound_check.sh
PGUSER="postgres"
PGDATABASE="postgres"
THRESHOLD_WARNING=200000000
THRESHOLD_CRITICAL=1000000000
# Vérifier l'âge des bases
RESULT=$(psql -U $PGUSER -d $PGDATABASE -t -c "
SELECT
datname || ': ' || age(datfrozenxid)
FROM pg_database
WHERE age(datfrozenxid) > $THRESHOLD_WARNING
ORDER BY age(datfrozenxid) DESC;
")
if [ -n "$RESULT" ]; then
echo "⚠️ WRAPAROUND WARNING ⚠️"
echo "$RESULT"
# Vérifier si critique
CRITICAL=$(psql -U $PGUSER -d $PGDATABASE -t -c "
SELECT COUNT(*)
FROM pg_database
WHERE age(datfrozenxid) > $THRESHOLD_CRITICAL;
")
if [ "$CRITICAL" -gt 0 ]; then
echo "🚨 CRITICAL: Immediate action required!"
# Envoyer alerte (email, Slack, etc.)
fi
else
echo "✅ All databases are safe from wraparound"
fiPlanifier avec cron :
# Tous les jours à 8h
0 8 * * * /usr/local/bin/wraparound_check.sh >> /var/log/wraparound_check.log 2>&1# prometheus.yml - Alertes wraparound
groups:
- name: postgresql_wraparound
rules:
- alert: PostgreSQLWraparoundWarning
expr: pg_database_age > 200000000
for: 1h
labels:
severity: warning
annotations:
summary: "PostgreSQL database {{ $labels.datname }} approaching wraparound"
description: "Age: {{ $value }} transactions"
- alert: PostgreSQLWraparoundCritical
expr: pg_database_age > 1000000000
for: 5m
labels:
severity: critical
annotations:
summary: "PostgreSQL database {{ $labels.datname }} in CRITICAL wraparound danger!"
description: "Age: {{ $value }} transactions - IMMEDIATE ACTION REQUIRED"Réponse : Dépend de la taille de la table et du disque.
Estimation approximative :
- Table 10 GB sur SSD : ~5-15 minutes
- Table 100 GB sur SSD : ~30-90 minutes
- Table 1 TB sur SSD : ~5-15 heures
Facteurs d'accélération :
- Augmenter
maintenance_work_mem - Utiliser SSD NVMe
- Mettre
vacuum_cost_delay = 0temporairement
Réponse :
- ❌ VACUUM normal ne bloque pas les lectures ni les écritures
⚠️ VACUUM FULL bloque tout (exclusif), évitez-le en production- ✅ VACUUM FREEZE se comporte comme VACUUM normal (non bloquant)
Réponse : Oui, avec pg_cancel_backend(pid) ou pg_terminate_backend(pid).
Conséquence : Le travail déjà fait est conservé, mais la table n'est pas complètement gelée.
Causes possibles :
- Autovacuum désactivé (vérifier avec
SHOW autovacuum;) - Pas assez de workers (
autovacuum_max_workerstrop faible) - Transactions longues bloquent VACUUM
vacuum_cost_delaytrop élevé (VACUUM trop lent)
Solution :
-- Vérifier les verrous
SELECT * FROM pg_stat_activity WHERE state = 'active' AND query_start < now() - interval '1 hour';
-- Augmenter les workers
ALTER SYSTEM SET autovacuum_max_workers = 6;
SELECT pg_reload_conf(); Réponse :
- Réplication physique : Les réplicas reçoivent les modifications VACUUM automatiquement
- Réplication logique : Oui, chaque instance doit faire son propre VACUUM
- Vérifier que autovacuum est actif
- Consulter les logs pour warnings
- Vérifier via monitoring (Grafana, Prometheus)
- Exécuter la requête de monitoring :
SELECT * FROM v_wraparound_monitoring; - Vérifier les tables avec
age(relfrozenxid) > 100M - Analyser les statistiques de VACUUM :
SELECT * FROM pg_stat_user_tables;
- VACUUM manuel des tables les plus volumineuses
- Vérifier la configuration autovacuum
- Analyser l'efficacité de VACUUM (logs)
- Vérifier disk space (VACUUM nécessite espace temporaire)
- Revoir les paramètres
autovacuum_freeze_max_age,vacuum_freeze_min_age - Tester un VACUUM FREEZE complet en maintenance
- Auditer les tables avec autovacuum désactivé
# postgresql.conf
# Autovacuum
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2
# Freeze
vacuum_freeze_min_age = 50000000
vacuum_freeze_table_age = 150000000
autovacuum_freeze_max_age = 200000000
# Performance VACUUM
maintenance_work_mem = 1GB
autovacuum_work_mem = -1 # Utilise maintenance_work_mem
autovacuum_vacuum_cost_delay = 2ms # postgresql.conf
# Plus de workers pour paralléliser
autovacuum_max_workers = 8
autovacuum_worker_slots = 12 # PG 18
# Seuils ajustés pour grandes tables
autovacuum_vacuum_scale_factor = 0.05 # 5% au lieu de 20%
autovacuum_vacuum_max_threshold = 5000000 # PG 18
# Plus de mémoire
maintenance_work_mem = 4GB
# Freeze plus agressif pour éviter accumulation
vacuum_freeze_table_age = 100000000
autovacuum_freeze_max_age = 150000000 -- Installer l'extension
CREATE EXTENSION pg_visibility;
-- Vérifier les pages gelées d'une table
SELECT
all_visible,
all_frozen,
count(*)
FROM pg_visibility_map('large_table')
GROUP BY all_visible, all_frozen; Interprétation :
all_frozen = true: Pages complètement gelées (bon)all_frozen = false: Pages nécessitant un gel
-- Installer
CREATE EXTENSION pgstattuple;
-- Analyser une table
SELECT
table_len,
tuple_count,
tuple_len,
tuple_percent,
dead_tuple_count,
dead_tuple_len,
dead_tuple_percent,
free_space,
free_percent
FROM pgstattuple('large_table');Utilisation : Identifier les tables avec beaucoup de lignes mortes nécessitant VACUUM.
- Documentation officielle : Routine Database Maintenance
- Wiki PostgreSQL : Vacuum
- Blog 2ndQuadrant : "Understanding Transaction ID Wraparound"
- Percona Blog : "PostgreSQL Vacuum and Wraparound"
✅ XID (Transaction ID) : Identifiant unique de chaque transaction, limité à 4 milliards
✅ Wraparound : Retour à zéro des XID après 4 milliards, causant confusion temporelle
✅ Freezing : PostgreSQL "gèle" les anciennes transactions pour éviter le wraparound
✅ VACUUM : Processus qui gèle les lignes et prévient le wraparound
✅ autovacuum_freeze_max_age : 200M par défaut, limite critique à 2 milliards
✅ Monitoring : Vérifier age(datfrozenxid) régulièrement
✅ Alertes : Mettre en place monitoring proactif (< 5% = OK, > 20% = URGENT)
✅ Prévention : Autovacuum activé + configuration appropriée + monitoring
✅ Urgence : VACUUM FREEZE manuel si table approche limite
✅ Protection finale : PostgreSQL refuse les transactions pour protéger les données
Le Transaction Wraparound peut sembler effrayant, mais c'est un problème totalement maîtrisable avec :
- Autovacuum activé (ne JAMAIS désactiver)
- Monitoring régulier de
age(datfrozenxid) - Alertes proactives avant d'atteindre les seuils critiques
- Maintenance planifiée pour les très grandes tables
PostgreSQL vous protège avec plusieurs niveaux de défense :
- Autovacuum automatique
- VACUUM forcé à 200M transactions
- Warnings dans les logs
- Mode lecture seule en dernier recours
Avec une configuration appropriée et un monitoring minimal, vous ne devriez jamais rencontrer ce problème en production. Le wraparound n'est un danger que pour les bases de données mal maintenues ou avec autovacuum désactivé.
Votre meilleure défense : Laissez autovacuum faire son travail, et surveillez occasionnellement que tout va bien !
Prochaine étape : 19.4.4 - Corruption de données et Checksums