Debian 13 : « Système de fichiers plein » a cassé votre base — les étapes de récupération qui fonctionnent (cas n°59)

Cet article vous a aidé ?

L’alerte indique « système de fichiers plein », l’application commence à renvoyer des 500, et votre base de données — auparavant tranquille — se comporte maintenant comme si elle avait oublié comment écrire.
Vous avez libéré « un peu d’espace » et redémarré tout le monde, et maintenant la base refuse de démarrer, rejoue les WAL/redo indéfiniment, ou pire : elle démarre et ment.

C’est un guide de récupération pour Debian 13 lorsque ENOSPC (aucun espace disponible sur le périphérique) a frappé votre base de données en production. Pas de théorie. Pas de « supprimez juste les logs ».
Un vrai runbook : comment trouver ce qui est vraiment plein, comment libérer de l’espace sans aggraver la cohérence, et comment atteindre un état sûr et vérifié.

Ce que « système de fichiers plein » fait réellement aux bases de données

« Disque plein » n’est pas un seul mode de défaillance. C’est une famille d’échecs qui ressemblent tous à la même chose sur un tableau de bord et qui se comportent très différemment à 03h00.
Sur Linux, vous pouvez être « plein » parce que les blocs sont épuisés, parce que les inodes sont épuisés, parce que votre processus ne peut pas allouer à cause de quotas,
ou parce que votre système de fichiers a de l’espace mais que votre base a besoin d’un espace plus ou moins contigu pour ses mécanismes de sécurité (pensez WAL, redo logs, fichiers temporaires).

Le moment le plus dangereux est les premières minutes après ENOSPC. Les bases de données réagissent en :

  • Échouant des écritures en plein milieu d’une transaction, laissant un état partiel qui doit être annulé ou rejoué.
  • Échouant des fsync. C’est alors que vous entrez dans le territoire « je pensais que c’était durable ».
  • Mal tournant les rotations de logs. Certains moteurs continuent d’écrire sur le même descripteur de fichier et ne « voient » pas que vous avez libéré de l’espace avant un redémarrage.
  • Bloquant la récupération parce que la récupération elle‑même a besoin d’espace temporaire.
  • Brisant la réplication parce que les WAL/binlogs ne peuvent pas être archivés ou streamés.

Votre travail est de restaurer l’invariant attendu par la base : suffisamment d’espace libre pour achever la récupération après crash, appliquer/annuler, checkpoint et reprendre des schémas d’écriture normaux.
« Suffisamment » n’est pas « quelques centaines de Mo ». En production, je vise au moins 10–20% d’espace libre sur le volume DB comme marge minimale de fonctionnement.
Si vous ne pouvez pas atteindre cela, traitez ceci comme une urgence de capacité, pas comme du ménage.

Une idée à retenir, paraphrasant Werner Vogels (CTO d’Amazon) : tout échoue, donc concevez pour détecter l’échec rapidement et récupérer automatiquement.
Les événements disque-plein sont la forme la plus ennuyeuse de défaillance — et la plus humiliante, car ils sont aussi les plus prévisibles.

Playbook de diagnostic rapide (premier/deuxième/troisième)

Vous êtes sous pression. Vous avez besoin d’une courte séquence qui identifie rapidement le goulot d’étranglement et évite le « nettoyage aléatoire » qui supprime des preuves ou rend la récupération plus difficile.
Faites cela dans l’ordre. Ne soyez pas créatif avant d’avoir fait les bases.

Premier : confirmer quel type de « plein » vous avez

  1. Blocs pleins ? Vérifiez df -h sur le point de montage DB.
  2. Inodes pleins ? Vérifiez df -i. L’épuisement d’inodes ressemble à « plein » même quand df -h semble OK.
  3. Fichiers supprimés mais ouverts ? Vérifiez lsof +L1. Vous pouvez « supprimer » des logs et ne rien récupérer.

Deuxième : identifier les plus gros écrivains, pas les plus gros fichiers

  1. Vérifiez la croissance récente des logs/journaux : journalctl --disk-usage, du sur /var/log.
  2. Vérifiez les répertoires temporaires et l’utilisation temporaire DB : /tmp, /var/tmp, équivalents Postgres base/pgsql_tmp.
  3. Vérifiez les couches d’images et les conteneurs le cas échéant : docker system df (ou votre runtime).

Troisième : décider de votre stratégie de récupération

  1. Si la DB ne démarre pas : libérez de l’espace d’abord, puis démarrez la DB, puis vérifiez la cohérence.
  2. Si la DB démarre mais renvoie des erreurs à l’écriture : maintenez-la en ligne juste le temps de capturer l’état et d’assécher le trafic ; puis corrigez la capacité.
  3. Si la réplication existe : envisagez une bascule vers une réplique saine plutôt que des réparations héroïques sur le primaire.

Blague n°1 : Les incidents disque-plein sont comme la gravité — tout le monde n’y croit « pas » jusqu’à ce qu’on tombe du toit.

Faits intéressants et contexte historique (points rapides)

  • Blocs réservés sur les systèmes de fichiers ext : ext2/3/4 réservent traditionnellement ~5% des blocs pour root afin d’éviter de tout briquer ; pratique pour les serveurs, déroutant pour les humains.
  • ENOSPC n’est pas que des blocs : la même erreur est souvent renvoyée pour épuisement d’inodes et quotas, d’où le cas où « df indique 40% libre » peut quand même être une crise.
  • Le journal n’est pas magique : le journaling ext4 protège la cohérence des métadonnées, pas la justesse logique de votre base. Votre DB a son propre journal pour une raison.
  • Vieille leçon Unix : supprimer un fichier ne libère pas l’espace tant qu’un processus le garde ouvert — cela est vrai depuis des décennies et provoque encore des incidents modernes.
  • L’amplification d’écriture est réelle : les bases transforment une écriture logique en plusieurs écritures physiques (WAL/redo + données + index + checkpoint). « Nous n’insérons que 1 Go/jour » est la façon de perdre des weekends.
  • Les inodes ont été dimensionnés pour les charges des années 1980 : les paramètres par défaut peuvent encore vous piéger avec des millions de petits fichiers (pensez caches), même sur des disques multi-téraoctets.
  • La récupération après crash demande de l’espace : Postgres peut avoir besoin d’espace pour rejouer les WAL et pour les fichiers temporaires ; InnoDB peut devoir étendre ses logs ; on ne récupère pas sur la réserve.
  • Les sémantiques de système de fichiers diffèrent : XFS se comporte différemment d’ext4 sous pression (et gère les suppressions/fichiers ouverts de façon similaire) ; ZFS a sa propre culture de « ne pas remplir le pool au-delà d’environ 80% » pour des raisons de performance.

Stabiliser d’abord : arrêter l’hémorragie en sécurité

Quand un système de fichiers est plein, la mauvaise décision est de redémarrer frénétiquement. Les redémarrages peuvent transformer « temporairement bloqué mais cohérent » en « boucle de récupération qui nécessite plus de disque ».
La stabilisation signifie : réduire les écritures, conserver les preuves et éviter de faire faire du travail supplémentaire à la DB jusqu’à ce que vous ayez de la marge.

Tâche 1 : confirmer ce qui échoue au niveau service

cr0x@server:~$ systemctl --failed
  UNIT                      LOAD   ACTIVE SUB    DESCRIPTION
● postgresql@16-main.service loaded failed failed PostgreSQL Cluster 16-main

Ce que ça signifie : systemd confirme que votre DB échoue, pas seulement votre application.
Décision : n’envoyez pas de redémarrages en rafale. Passez aux logs et à l’état du disque.

Tâche 2 : capturer les dernières erreurs pertinentes avant qu’elles ne soient archivées

cr0x@server:~$ journalctl -u postgresql@16-main.service -n 200 --no-pager
Dec 30 02:11:44 db1 postgres[9123]: FATAL:  could not write to log file: No space left on device
Dec 30 02:11:44 db1 postgres[9123]: PANIC:  could not write to file "pg_wal/00000001000000A9000000FF": No space left on device
Dec 30 02:11:44 db1 systemd[1]: postgresql@16-main.service: Main process exited, code=exited, status=1/FAILURE

Ce que ça signifie : les écritures WAL ont échoué. Ce n’est pas « agréable à avoir » ; c’est la durabilité au cœur.
Décision : votre premier objectif est de restaurer suffisamment d’espace pour les WAL et la récupération après crash.

Tâche 3 : arrêter le trafic ou mettre la DB en maintenance

cr0x@server:~$ systemctl stop myapp.service
cr0x@server:~$ systemctl stop nginx.service

Ce que ça signifie : vous réduisez la pression d’écriture pendant la récupération.
Décision : si vous avez un basculement en lecture seule, utilisez-le ; sinon acceptez un temps d’indisponibilité plutôt que la corruption.

Tâche 4 : geler l’état du processus DB (s’il boucle) plutôt que de tuer immédiatement

cr0x@server:~$ systemctl kill -s SIGSTOP postgresql@16-main.service
cr0x@server:~$ systemctl status postgresql@16-main.service | sed -n '1,12p'
● postgresql@16-main.service - PostgreSQL Cluster 16-main
     Loaded: loaded (/lib/systemd/system/postgresql@.service; enabled)
     Active: activating (start) since Tue 2025-12-30 02:12:01 UTC; 3min ago
    Process: 10455 ExecStart=/usr/bin/pg_ctlcluster --skip-systemctl-redirect 16-main start (code=exited, status=1/FAILURE)
   Main PID: 10501 (code=killed, signal=STOP)

Ce que ça signifie : le processus est mis en pause, il ne surcharge pas le disque.
Décision : faites cela s’il tente de récupérer en boucle et grignote l’espace que vous libérez ; reprenez avec SIGCONT une fois que vous avez de la marge.

Une règle pratique : si vous n’êtes pas sûr de ce qu’il faut supprimer, arrêtez d’abord les écrivains. Vous pouvez toujours redémarrer les services ; vous ne pouvez pas remettre un fichier supprimé en cas d’incident de cohérence.

Trouver de l’espace comme si vous en aviez besoin (commandes + décisions)

Vous devez répondre rapidement à quatre questions :
Quel système de fichiers est plein ? Est-ce des blocs, des inodes, des quotas ou des « fichiers supprimés mais ouverts » ?
Qui écrit ? Puis-je créer un espace libre stable qui reste libre ?

Tâche 5 : identifier le(s) système(s) de fichiers plein(s)

cr0x@server:~$ df -hT
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4    80G   79G     0 100% /
/dev/nvme1n1p1 ext4   1.8T  1.2T  520G  70% /var/lib/postgresql

Ce que ça signifie : le système root est complètement rempli ; le montage DB est correct. Cela casse néanmoins la DB si elle journalise vers /var/log ou utilise /tmp sur /.
Décision : concentrez-vous sur /, pas sur le volume DB. « Le disque DB est à 70% » ne vous sauve pas.

Tâche 6 : vérifier l’épuisement d’inodes

cr0x@server:~$ df -i
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2 5242880 5242880       0  100% /
/dev/nvme1n1p1 61054976  712345 60342631    2% /var/lib/postgresql

Ce que ça signifie : vous êtes à court d’inodes sur /. Cela provient souvent de caches, de nombreux petits fichiers ou d’une file de mail.
Décision : supprimer un gros fichier ne suffira pas. Il faut supprimer beaucoup de petits fichiers ou déplacer la charge.

Tâche 7 : détecter les fichiers supprimés mais ouverts (le classique « je l’ai supprimé, pourquoi c’est encore plein ? »)

cr0x@server:~$ sudo lsof +L1 | head -n 15
COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF NLINK    NODE NAME
rsyslogd  721 syslog  6w   REG  259,2 2147483648     0 1049883 /var/log/syslog.1 (deleted)
java     2041 app    12w   REG  259,2 1073741824     0 1051122 /var/log/myapp/app.log (deleted)

Ce que ça signifie : l’espace est toujours détenu par des processus en cours d’exécution. Le nom de fichier a disparu, mais l’inode est toujours alloué.
Décision : redémarrez ou signalez ces processus pour qu’ils ferment/rouvrent les logs (par ex. systemctl restart rsyslog), ou tronquez via /proc/<pid>/fd/<fd> si nécessaire.

Tâche 8 : trouver quel répertoire sur le système de fichiers plein est en cause

cr0x@server:~$ sudo du -xhd1 / | sort -h
0	/boot
1.2G	/etc
2.8G	/home
4.5G	/opt
9.9G	/usr
12G	/var
49G	/

Ce que ça signifie : /var est volumineux sur root ; c’est typiquement des logs, caches, spool, ou déchets de conteneurs.
Décision : creusez dans /var ensuite, en restant sur le même système de fichiers (-x est important).

Tâche 9 : identifier les plus gros consommateurs à l’intérieur de /var

cr0x@server:~$ sudo du -xhd1 /var | sort -h
120M	/var/cache
260M	/var/tmp
1.1G	/var/lib
1.7G	/var/spool
8.4G	/var/log
12G	/var

Ce que ça signifie : les logs sont massifs. C’est courant, mais ne supposez pas qu’il soit sûr de tout supprimer.
Décision : inspectez /var/log, en particulier les journaux et les logs applicatifs.

Tâche 10 : vérifier l’utilisation et les limites de systemd-journald

cr0x@server:~$ journalctl --disk-usage
Archived and active journals take up 6.8G in the file system.

Ce que ça signifie : journald occupe une part significative. Sur de petites partitions root, il peut silencieusement gagner le concours « qui a mangé mon disque ».
Décision : vacuumisez les journaux à une taille sûre, puis définissez des limites persistantes pour éviter la récurrence.

Tâche 11 : trouver rapidement les gros fichiers (triage par taille)

cr0x@server:~$ sudo find /var/log -xdev -type f -size +200M -printf '%s %p\n' | sort -n | tail -n 10
268435456 /var/log/journal/3b2b1a.../system@0000000000000000-0000000000000000.journal
536870912 /var/log/myapp/app.log
2147483648 /var/log/syslog.1

Ce que ça signifie : vous avez quelques fichiers très volumineux. C’est le type de nettoyage le plus simple — s’ils ne sont pas ouverts.
Décision : si les logs sont ouverts, redémarrez le logger/l’application après avoir tronqué/rotaté correctement.

Tâche 12 : vérifier les caches de paquets (plutôt sûrs à supprimer)

cr0x@server:~$ sudo du -sh /var/cache/apt/archives
1.1G	/var/cache/apt/archives

Ce que ça signifie : le cache apt est non négligeable.
Décision : le nettoyer est généralement sûr et rapide ; cela ne change pas l’état de la DB.

Tâche 13 : vérifier si le volume DB lui-même a une bloat cachée (WAL, temp, sauvegardes)

cr0x@server:~$ sudo du -sh /var/lib/postgresql /var/lib/postgresql/* 2>/dev/null | sort -h | tail -n 8
48G	/var/lib/postgresql/16
48G	/var/lib/postgresql/16/main
7.9G	/var/lib/postgresql/16/main/pg_wal

Ce que ça signifie : les WAL sont importants mais pas nécessairement incorrects — cela peut être dû à une réplique en panne, un archivage défaillant, ou des transactions longues.
Décision : ne supprimez pas manuellement les fichiers WAL. Corrigez la cause amont (réplication/archivage) et laissez Postgres gérer la rétention.

Tâche 14 : vérifier la pression mémoire et swap (car la récupération nécessite RAM et tmp)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            32Gi        26Gi       1.1Gi       214Mi       4.9Gi       3.8Gi
Swap:            0B          0B          0B

Ce que ça signifie : vous êtes juste en RAM disponible, pas de swap. Certaines tâches de récupération DB débordent vers des fichiers temporaires ; une faible RAM peut amplifier l’utilisation disque et la durée.
Décision : évitez d’exécuter des maintenances lourdes (VACUUM FULL, OPTIMIZE) pendant la récupération ; stabilisez d’abord.

Libérer de l’espace en sécurité (quoi supprimer, quoi éviter)

La priorité est de créer de l’espace libre stable — de l’espace qui reste libre après la reprise des services.
Supprimer des fichiers au hasard peut ralentir le démarrage du système, faire perdre des pistes d’audit ou casser l’état des paquets. Supprimer les mauvais fichiers DB peut ruiner votre semaine.

Gains à faible risque (faites-les en premier)

Tâche 15 : vacuumiser le journal systemd à une taille bornée

cr0x@server:~$ sudo journalctl --vacuum-size=500M
Vacuuming done, freed 6.3G of archived journals from /var/log/journal.

Ce que ça signifie : vous avez récupéré de l’espace réel. C’est typiquement sûr pendant les incidents.
Décision : si cela libère peu, journald n’est pas votre principal coupable ; passez à autre chose.

Tâche 16 : définir des limites journald pour ne pas répéter cela demain

cr0x@server:~$ sudo sed -i 's/^#SystemMaxUse=.*/SystemMaxUse=500M/' /etc/systemd/journald.conf
cr0x@server:~$ sudo systemctl restart systemd-journald

Ce que ça signifie : vous avez contraint la croissance future des logs.
Décision : choisissez une valeur adaptée à votre disque et à vos exigences de rétention ; le bon nombre dépend de vos besoins d’intervention.

Tâche 17 : vider le cache apt

cr0x@server:~$ sudo apt-get clean
cr0x@server:~$ sudo du -sh /var/cache/apt/archives
4.0K	/var/cache/apt/archives

Ce que ça signifie : vous avez récupéré un morceau d’espace sans toucher à l’état applicatif.
Décision : faites-le pour un soulagement rapide ; ce n’est pas une correction de la cause racine.

Tâche 18 : faire tourner/tronquer proprement les logs applicatifs qui partent en vrille

cr0x@server:~$ sudo truncate -s 0 /var/log/myapp/app.log
cr0x@server:~$ sudo systemctl restart myapp.service

Ce que ça signifie : la troncature libère immédiatement de l’espace (à moins que le fichier soit remplacé via les patterns de logrotate).
Décision : ne tronquez que les logs que vous pouvez vous permettre de perdre. Préférez les corrections logrotate après l’incident.

Tâche 19 : résoudre les logs supprimés mais ouverts en redémarrant le bon daemon

cr0x@server:~$ sudo systemctl restart rsyslog.service
cr0x@server:~$ sudo lsof +L1 | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME

Ce que ça signifie : plus (ou moins) de fichiers supprimés mais ouverts listés.
Décision : si l’utilisation d’espace ne baisse pas, votre problème n’est pas des fichiers ouverts ; re-vérifiez df et les inodes.

Mouvements à risque modéré (faites-les avec intention)

Ceux-ci peuvent être sûrs, mais ne sont pas sans conséquences.

Tâche 20 : nettoyer les gros caches avec un propriétaire connu (exemple : répertoire cache applicatif)

cr0x@server:~$ sudo du -sh /var/cache/myapp
3.4G	/var/cache/myapp
cr0x@server:~$ sudo rm -rf /var/cache/myapp/*
cr0x@server:~$ sudo du -sh /var/cache/myapp
12K	/var/cache/myapp

Ce que ça signifie : vous avez récupéré de l’espace, mais vous pouvez augmenter la charge lorsque le cache se réchauffe.
Décision : acceptable pendant l’incident, mais coordonnez-vous avec les propriétaires d’app ; surveillez CPU et latence après redémarrage.

Tâche 21 : nettoyer les core dumps (souvent énormes, souvent oubliés)

cr0x@server:~$ sudo coredumpctl list | head
TIME                            PID   UID   GID SIG COREFILE  EXE
Tue 2025-12-30 00:13:19 UTC    8123  1001  1001  11 present   /usr/bin/myapp
cr0x@server:~$ sudo du -sh /var/lib/systemd/coredump
5.2G	/var/lib/systemd/coredump
cr0x@server:~$ sudo rm -f /var/lib/systemd/coredump/*
cr0x@server:~$ sudo du -sh /var/lib/systemd/coredump
0	/var/lib/systemd/coredump

Ce que ça signifie : vous avez supprimé des artefacts de diagnostic.
Décision : faites-le seulement si vous avez déjà capturé ce dont vous avez besoin pour le debugging, ou si la disponibilité prime sur le post-mortem.

Mouvements à haut risque (éviter pendant la récupération)

  • Supprimer des fichiers de base de données sous /var/lib/postgresql ou /var/lib/mysql parce qu’ils « ont l’air gros ». C’est ainsi que vous créez un second incident.
  • Lancer une maintenance DB agressive (VACUUM FULL, REINDEX DATABASE, OPTIMIZE TABLE) alors que vous manquez d’espace. Ces opérations nécessitent souvent plus d’espace pour se terminer.
  • Déplacer le répertoire de la base à la volée sans plan testé. Si vous devez déplacer le stockage, faites un arrêt contrôlé, copie/rsync, vérifiez, mettez à jour la config du service, puis démarrez.

Blague n°2 : Le moyen le plus rapide de libérer du disque est de supprimer /var/lib ; le moyen le plus rapide de mettre à jour votre CV est de le faire en production.

Étapes de récupération pour bases de données qui fonctionnent vraiment

« Qui fonctionnent vraiment » ici signifie : vous terminez avec une base qui démarre, accepte les écritures et est suffisamment cohérente pour être fiable — soutenue par des vérifications, pas des impressions.
Les étapes exactes dépendent de votre moteur, mais la forme est constante :
faire de l’espace → démarrer en sécurité → confirmer l’intégrité → corriger le vecteur de croissance → restaurer les marges.

Étape zéro : obtenir une vraie marge

Avant de relancer la DB, visez au moins :

  • PostgreSQL : espace libre ≥ WAL nécessaire pour le replay + quelques fichiers temporaires ; je vise 10–20% du volume du cluster, ou au moins plusieurs Go pour les petits systèmes.
  • MySQL/InnoDB : assez pour l’activité redo/undo et les tables temporaires ; là encore, 10–20% sur le système de fichiers hébergeant datadir et tmpdir.

Tâche 22 : vérifier l’espace libre après nettoyage (ne pas supposer)

cr0x@server:~$ df -hT /
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4   80G   62G   14G  82% /

Ce que ça signifie : vous avez maintenant 14G de libre sur root. C’est une marge utilisable pour les logs, temporaires et la récupération.
Décision : procédez au redémarrage de la DB. Si vous êtes encore >95%, continuez à libérer de l’espace ou étendez le système de fichiers d’abord.

PostgreSQL sur Debian 13 (service cluster)

Le packaging PostgreSQL de Debian utilise des clusters (ex. postgresql@16-main), et il se comporte généralement bien pendant la récupération — si vous lui donnez de l’espace.
Les deux principales erreurs avec Postgres après disque-plein sont : supprimer des WAL manuellement, et redémarrer en boucle sans laisser la récupération finir.

Tâche 23 : reprendre la DB en pause (si vous avez utilisé SIGSTOP), puis démarrer proprement

cr0x@server:~$ sudo systemctl kill -s SIGCONT postgresql@16-main.service
cr0x@server:~$ sudo systemctl start postgresql@16-main.service
cr0x@server:~$ sudo systemctl status postgresql@16-main.service | sed -n '1,12p'
● postgresql@16-main.service - PostgreSQL Cluster 16-main
     Loaded: loaded (/lib/systemd/system/postgresql@.service; enabled)
     Active: active (running) since Tue 2025-12-30 02:24:09 UTC; 3s ago

Ce que ça signifie : le service est en route. Pas encore prouvé sain, mais il respire.
Décision : vérifiez les logs pour la fin de récupération et assurez-vous qu’il accepte des connexions.

Tâche 24 : confirmer la fin de la récupération crash dans les logs

cr0x@server:~$ sudo journalctl -u postgresql@16-main.service -n 80 --no-pager
Dec 30 02:24:08 db1 postgres[11001]: LOG:  database system was interrupted; last known up at 2025-12-30 02:10:21 UTC
Dec 30 02:24:08 db1 postgres[11001]: LOG:  redo starts at A9/FF000028
Dec 30 02:24:09 db1 postgres[11001]: LOG:  redo done at A9/FF9A2B30
Dec 30 02:24:09 db1 postgres[11001]: LOG:  database system is ready to accept connections

Ce que ça signifie : le replay WAL est terminé et Postgres a déclaré sa disponibilité.
Décision : passez aux vérifications d’intégrité et à la réintroduction progressive de la charge.

Tâche 25 : vérifier la connectivité DB et une lecture/écriture basique

cr0x@server:~$ sudo -u postgres psql -d postgres -c "select now();"
              now
-------------------------------
 2025-12-30 02:24:31.12345+00
(1 row)

cr0x@server:~$ sudo -u postgres psql -d postgres -c "create table if not exists diskfull_probe(x int); insert into diskfull_probe values (1);"
CREATE TABLE
INSERT 0 1

Ce que ça signifie : vous pouvez vous connecter et commit.
Décision : gardez cette table probe ou supprimez-la plus tard ; l’objectif est de vérifier que les écritures sont possibles.

Tâche 26 : vérifier les erreurs persistantes « out of space » au niveau SQL

cr0x@server:~$ sudo -u postgres psql -d postgres -c "select datname, temp_bytes, deadlocks from pg_stat_database;"
  datname  | temp_bytes | deadlocks
-----------+------------+-----------
 postgres  |          0 |         0
 template1 |          0 |         0
(2 rows)

Ce que ça signifie : l’utilisation temporaire est actuellement minimale ; pas d’artéfacts évidents de contention.
Décision : si temp_bytes explose, votre charge déborde sur le disque ; assurez-vous que temp_tablespaces et la marge du système de fichiers sont adéquats.

Tâche 27 : exécuter une vérification d’intégrité ciblée (pas une panique sur l’ensemble des tables)

cr0x@server:~$ sudo -u postgres psql -d postgres -c "select * from pg_stat_wal;"
 wal_records | wal_fpi | wal_bytes | wal_buffers_full | wal_write | wal_sync
-------------+---------+-----------+------------------+----------+----------
       18234 |     112 |  12345678 |                0 |      214 |       93
(1 row)

Ce que ça signifie : le sous-système WAL fonctionne. Ce n’est pas une garantie d’absence de corruption, mais c’est un signal « ce n’est pas en feu pour l’instant ».
Décision : si vous suspectez une corruption, planifiez amcheck ou restaurez depuis une sauvegarde ; n’improvisez pas pendant la fenêtre d’incident.

MySQL / MariaDB (InnoDB) sur Debian 13

InnoDB gère la récupération après crash en rejouant les redo logs. Le disque-plein peut interrompre cela et vous laisser avec un service qui ne démarre pas ou démarre en lecture seule.
Le pire geste est de supprimer ib_logfile ou ibdata pour « forcer » un démarrage. Ce n’est pas une réparation ; c’est une perte de données avec des étapes supplémentaires.

Tâche 28 : lire les derniers messages de récupération InnoDB

cr0x@server:~$ journalctl -u mariadb.service -n 120 --no-pager
Dec 30 02:13:02 db1 mariadbd[9322]: InnoDB: Error: Write to file ./ib_logfile0 failed at offset 1048576.
Dec 30 02:13:02 db1 mariadbd[9322]: InnoDB: Error: 28  No space left on device
Dec 30 02:13:02 db1 mariadbd[9322]: InnoDB: Plugin initialization aborted with error Generic error
Dec 30 02:13:02 db1 systemd[1]: mariadb.service: Main process exited, code=exited, status=1/FAILURE

Ce que ça signifie : l’écriture du redo log a échoué. La récupération ne peut pas progresser sans espace.
Décision : libérez de l’espace sur le système de fichiers contenant datadir (souvent /var/lib/mysql) et tout tmpdir configuré.

Tâche 29 : confirmer les emplacements datadir et tmpdir avant de courir sur le mauvais montage

cr0x@server:~$ sudo my_print_defaults --mysqld | egrep -i 'datadir|tmpdir'
--datadir=/var/lib/mysql
--tmpdir=/tmp

Ce que ça signifie : tmpdir est sur /. Si root est plein, les requêtes créant des tables temporaires échoueront même si /var/lib/mysql est OK.
Décision : envisagez de déplacer tmpdir vers un système plus grand après la récupération (et testez-le).

Tâche 30 : démarrer MariaDB/MySQL et surveiller la progression de la récupération

cr0x@server:~$ sudo systemctl start mariadb.service
cr0x@server:~$ sudo systemctl status mariadb.service | sed -n '1,14p'
● mariadb.service - MariaDB 10.11.6 database server
     Loaded: loaded (/lib/systemd/system/mariadb.service; enabled)
     Active: active (running) since Tue 2025-12-30 02:27:40 UTC; 2s ago

Ce que ça signifie : il a démarré. Maintenant confirmez qu’il n’est pas silencieusement en difficulté.
Décision : vérifiez le log d’erreur et exécutez des requêtes basiques.

Tâche 31 : valider lecture/écriture basique et l’état d’InnoDB

cr0x@server:~$ sudo mariadb -e "select @@version, now();"
@@version	now()
10.11.6-MariaDB-0+deb13u1	2025-12-30 02:28:03

cr0x@server:~$ sudo mariadb -e "create database if not exists diskfull_probe; use diskfull_probe; create table if not exists t(x int); insert into t values (1);"
cr0x@server:~$ sudo mariadb -e "show engine innodb status\G" | sed -n '1,35p'
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
2025-12-30 02:28:11 0x7f9c2c1fe6c0 INNODB MONITOR OUTPUT
...
Log sequence number 123456789
Log flushed up to   123456789

Ce que ça signifie : le flush du redo log a rattrapé son retard ; les commits basiques fonctionnent.
Décision : ramenez le trafic progressivement, surveillez les taux d’erreur et l’utilisation du disque de près.

Si la DB ne démarre toujours pas : quoi faire (et quoi éviter)

Si vous avez créé de la marge et que la DB échoue encore, n’allez pas immédiatement chercher les modes « forced recovery » sauf si vous êtes prêt à restaurer depuis une sauvegarde.
La récupération forcée peut servir à extraire des données, pas à revenir en service normal.

Tâche 32 : vérifier les permissions et les signaux de santé du système de fichiers (sanity rapide)

cr0x@server:~$ sudo dmesg -T | tail -n 20
[Thu Dec 30 02:22:11 2025] EXT4-fs warning (device nvme0n1p2): ext4_dx_add_entry: Directory index full!
[Thu Dec 30 02:22:12 2025] EXT4-fs (nvme0n1p2): Delayed block allocation failed for inode 3932211 at logical offset 0 with max blocks 2 with error 28

Ce que ça signifie : le kernel a observé des échecs d’allocation cohérents avec ENOSPC. Pas de signe immédiat d’erreurs média ici, mais vous lisez la vérité terrain.
Décision : si vous voyez des erreurs I/O, des remontées read-only, ou des avertissements de corruption, arrêtez et planifiez une restauration/migration.

Tâche 33 : si vous êtes sur LVM, étendez le système de fichiers au lieu de jouer au nettoyage whack-a-mole

cr0x@server:~$ sudo vgs
  VG   #PV #LV #SN Attr   VSize   VFree
  vg0    1   2   0 wz--n- 200.00g  40.00g

cr0x@server:~$ sudo lvextend -L +20G /dev/vg0/root
  Size of logical volume vg0/root changed from 80.00 GiB (20480 extents) to 100.00 GiB (25600 extents).
  Logical volume vg0/root successfully resized.

cr0x@server:~$ sudo resize2fs /dev/vg0/root
resize2fs 1.47.0 (5-Feb-2023)
Filesystem at /dev/vg0/root is mounted on /; on-line resizing required
old_desc_blocks = 10, new_desc_blocks = 13
The filesystem on /dev/vg0/root is now 26214400 (4k) blocks long.

cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/mapper/vg0-root   99G   62G   33G  66% /

Ce que ça signifie : vous avez converti un incident récurrent en capacité.
Décision : si vous pouvez étendre, étendez. Le nettoyage est une solution temporaire ; la capacité est une correction.

Listes de contrôle / plan pas-à-pas (mentalité imprimable)

Checklist A : réponse à l’incident pour « système de fichiers plein a cassé la DB »

  1. Arrêter les services générant beaucoup d’écritures (workers d’app, ingestion, cron jobs). Gardez la DB arrêtée jusqu’à ce que vous ayez de la marge.
  2. Capturer les logs de systemd et des logs DB avant de les archiver/tronquer.
  3. Confirmer le mode de défaillance : blocs (df -h), inodes (df -i), supprimé-ouvert (lsof +L1), quotas.
  4. Libérer de l’espace à faible risque : vacuum journald, cache apt, caches connus sûrs, rotation des logs.
  5. Re-vérifier l’espace libre et assurer la marge (ne vous contentez pas de 200 Mo).
  6. Démarrer la DB une seule fois, et laissez-la récupérer. Ne relancez pas en boucle.
  7. Vérifier la disponibilité : la DB indique « ready », connectivité, test d’écriture basique.
  8. Rétablir le trafic progressivement : d’abord des workers canary, puis monter en charge.
  9. Corriger la cause de la croissance : rotation des logs, archivage bloqué, requête générant des temporaires, conteneurs, sauvegardes.
  10. Mettre des garde-fous : monitoring, quotas, limites journald, seuils d’alerte, plan de capacité.

Checklist B : durcissement post-incident (prévenir le cas n°60)

  1. Alerter sur les deux : blocs et inodes. Beaucoup d’équipes alertent seulement sur les blocs et sont surprises ensuite.
  2. Mettre les chemins temporaires DB sur un volume avec marge. Pour Postgres, envisagez un tablespace dédié pour les temporaires si pertinent.
  3. Faire respecter des limites de logs (journald + logs applicatifs) et tester logrotate sous charge.
  4. Conserver au moins une voie de restauration testée (restauration de sauvegarde ou promotion de réplique) qui ne nécessite pas de héros.
  5. Organiser périodiquement un « disk full game day » en staging : simuler ENOSPC et valider le temps et les étapes de récupération.

Trois mini-histoires d’entreprise depuis le terrain

1) L’incident causé par une mauvaise hypothèse : « La DB a son propre disque, donc root peut se remplir »

Une entreprise SaaS de taille moyenne avait séparé le stockage « correctement » : les données DB sur un grand montage séparé, root sur une petite partition NVMe.
L’équipe se sentait vertueuse. Le volume DB avait beaucoup d’espace ; les graphiques étaient verts. La rotation on-call s’est détendue.

Puis un changement anodin a été déployé : un flag de debug verbeux activé par erreur pour un service d’authentification.
Les logs ont explosé dans /var/log sur root. En quelques heures, root est arrivé à 100%. Le volume de la DB avait encore des centaines de gigaoctets libres,
donc le responsable d’incident a d’abord écarté le stockage comme cause et a poursuivi des fausses pistes réseau et CPU.

PostgreSQL a commencé à échouer des écritures — pas parce que /var/lib/postgresql était plein, mais parce qu’il ne pouvait pas écrire ses logs et créer des fichiers temporaires.
Des boucles de récupération ont commencé : systemd tentait des redémarrages ; chaque redémarrage essayait de journaliser ; chaque écriture de log échouait ; le service clapait.
Pendant ce temps, l’application, voyant des échecs de connexion, a relancé agressivement, amplifiant le problème.

La correction a été douloureusement simple : vacuum journald, faire tourner le log runaway de l’app, arrêter la tempête de retries, et seulement alors redémarrer la DB une fois.
L’action post-mortem qui a compté n’était pas « ajouter plus de disque ». C’était « traiter root comme une dépendance de la DB », et alerter en conséquence.

2) L’optimisation qui a échoué : « Désactiver la compression de logrotate, ça bouffe du CPU »

Une grande équipe plateforme voulait réduire les pics CPU sur une flotte de proxies de base de données Debian.
Quelqu’un a remarqué que la compression de logrotate bouffait du CPU pendant les heures de pointe. Pensée raisonnable : désactiver la compression et garder la rotation.
Le CPU s’est aplati. Tout le monde s’est auto-congratulé et a continué.

Deux semaines plus tard, plusieurs nœuds ont commencé à atteindre disque-plein à peu près en même temps. Les bases proxifiées allaient bien ; les proxies non.
Les logs rotatés non compressés étaient maintenant énormes, et la rétention était réglée pour la « taille compressée », pas la taille brute. Les partitions root étaient petites parce que « elles ne font pas grand-chose ».
L’incident s’est manifesté par des churns de connexion et des retries en cascade — comportement classique des systèmes distribués : une petite faute devient le problème de tout le monde.

L’effort de récupération est devenu compliqué parce que les ingénieurs continuaient de supprimer d’anciens logs alors que rsyslog tenait encore des descripteurs ouverts.
L’espace ne revenait pas, ce qui a entraîné plus de suppressions, puis moins de logs forensiques. Ils ont fini par résoudre en redémarrant rsyslog, puis en mettant des politiques logrotate sensées,
et en déplaçant les logs à fort volume sur un système dédié.

Leçon : les optimisations qui suppriment des garde-fous ne sont rarement gratuites. Le CPU est habituellement plus facile à racheter que le temps de récupération propre.
Si vous désactivez la compression, vous devez re-régler la rétention et l’alerte, sinon le disque devient votre nouvel ordonnanceur.

3) La pratique ennuyeuse mais correcte qui a sauvé la mise : « Toujours garder une réplique avec runbooks de promotion réels »

Une équipe de services financiers faisait tourner PostgreSQL avec une réplique streaming dans un rack différent. Rien de sophistiqué.
La partie ennuyeuse : une fois par trimestre, ils s’exerçaient à promouvoir la réplique, mettre à jour les configs applicatives, et démotionner/reconstruire l’ancien primaire.
Ils traitaient ça comme des exercices d’incendie — agaçants, planifiés et non optionnels.

Pendant un lot de fin de mois, le primaire a rempli son système de fichiers à cause d’une mauvaise configuration d’archivage qui a fait gonfler la rétention WAL.
Les écritures se sont arrêtées. Les tentatives de récupération se sont battues pour le disque. L’équipe n’a pas tenté de chirurgie sur l’hôte pendant que les parties prenantes commerciales regardaient.
Ils ont confirmé que la réplique était suffisamment à jour, l’ont promue, et ont restauré le service avec un minimum de drame.

Après le basculement, ils ont pris leur temps : ils ont étendu le stockage, corrigé l’archivage, validé les sauvegardes et reconstruit proprement la réplication.
Le post-mortem ressemblait à une liste de courses, pas à un roman à suspense. C’est l’objectif. L’ennuyeux est une caractéristique en opérations.

Erreurs courantes : symptôme → cause racine → correction

1) « df montre 0 octet libéré après suppression des logs »

  • Symptôme : vous supprimez de gros fichiers, mais df -h reste à 100%.
  • Cause racine : les fichiers ont été supprimés mais sont encore détenus ouverts par des processus.
  • Correction : lsof +L1 ; redémarrez le service propriétaire ou tronquez le FD ouvert via /proc/<pid>/fd/<fd>. Puis re-vérifiez df.

2) « df -h a l’air correct, mais tout échoue ‘No space left on device’ »

  • Symptôme : des Go disponibles, pourtant les créations/écritures échouent.
  • Cause racine : épuisement d’inodes (df -i) ou limites de quota/projet.
  • Correction : supprimer un grand nombre de petits fichiers dans le répertoire en faute ; pour les quotas, inspecter et augmenter les limites ; pour les inodes, arrêter le workload qui crée des fichiers et repenser la disposition du stockage.

3) « La DB démarre, mais l’application a des échecs d’écriture intermittents »

  • Symptôme : le service est en ligne, mais certaines écritures échouent, les opérations sur tables temporaires échouent, ou les tris plantent.
  • Cause racine : tmpdir ou répertoire de logs est sur un système encore plein (souvent root), alors que le datadir est OK.
  • Correction : confirmer les chemins tmpdir (l’emplacement des temporaires Postgres varie ; tmpdir MySQL est configurable). Libérez/étendez le bon système de fichiers ; déplacez tmpdir vers un montage plus grand avec des modifications de configuration testées.

4) « Le répertoire WAL de Postgres est énorme ; supprimons les anciens WAL »

  • Symptôme : pg_wal consomme des dizaines de Go.
  • Cause racine : un replication slot retient WAL, réplique en panne, archivage en échec, ou transactions longues empêchent le nettoyage.
  • Correction : identifier les replication slots et le lag ; réparer l’archivage ; supprimer les slots inutilisés ; résoudre les transactions longues. Ne supprimez pas les fichiers WAL à la main.

5) « MySQL ne démarre pas ; quelqu’un suggère de supprimer ib_logfile0 »

  • Symptôme : erreurs d’initialisation InnoDB après ENOSPC.
  • Cause racine : écritures redo incomplètes dues au disque-plein et espace insuffisant pour la récupération.
  • Correction : restaurer l’espace disque, démarrer proprement, vérifier l’état d’InnoDB. Si la corruption persiste, utilisez sauvegardes/répliques ; la récupération forcée sert à l’extraction, pas au rétablissement normal.

6) « Nous avons libéré 2GB ; pourquoi la récupération échoue encore ? »

  • Symptôme : la récupération démarre puis échoue à nouveau avec ENOSPC.
  • Cause racine : la récupération elle-même génère des écritures (replay WAL, tmp, checkpoints). 2GB n’est pas une stratégie.
  • Correction : libérez/étendez jusqu’à avoir une marge significative (10–20% ou plusieurs Go selon la taille et la charge DB), puis réessayez une seule fois.

7) « Après nettoyage, le disque se remplit instantanément »

  • Symptôme : vous libérez de l’espace, redémarrez les services, et c’est de nouveau à 100% en quelques minutes.
  • Cause racine : spam de logs, tempête de retries, file bloquée ou job batch qui reprend.
  • Correction : maintenez les écrivains arrêtés ; identifiez le principal écrivain ; ajoutez des limites de débit ; corrigez les niveaux de log ; vidangez les files avec précaution ; réintroduisez le trafic ensuite.

FAQ

1) « système de fichiers plein » est‑ce la même chose que « disque plein » ?

Pas toujours. Un système de fichiers peut être « plein » parce que les blocs sont épuisés, les inodes sont épuisés, ou l’espace est réservé/limité par des quotas.
Vérifiez toujours df -h et df -i, et scannez les fichiers supprimés mais ouverts avec lsof +L1.

2) Puis‑je simplement supprimer d’anciens logs pour régler le problème ?

Parfois, mais faites‑le volontairement. Si les logs sont ouverts, la suppression ne récupérera pas l’espace tant que le processus ne les ferme pas.
Aussi : supprimer des logs peut enlever la seule preuve de ce qui s’est passé — capturez ce dont vous avez besoin d’abord.

3) Pourquoi ma DB a‑t‑elle cassé si la partition de la base n’était pas pleine ?

Parce que les bases dépendent d’autres chemins : répertoires de logs, tmp dirs, sockets, fichiers PID, et parfois des artefacts de récupération.
Root plein peut casser une DB dont le datadir est sur un montage séparé.

4) Dois‑je redémarrer la DB en boucle jusqu’à ce qu’elle revienne ?

Non. Les redémarrages répétés peuvent faire échouer la récupération et générer plus d’écritures (et plus de logs) alors que vous manquez d’espace.
Libérez de l’espace d’abord, puis démarrez une fois et laissez la récupération se terminer.

5) Combien d’espace libre est « suffisant » avant de redémarrer ?

Suffisamment pour finir la récupération et survivre à un pic d’écritures normal. En production, je vise 10–20% d’espace libre sur les systèmes de fichiers pertinents.
Si vous ne pouvez pas arriver à cela, étendez le stockage ou basculez vers une réplique au lieu de tenter le tout pour le tout.

6) Quelle est la manière la plus sûre de récupérer de l’espace immédiatement ?

Vacuumiser les journaux systemd, vider les caches de paquets, supprimer des caches connus sûrs, et faire tourner/tronquer les logs applicatifs qui partent en vrille.
Évitez de toucher aux fichiers de base de données à moins de suivre une procédure éprouvée spécifique au moteur.

7) Et si je suis à court d’inodes ?

Supprimer quelques gros fichiers n’aidera pas. Vous devez supprimer de nombreux petits fichiers (souvent des caches) ou déplacer cette charge hors du système de fichiers.
À long terme, repensez : séparez les caches de root, et choisissez des paramètres de système de fichiers adaptés au nombre de fichiers.

8) Les « blocs réservés » d’ext4 signifient‑ils que je peux récupérer de l’espace d’urgence ?

Les blocs réservés existent pour que root puisse encore fonctionner quand les utilisateurs remplissent le disque. Vous pouvez les ajuster avec tune2fs,
mais changer les blocs réservés pendant un incident est rarement votre meilleur premier geste. Libérez de l’espace réel ou étendez le système de fichiers.

9) Un disque plein peut‑il causer une corruption silencieuse des données ?

Le disque plein provoque typiquement des échecs bruyants (ENOSPC), mais il peut aussi mettre votre base dans un état nécessitant une récupération et où la cohérence doit être vérifiée.
Si vous suspectez une corruption — surtout avec des erreurs I/O dans dmesg — préférez la restauration/la promotion d’une réplique plutôt que « redémarrer et espérer ».

10) Comment empêcher que cela se reproduise ?

Alertez sur les blocs et les inodes, faites respecter des limites de logs (journald et app), mettez les chemins temporaires sur un stockage avec marge, et exercez la bascule/restauration.
La prévention est majoritairement une configuration ennuyeuse — et l’ennuyeux coûte moins cher que l’indisponibilité.

Conclusion : prochaines étapes à réaliser aujourd’hui

Les incidents disque-plein ne sont pas glamour, mais ils sont honnêtes : ils révèlent si votre système est opéré avec des marges, des garde-fous et des procédures de récupération répétées.
Si cela vient de vous arriver sur Debian 13, votre priorité est de finir dans un état vérifié-bon, pas simplement « service en cours d’exécution ».

Faites ceci ensuite, dans cet ordre :

  1. Définir des limites strictes pour les logs (journald + logrotate) et confirmer qu’elles fonctionnent sous charge.
  2. Ajouter des alertes pour les inodes et les fichiers supprimés-ouverts (ou au moins faire de lsof +L1 une partie de votre mémoire musculaire on-call).
  3. Déplacer les dépendances DB hors de root quand c’est sensé : tmp dirs, logs à fort volume, couches de conteneurs.
  4. Décider de votre « politique d’espace libre minimum » pour les volumes DB (10–20% est un bon départ), et l’appliquer avec alertes et planification de capacité.
  5. Pratiquer le chemin de récupération (restauration ou promotion de réplique). Si vous ne le faites que pendant une panne, ce n’est pas un plan — c’est un pari.

Le meilleur résultat d’un incident disque-plein n’est pas que vous ayez récupéré. C’est que vous ayez maintenant assez de discipline dans le système pour que le prochain ne devienne jamais un incident.

← Précédent
VPN connecté mais pas d’accès Internet sous Windows : liste de contrôle des routes et métriques
Suivant →
Migration de stockage ESXi vers Proxmox : déplacer des datastores VMFS vers ZFS, NFS ou iSCSI avec un minimum d’interruption

Laisser un commentaire