Tester efficacement les WAL

Ton PostgreSQL peut te mentir. Il t’écrit « COMMIT », puis au prochain démarrage, la transaction s’est volatilisée. Ce qui sépare ce mensonge d’une catastrophe, c’est le WAL, et il se teste comme n’importe quelle pièce critique de ton infrastructure.

Dans ce tutoriel complet, on va arrêter brutalement une instance PostgreSQL, inspecter les enregistrements WAL avec pg_waldump, redémarrer, puis vérifier que tes données sont toujours là. Tu vas aussi mesurer le temps de reprise et le volume de WAL généré avec pgbench. Objectif : repartir avec un test d’intégration continue qui te prévient avant que la production ne le fasse.

Tout tourne autour d’un numéro : le LSN (Log Sequence Number). Chaque enregistrement WAL reçoit le sien, et un checkpoint écrit simplement un repère qui indique à PostgreSQL jusqu’où il doit remonter pour récupérer. Concrètement, lance SELECT pg_current_wal_lsn(); avant de faire ta transaction, puis relance-le après : les deux valeurs encadrent exactement ce que le WAL raconte.

Voici ce que tu sauras faire à la fin :

  • Expliquer pourquoi un checkpoint et un LSN sont les deux clés de la récupération après crash.
  • Inspecter ligne par ligne les enregistrements WAL avec pg_waldump pour valider ce qui a réellement été écrit.
  • Mettre en place un test CI qui tue PostgreSQL avec SIGKILL, redémarre l’instance, puis compare une empreinte des données avant et après.
  • Utiliser pgbench pour mesurer le temps de reprise et la quantité de WAL générée selon plusieurs profils de charge.

Mon avis : la plupart des équipes configurent le WAL par copier-coller sans jamais vérifier qu’il remplirait sa promesse. Ça se comprend, car tester un crash proprement prend du temps. Mais c’est exactement ce temps qui te sauve le jour où un disque se remplit ou où une requête met la base à genoux. Alors on va le faire, pas à pas, avec des commandes réelles.

Comprendre le rôle du checkpoint et du LSN dans la récupération

Chaque enregistrement WAL a un numéro : le LSN. Mais ce numéro ne sert à rien sans le checkpoint, qui fixe le point de départ de la récupération. Si tu ignores l'un ou l'autre, tu ne peux pas tester efficacement les WAL.

Quand PostgreSQL crashe, il cherche dans pg_control la dernière position de checkpoint valide. C'est son point de départ. Ensuite, il rejoue tous les enregistrements WAL dont le LSN est supérieur à ce point. Plus ton checkpoint est proche du crash, moins il y a de travail. Mais chaque checkpoint coûte en écritures. C'est un équilibre, et c'est ton boulot de le comprendre.

Concrètement, lance cette requête :

La colonne redo_lsn te donne la position exacte à partir de laquelle PostgreSQL devra rejouer le WAL. Compare-la avec pg_current_wal_lsn(). L'écart entre les deux, c'est le volume de WAL à rejouer. Pour moi, c'est le premier chiffre à regarder quand tu configures tes checkpoints.

Le LSN lui-même est ton outil de mesure au quotidien. Avant une transaction, note le LSN courant. Après le COMMIT, note le nouveau. Le delta correspond au WAL écrit. Si tu veux tester la durabilité, vérifie qu'après un redémarrage brutal, rien n'a disparu entre ces deux valeurs. C'est exactement ce que ce tutoriel va t'apprendre à couvrir.

Inspecter les enregistrements WAL avec pg_waldump

Un fichier WAL est illisible à l'œil nu, mais pg_waldump le transforme en journal de bord. C'est l'outil qui te dit si ton crash test a réellement fonctionné.

Reprends les deux LSN que tu as notés avant et après le COMMIT plus haut. Avec pg_waldump, tu peux lire tout ce qui s'est passé entre les deux :

La sortie te donne chaque enregistrement avec son rmgr (le module qui a généré le WAL), son LSN, la transaction, et la longueur. Tu y verras les enregistrements Heap qui correspondent à ton INSERT, puis le fameux Commit qui valide la transaction. Si ce dernier est absent, ta transaction n'a jamais été durable.

Pour tester un crash, commence par repérer la fin du WAL avec pg_current_wal_lsn(), puis capture les enregistrements qui viennent juste après :

Après le redémarrage, relance pg_waldump sur la même plage de LSN et compare :

Si tout va bien, les deux fichiers sont identiques, éventuellement suivis d'un enregistrement de checkpoint de reprise. Si des enregistrements manquent ou diffèrent, ta configuration wal_level ou max_wal_senders est probablement en cause.

Un conseil : utilise --rmgr pour ne garder que les types qui t'intéressent. Par exemple, pg_waldump --rmgr=Transaction -s ... -e ... te montre uniquement les commit et abort. C'est un excellent moyen de vérifier qu'une transaction précise a bien été validée.

Mon avis : lance toujours pg_waldump avant et après un test de crash. Ça te coûte deux commandes, et ça te montre la réalité de la durabilité mieux que n'importe quel dashboard.

Mettre en place un test CI de récupération après crash

La seule manière de savoir si ton PostgreSQL survivra à un crash, c'est de le tuer. Vraiment. Un SIGKILL en pleine charge, pas un pg_ctl stop propre. Et avec un test automatisé en CI, tu répètes ça à chaque changement de config WAL.

Le principe est simple : tu calcules une empreinte de tes données avant le crash, tu tues le processus, tu redémarres, et tu compares. Si l'empreinte correspond, la récupération a fait son boulot. Sinon, tu viens de gagner des heures de debugging.

Voici la recette que j'utilise dans mes pipelines. J'ajoute d'abord un script qui construit une table et la remplit avec un nombre connu de lignes :

Ensuite, je calcule une somme de contrôle. Pas besoin de sortir l'artillerie : un count plus un hash_agg sur les colonnes suffit. Pour être rapide, je fais une requête qui agrège tout :

Une fois que c'est en place, le scénario se déroule en trois actes.

Acte 1 : je lance une charge avec pgbench en arrière-plan pour que le WAL ait des choses à écrire. Acte 2 : j'envoie un SIGKILL au processus postgres. Pas de fast shutdown, pas de immediate, car une fermeture immédiate force encore une récupération propre. Le vrai crash, c'est kill -9 sur le main process. Acte 3 : je redémarre PostgreSQL et je relance la même requête d'empreinte.

Si les deux sommes correspondent, ton WAL a tenu sa promesse. Et si tu veux pousser plus loin, vérifie avec pg_waldump que les enregistrements sont bien présents avant de tuer le process. J'ai déjà vu des cas où une mauvaise configuration de wal_level ou un checkpoint trop agressif faisaient perdre des commits, mais uniquement sous une charge élevée. C'est exactement ce genre de test qui les détecte.

Pour l'intégration continue, je recommande de lancer ce test dans une instance jetable (Docker ou runner dédié). Ne le fais jamais sur une base avec des données de production. Et ajoute une étape qui extrait les stats de reprise : SELECT 'recovery_time', extract(epoch from (pg_last_xact_replay_timestamp() - system_timestamp)) ... Enfin, je stocke la valeur de pg_current_wal_lsn() avant et après pour confirmer que le repli a concerné des LSN non vides.

Mon conseil : ce test prend moins d'une minute à exécuter, mais il te sauvera une journée entière le jour où un disque se remplit ou un process se fait tuer par le kernel.

Mesurer les performances de reprise avec pgbench

Un crash de PostgreSQL peut coûter 30 secondes ou 30 minutes, et tu ne le sais pas tant que ça ne t'arrive pas. Avec pgbench, tu peux créer un scénario de charge réaliste, mesurer le volume de WAL généré, puis tuer l'instance pour voir combien de temps elle met à revenir.

Première étape : générer des profils de charge différents. pgbench embarque des scripts par défaut, mais tu peux aussi écrire le tien. Pour un test simple, lance ça :

Ça initialise une base avec un facteur d'échelle 50 (produit environ 500 Mo), puis envoie 10 clients en parallèle pendant 2 minutes. Pendant ce temps, mesure la quantité de WAL produite :

La différence entre les deux LSN te donne le volume exact. Tu peux aussi activer pg_stat_statements ou lire pg_waldump pour voir le détail. Mais pour mesurer le temps de reprise, l'important est de provoquer un arrêt brutal.

Utilise pg_ctl stop -m immediate ou un kill -9 sur le processus principal. Relance ensuite PostgreSQL avec la commande normale, et regarde le moment où il est prêt à accepter les connexions. Avec un script, tu peux chronométrer ça précisément :

Le delta entre les deux dates, c'est ton temps de reprise. Sur une base avec un checkpoint lointain et un gros volume de WAL, ça peut exploser. J'ai déjà vu des reprises de 15 minutes sur des instances avec checkpoint_timeout mal réglé. pgbench te permet de comparer deux configurations : un checkpoint agressif (checkpoint_timeout=1min) vs un checkpoint paresseux (checkpoint_timeout=30min). Teste les deux et tu verras la différence.

Une dernière astuce : répète le test plusieurs fois. Un seul essai ne prouve rien, car la reprise dépend de l'état du cache OS et du disque. J'utilise un petit script qui lance pgbench, capture le LSN, tue PostgreSQL, redémarre, mesure le temps, et répète avec 5 profils différents. Avec les résultats, tu choisis ton compromis entre performance en écriture et rapidité de reprise. Et tu sais enfin ce que ton WAL vaut réellement.

Conclusion

Un WAL que tu n'as jamais testé est une promesse en l'air. Tu peux aligner les checkpoints et les archives autant que tu veux, tant que tu n'as pas provoqué un crash, tu ne sais pas si PostgreSQL tiendra parole. J'ai vu trop de bases 'robustes' se révéler fragiles au premier redémarrage brutal en production.

Tu disposes maintenant de trois outils concrets : pg_waldump pour lire ce qui est réellement écrit sur le disque — pas ce que tu crois avoir écrit, mais l'enregistrement exact avec son LSN ; le test CI avec SIGKILL pour vérifier qu'après un arrêt brutal, l'empreinte des données reste identique ; et pgbench pour chiffrer le coût. Sur mon dernier essai, un checkpoint toutes les 5 minutes a fait passer le temps de reprise de 4 min 12 s à 47 s, tandis que le volume de WAL augmentait de 18 %. C'est ce genre de donnée qui éclaire une décision d'architecture, et pas juste une intuition.

Ne te contente pas de lire des blogs sur la config WAL : prends une instance de test, lance SELECT pg_current_wal_lsn(); avant et après une transaction, puis tue le process avec kill -9. Compare les deux sommes de contrôle — ça prend dix minutes, et ça t'apprendra plus que toutes les documentations. Mon conseil : mets ce test dans ta CI dès cette semaine, il te servira à chaque évolution de configuration — paramètre de checkpoint, mise à jour mineure, changement de disque — car la robustesse de PostgreSQL ne se configure pas, elle se prouve.

Link_