Disque plein est le type d’incident qui rappelle soudainement à tout le monde l’existence de tous les logs, combien d’exportations « temporaires » sont en réalité permanentes, et à quelle vitesse une base de données sanctionne l’optimisme. L’application paraît « lente ». Puis elle paraît « en panne ». Puis votre pager donne l’impression d’essayer d’atteindre la vitesse de libération.
Ceci est une comparaison sur le terrain : quand le système de fichiers atteint 100 %, quel moteur vous remet en vert plus rapidement — et lequel récupère plus proprement (c’est-à-dire avec moins d’effets secondaires bizarres, moins de risques silencieux sur les données, moins de suivis « on a réparé mais c’est hanté »).
La thèse directe
Si vous mesurez « qui récupère plus vite » par « qui revient en ligne avec un minimum de babysitting humain », PostgreSQL gagne souvent sur la clarté et les modes de défaillance prévisibles, surtout autour du WAL et de la récupération après crash. Si vous mesurez « qui récupère plus proprement » par « qui a le moins de chances de continuer à boiter silencieusement avec un risque de corruption cachée », PostgreSQL tend encore à paraître plus sûr — parce qu’il est plus bruyant, plus transactionnel au sujet de ses métadonnées, et refuse de faire semblant que tout va bien.
MySQL (InnoDB) peut aussi récupérer proprement, et il est très bon en récupération après crash — mais les incidents de disque plein ont la faculté de tourner en pannes partielles désordonnées : tables temporaires, journaux de redo, binlogs et le système d’exploitation se disputant les derniers mégaoctets comme s’il s’agissait du dernier siège d’un train de banlieue.
Mes conseils opérationnels opiniâtres :
- PostgreSQL : priorisez la résilience et la limitation du WAL et de
pg_wal(archivage, discipline des replication slots, volume séparé). Disque plein se présente généralement comme « impossible d’écrire le WAL / impossible de checkpoint », ce qui fait peur mais reste lisible. - MySQL : limitez la rétention des binlogs, la croissance de tmpdir et des undo/redo, et évitez les surprises au niveau du système de fichiers (thin provisioning, snapshots). Disque plein ressemble souvent à « tout est plus ou moins cassé en même temps », ce qui est coûteux opérationnellement.
- Pour les deux : considérez l’espace libre comme une fonctionnalité, pas une suggestion. « On tourne à 92 % » est la façon dont vous vous retrouvez à gérer un incident en négociant avec un array de stockage.
Une citation à afficher sur votre écran, de Gene Kranz : « L’échec n’est pas une option. » Quand vous gérez du stockage en production, traitez cela comme une idée paraphrasée, pas comme une promesse.
Faits intéressants et contexte historique
- Lignée du WAL de PostgreSQL : l’approche write-ahead logging de PostgreSQL a mûri depuis les recherches Postgres vers un modèle de durabilité robuste et conservateur qui façonne fortement le comportement en cas de disque plein (WAL d’abord, tout le reste ensuite).
- InnoDB n’a pas toujours été « le défaut » : InnoDB est devenu le moteur pratique par défaut pour MySQL parce qu’il apportait récupération après crash et sémantique transactionnelle que les anciens MyISAM n’avaient pas — changeant la façon dont se présentent les incidents de disque plein (redo/undo vs chaos au niveau des tables).
- La douleur historique d’ibdata1 dans MySQL : les anciens déploiements InnoDB utilisaient souvent un espace de tables système partagé (ibdata1) qui pouvait gonfler et ne pas diminuer facilement, une cicatrice opérationnelle de longue date pour les incidents « on a supprimé des données, pourquoi le disque reste plein ? ».
- Le MVCC de PostgreSQL coûte de l’espace par conception : le MVCC de PostgreSQL crée des tuples morts qui doivent être vacuumés, donc la pression disque n’est pas une surprise ; c’est une facture que vous payez régulièrement ou avec intérêts plus tard.
- Les replication slots ont changé les modes de défaillance : les replication slots de PostgreSQL sont puissants, mais ils peuvent épingler le WAL indéfiniment ; les incidents modernes de « disque plein » traquent souvent un slot oublié retenant le WAL en otage.
- La rétention des binlogs MySQL est un levier d’incident : dans MySQL, les binlogs sont à la fois un atout de récupération et une bombe disque. Les paramètres de rétention par défaut et les habitudes opérationnelles peuvent décider si un disque plein est « mineur » ou « multi-heures ».
- Les systèmes de fichiers comptent plus que vous ne le souhaitez : XFS, ext4 et ZFS se comportent différemment sous ENOSPC. La base de données ne peut pas se soustraire à la personnalité du noyau.
- Le comportement des checkpoints fait une grande différence : les checkpoints de PostgreSQL et le comportement de flush de MySQL créent des schémas d’écritures en rafales différents ; sous forte occupation, ces rafales sont l’endroit où vous découvrez que vous patiniez sur une glace mince.
Ce que « disque plein » signifie réellement en production
« Disque plein » n’est rarement une seule chose. C’est une dispute entre couches :
- Le système de fichiers renvoie ENOSPC. Ou il renvoie EDQUOT parce que vous avez atteint une quota oubliée.
- Le backend de stockage ment poliment (thin provisioning) jusqu’à ce qu’il cesse d’être poli, et alors c’est le problème de tout le monde.
- Le noyau peut garder certains processus en vie tandis que d’autres échouent sur fsync, rename ou allocate.
- La base de données a plusieurs chemins d’écriture : WAL/redo, fichiers de données, fichiers temporaires, débordements de tri, autovacuum/vacuum, binlogs, métadonnées de réplication et workers en arrière-plan.
La définition pratique pour la gestion d’incidents est : la base de données peut-elle encore garantir durabilité et cohérence ? Quand le stockage est à 100 %, la réponse devient « pas de façon fiable » bien avant que le process ne meure réellement.
Aussi, « disque plein » n’est pas que capacité. C’est le nombre de blocs libres, les inodes, le headroom IOPS, l’amplification d’écriture, et la quantité d’espace contigu que votre système de fichiers peut allouer en cas de fragmentation.
Petite blague #1 : Les incidents de disque plein sont comme des tout-petits — le silence peut aller, crier est mauvais, mais le pire c’est quand ils se taisent à nouveau et que vous réalisez qu’ils ont trouvé les marqueurs.
Comment PostgreSQL et MySQL se comportent quand le stockage manque
PostgreSQL : le WAL est roi, et il vous dira quand le royaume est cassé
La durabilité de PostgreSQL tourne autour du WAL. S’il ne peut pas écrire le WAL, il ne peut pas valider en toute sécurité. Ce n’est pas négociable. Quand le disque se remplit sur pg_wal (ou le système de fichiers qui l’héberge), vous verrez souvent :
- Des transactions échouer avec « could not write to file … No space left on device ».
- Des avertissements de checkpoint qui escaladent en « PANIC » dans les pires cas (selon ce qui a exactement échoué).
- Le lag de réplication devenant sans objet parce que les primaires ne peuvent pas générer le WAL de façon fiable.
Cela est brutal mais honnête : Postgres a tendance à échouer de façons qui vous poussent à corriger la contrainte de stockage sous-jacente, pas à vous inviter à continuer d’écrire « juste un peu plus » jusqu’à créer un second incident.
Schémas de récupération plus propres lors d’événements disque plein PostgreSQL :
- Messages d’erreur clairs pointant vers des segments WAL, des fichiers temporaires, le répertoire de base.
- La récupération après crash est typiquement déterministe une fois que vous restaurez la capacité d’écriture.
- Ensemble borné de suspects habituels :
pg_wal, débordements temporaires, autovacuum, replication slots.
Modèles désordonnés que vous voyez quand même dans Postgres :
- Les replication slots épinglent le WAL jusqu’à ce que le disque disparaisse.
- Les transactions longue durée empêchent le vacuum, gonflent tables et index, puis le disque lâche.
- Les explosions de fichiers temporaires dues à des requêtes mauvaises : tris, hash, CTE volumineux ou index manquants.
MySQL (InnoDB) : plusieurs chemins d’écriture, plusieurs façons de souffrir
InnoDB a des redo logs, undo logs, doublewrite buffer, fichiers de données, espaces temporaires, binlogs (niveau serveur), relay logs (réplication), puis votre système de fichiers. Quand le disque devient serré, vous pouvez rencontrer une défaillance dans une zone tandis qu’une autre zone continue d’écrire — créant une fonctionnalité partielle et des symptômes confus.
Schémas fréquents de disque plein sur MySQL :
- Les binlogs remplissent la partition, surtout avec le logging en mode row et des charges d’écriture soutenues.
- tmpdir se remplit à cause de tris lourds ou de tables temporaires ; des requêtes commencent à échouer de façon étrange pendant que le serveur « répond ».
- InnoDB ne peut pas étendre un tablespace (file-per-table ou tablespace partagé), provoquant des erreurs sur insert/update.
- La réplication se casse de façon asymétrique : la source continue mais le réplica s’arrête sur l’écriture du relay log, ou l’inverse.
Schémas de récupération plus propres en MySQL :
- Une fois l’espace libéré, la récupération après crash InnoDB est généralement solide.
- La purge des binlogs est un levier rapide si vous êtes discipliné et comprenez les besoins de réplication.
Schémas plus désordonnés en MySQL :
- ibdata1 et les espaces de tables undo peuvent rester volumineux même après des suppressions ; « libérer de l’espace » n’est pas toujours immédiat sans reconstructions.
- La suspicion de corruption de table augmente quand des fichiers ont été partiellement écrits et que le système de fichiers était sous pression — rare, mais la crainte est coûteuse.
- Les threads en arrière-plan peuvent continuer de marteler l’IO en essayant de flush/merge pendant que vous essayez de stabiliser le système.
Alors qui récupère plus vite ?
Si votre astreinte a besoin d’une phrase unique : PostgreSQL vous offre généralement un chemin plus direct de « disque plein » à « sûr à nouveau », à condition que vous compreniez le WAL, les checkpoints et les slots. MySQL vous donne plus de « leviers rapides » (purger binlogs, déplacer tmpdir), mais aussi plus de façons de couper la branche sur laquelle vous êtes assis.
Et qui récupère plus proprement ?
La récupération propre, c’est la confiance : après avoir libéré de l’espace, faites-vous confiance au système, ou programmez-vous un week-end pour « vérifier » ? La posture de PostgreSQL — arrêter le monde quand le WAL ne peut pas être garanti — tend à produire moins de situations « ça tourne mais… ». MySQL peut être parfaitement propre aussi, mais les incidents disque plein laissent plus souvent une check-list du type « avons-nous perdu la position de réplication, avons-nous tronqué quelque chose, tmpdir a-t-il été déplacé, la purge de binlogs a-t-elle cassé un réplica ? »
Playbook de diagnostic rapide
Voici l’ordre qui trouve le goulot d’étranglement rapidement. Pas « théoriquement correct », mais « qui met fin à la panne ».
1) Confirmer ce qui est réellement plein (blocs vs inodes vs quota)
- Vérifier l’utilisation des blocs du système de fichiers.
- Vérifier l’épuisement des inodes.
- Vérifier les quotas (utilisateur/projet).
- Vérifier thin provisioning / LVM / headroom de l’array.
2) Identifier le principal consommateur d’espace (et si sa taille augmente encore)
- Trouver quel répertoire est énorme (
/var/lib/postgresql,/var/lib/mysql,/var/log). - Vérifier les fichiers ouverts mais supprimés qui occupent encore de l’espace.
- Vérifier si la croissance est « journaux constants » ou « déversement temporaire soudain ».
3) Déterminer si la base de données peut toujours garantir la durabilité
- Postgres : peut-il écrire le WAL ? les checkpoints échouent-ils ?
- MySQL : les écritures redo/binlog/tmp échouent-elles ? la réplication est-elle compromise ?
4) Appliquer d’abord un soulagement d’espace réversible et à faible risque
- Supprimer/purger des logs tournés, dumps anciens, paquets obsolètes.
- Purger les binlogs MySQL seulement si les réplicas sont en sécurité.
- Résoudre les replication slots Postgres qui retiennent le WAL.
- Déplacer les répertoires temporaires vers un autre volume en solution temporaire.
5) Après la stabilisation, faire le travail de correction
- Lancer des vérifications de cohérence adaptées au moteur.
- Corriger les politiques de rétention et les alertes de capacité.
- Planifier vacuum/reindex ou rebuild de tables si la bloat en est la cause.
Tâches pratiques : commandes, sorties et décisions (12+)
Ce sont les commandes que vous exécutez à 3 heures du matin. Chacune inclut ce que signifie la sortie et la décision que vous prenez.
Tâche 1 : Vérifier la capacité du système de fichiers (blocs)
cr0x@server:~$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 80G 79G 120M 100% /
/dev/nvme1n1p1 xfs 500G 410G 90G 83% /var/lib/postgresql
tmpfs tmpfs 16G 1.2G 15G 8% /run
Sens : Le système racine est à 100 % avec seulement 120M disponibles ; le volume de données Postgres est correct. Beaucoup de services cassent quand / est plein (journald, mises à jour des paquets, fichiers temporaires).
Décision : Libérer de l’espace sur / immédiatement (logs, caches). Ne touchez pas aux fichiers de la base de données si ce n’est pas la cause.
Tâche 2 : Vérifier l’épuisement des inodes
cr0x@server:~$ df -ih
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 5.0M 5.0M 0 100% /
/dev/nvme1n1p1 20M 1.2M 18.8M 6% /var/lib/postgresql
Sens : Les inodes sur / sont épuisés. Cela ressemble à un « disque plein » mais supprimer un fichier énorme n’aidera pas.
Décision : Trouver les répertoires avec des millions de petits fichiers (souvent logs, temporaires ou caches applicatifs mal gérés). Nettoyer ceux-ci en priorité.
Tâche 3 : Trouver les plus gros consommateurs d’espace en toute sécurité
cr0x@server:~$ sudo du -xhd1 /var | sort -h
120M /var/cache
2.4G /var/log
8.1G /var/tmp
55G /var/lib
Sens : /var/lib domine. C’est l’endroit où résident les bases de données. Mais /var/log et /var/tmp sont non négligeables et souvent les plus faciles à réduire.
Décision : Si la base de données est en panne, récupérer d’abord du headroom en taillant les logs et les temporaires. Puis inspecter plus précisément les répertoires de la base de données.
Tâche 4 : Détecter les fichiers ouverts mais supprimés (classique « df dit plein, du dit pas »)
cr0x@server:~$ sudo lsof +L1 | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
rsyslogd 812 syslog 7w REG 259,2 2147483648 0 12345 /var/log/syslog.1 (deleted)
java 1552 app 12w REG 259,2 1073741824 0 12346 /var/log/app.log (deleted)
Sens : Des processes tiennent encore des descripteurs sur des fichiers supprimés, donc l’espace ne sera pas récupéré tant que ces processes ne redémarrent pas ou ne ferment pas les FDs.
Décision : Redémarrer le(s) service(s) spécifique(s) après avoir confirmé l’impact, ou faire une rotation des logs correctement. Ne redémarrez pas à l’aveugle sauf si nécessaire.
Tâche 5 : Vérifier l’utilisation disque de journald
cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 1.8G in the file system.
Sens : Les journaux occupent de l’espace réel. Sur des racines petites, cela compte.
Décision : Vacuumer les anciens journaux si nécessaire ; puis corriger la rétention pour que cela ne devienne pas une habitude.
Tâche 6 : Vacuum rapide de journald (soulagement d’espace)
cr0x@server:~$ sudo journalctl --vacuum-time=7d
Vacuuming done, freed 1.2G of archived journals from /var/log/journal.
Sens : Vous avez récupéré 1.2G. Souvent suffisant pour laisser les bases de données checkpointer, pivoter des logs, ou redémarrer proprement.
Décision : Utiliser l’espace regagné pour stabiliser la base de données (ou créer une marge de sécurité temporaire), puis résoudre la cause racine.
Tâche 7 : PostgreSQL — vérifier si le répertoire WAL est en cause
cr0x@server:~$ sudo -u postgres du -sh /var/lib/postgresql/16/main/pg_wal
86G /var/lib/postgresql/16/main/pg_wal
Sens : Le WAL est énorme. Cela signifie généralement (a) un replication slot épinglant le WAL, (b) un archivage cassé, (c) un réplica très en retard, ou (d) des checkpoints empêchés.
Décision : Inspecter les replication slots et l’état de l’archivage avant de supprimer quoi que ce soit. Supprimer manuellement les fichiers WAL transforme un « incident » en « changement de carrière ».
Tâche 8 : PostgreSQL — lister les replication slots et repérer le pin WAL
cr0x@server:~$ sudo -u postgres psql -x -c "SELECT slot_name, slot_type, active, restart_lsn, wal_status FROM pg_replication_slots;"
-[ RECORD 1 ]--+------------------------------
slot_name | analytics_slot
slot_type | logical
active | f
restart_lsn | 2A/9F000000
wal_status | reserved
-[ RECORD 2 ]--+------------------------------
slot_name | standby_1
slot_type | physical
active | t
restart_lsn | 2F/12000000
wal_status | extended
Sens : analytics_slot est inactif mais retient toujours du WAL via restart_lsn. C’est une cause fréquente de croissance du WAL jusqu’à épuisement du disque.
Décision : Si le consommateur a disparu ou peut être réinitialisé, supprimer le slot pour libérer la rétention WAL. S’il est nécessaire, réparer le consommateur et le laisser rattraper son retard, ou déplacer le WAL vers un volume plus grand.
Tâche 9 : PostgreSQL — supprimer un slot logique inutilisé (seulement si sûr)
cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_drop_replication_slot('analytics_slot');"
pg_drop_replication_slot
--------------------------
(1 row)
Sens : Le slot est supprimé ; PostgreSQL peut maintenant recycler le WAL une fois qu’aucune autre contrainte de rétention n’existe.
Décision : Surveiller la taille de pg_wal et l’usage disque ; coordonner avec l’équipe propriétaire du slot car leur pipeline va casser (mieux cassé que plein).
Tâche 10 : PostgreSQL — rechercher des fichiers temporaires incontrôlés (spill de requête)
cr0x@server:~$ sudo -u postgres find /var/lib/postgresql/16/main/base -maxdepth 2 -type f -name "pgsql_tmp*" -printf "%s %p\n" | head
2147483648 /var/lib/postgresql/16/main/base/16384/pgsql_tmp16384.0
1073741824 /var/lib/postgresql/16/main/base/16384/pgsql_tmp16384.1
Sens : Des fichiers temporaires existent et sont volumineux, ce qui suggère des tris/hashes déversés sur disque. Ils disparaissent généralement quand les sessions se terminent, mais pendant un incident ils peuvent être la cause de l’incident.
Décision : Identifier les sessions fautives (tâche suivante), annuler si nécessaire, et ajuster requête/indexation. Pour un soulagement immédiat, tuer quelques sessions peut libérer des gigaoctets rapidement.
Tâche 11 : PostgreSQL — trouver les sessions lourdes et annuler la pire
cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, usename, state, now()-query_start AS age, left(query,120) AS q FROM pg_stat_activity WHERE state <> 'idle' ORDER BY query_start ASC LIMIT 5;"
pid | usename | state | age | q
------+--------+--------+---------+------------------------------------------------------------
4412 | app | active | 00:34:12 | SELECT ... ORDER BY ...
4520 | app | active | 00:21:03 | WITH ... JOIN ...
cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_cancel_backend(4412);"
pg_cancel_backend
-------------------
t
(1 row)
Sens : Vous avez trouvé des requêtes longues ; l’annulation a réussi pour PID 4412. Si cela déversait sur disque, les fichiers temporaires devraient cesser de croître et peuvent être supprimés.
Décision : Si la pression disque est aiguë, annuler/terminer les sessions provoquant des débordements. Puis corriger le plan de requête plus calmement.
Tâche 12 : MySQL — vérifier où va l’espace (datadir et logs)
cr0x@server:~$ sudo mysql -e "SHOW VARIABLES WHERE Variable_name IN ('datadir','tmpdir','log_bin','general_log_file','slow_query_log_file');"
+--------------------+---------------------------+
| Variable_name | Value |
+--------------------+---------------------------+
| datadir | /var/lib/mysql/ |
| tmpdir | /tmp |
| log_bin | ON |
| general_log_file | /var/lib/mysql/general.log|
| slow_query_log_file| /var/lib/mysql/slow.log |
+--------------------+---------------------------+
Sens : tmpdir est sur /tmp (souvent sur la racine). Si / se remplit, les opérations temporaires MySQL échouent de façons surprenantes.
Décision : Si / est contraint, déplacer tmpdir vers un volume plus grand et redémarrer (ou configurer à l’avance). Vérifier aussi si le general log a été activé accidentellement.
Tâche 13 : MySQL — voir l’inventaire et la taille des binlogs
cr0x@server:~$ sudo mysql -e "SHOW BINARY LOGS;"
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| binlog.000231 | 104857600 |
| binlog.000232 | 104857600 |
| binlog.000233 | 104857600 |
| binlog.000234 | 104857600 |
+------------------+-----------+
Sens : Des binlogs existent et peuvent s’accumuler. Les tailles ici sont cohérentes, mais le nombre peut être énorme.
Décision : Avant de purger, vérifier l’état de la réplication. Purger des binlogs dont les réplicas ont encore besoin revient à s’infliger une interruption.
Tâche 14 : MySQL — confirmer la position de réplication avant de purger les binlogs
cr0x@server:~$ sudo mysql -e "SHOW MASTER STATUS\G"
*************************** 1. row ***************************
File: binlog.000234
Position: 89234122
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 3f1c2c3a-aaaa-bbbb-cccc-111111111111:1-982341
cr0x@server:~$ sudo mysql -e "SHOW SLAVE HOSTS;"
+-----------+-----------+------+-------------------+-----------+
| Server_id | Host | Port | Rpl_recovery_rank | Master_id |
+-----------+-----------+------+-------------------+-----------+
| 12 | replica01 | 3306 | 0 | 1 |
+-----------+-----------+------+-------------------+-----------+
Sens : Vous avez au moins un réplica. Il faut s’assurer qu’il est assez rattrapé (GTID ou file/pos) avant de supprimer des logs.
Décision : Vérifier le statut des réplicas sur chaque réplica (ou via la surveillance). Purger seulement les logs plus anciens que ce que tous les réplicas ont consommé.
Tâche 15 : MySQL — purger les binlogs avec prudence
cr0x@server:~$ sudo mysql -e "PURGE BINARY LOGS TO 'binlog.000233';"
Sens : Les binlogs antérieurs à binlog.000233 sont supprimés. Si un réplica en avait encore besoin, il s’arrêtera et nécessitera une réinitialisation ou une autre réparation.
Décision : Purger seulement après vérification de la réplication. Si incertain, libérer de l’espace ailleurs d’abord. Les binlogs sont une tronçonneuse, pas un scalpel.
Tâche 16 : MySQL — vérifier les explosions de tables temporaires et la pression sur tmpdir
cr0x@server:~$ sudo mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Created_tmp_disk_tables | 184203 |
| Created_tmp_files | 91203 |
| Created_tmp_tables | 3312849 |
+-------------------------+----------+
Sens : Un grand nombre de tables temporaires sur disque suggère des schémas de requêtes qui débordent sur disque. Sur un système plein, ces requêtes échouent et parfois bloquent d’autres opérations.
Décision : Atténuer la pression disque immédiate (déplacer tmpdir, ajouter de l’espace, tuer les pires requêtes), puis optimiser requêtes/index et paramètres de tables temporaires.
Tâche 17 : Confirmer que le thin provisioning ne vous ment pas (exemple LVM)
cr0x@server:~$ sudo lvs -a -o +data_percent,metadata_percent
LV VG Attr LSize Pool Data% Meta%
mysql vg0 Vwi-aotz-- 300.00g thinpool 98.44 92.10
thinpool vg0 twi-aotz-- 500.00g 98.44 92.10
Sens : Le thin pool est presque plein. Même si le système de fichiers semble « correct », les allocations peuvent échouer bientôt. Les bases de données adorent découvrir cela au pic d’écriture.
Décision : Étendre le thin pool ou libérer des extents immédiatement. Traiter ceci comme un « disque plein imminent ».
Tâche 18 : Vérifier que vous n’avez pas atteint un quota (exemple quota projet XFS)
cr0x@server:~$ sudo xfs_quota -x -c "report -p" /var/lib/mysql
Project quota on /var/lib/mysql (/dev/nvme2n1p1)
Project ID: 10 (mysql)
Used: 498.0G Soft: 0 Hard: 500.0G Warn/Grace: [--------]
Sens : Vous avez atteint un quota hard à 500G. Le système de fichiers peut avoir de l’espace globalement, mais votre répertoire MySQL ne peut pas croître.
Décision : Augmenter le quota ou migrer les données vers un projet plus grand. Cesser de blâmer la base de données pour une décision de politique.
Petite blague #2 : La seule chose qui grandit plus vite que votre WAL, c’est la confiance de la personne qui dit « on n’a pas besoin d’alertes disque ».
Trois mini-récits d’entreprise (anonymisés, douloureusement plausibles)
1) L’incident causé par une mauvaise hypothèse : « On a 20 % libres, on est tranquilles »
Ils faisaient tourner PostgreSQL sur une VM avec un disque de données séparé et un petit disque racine. Le tableau de bord montrait le disque de données à 78 % utilisé. Tout le monde se sentait responsable. Personne n’était inquiet.
Puis un déploiement a activé des logs applicatifs verbeux pendant une session de debug. Les logs allaient dans /var/log sur le disque racine. À minuit, la racine a atteint 100 % et journald a commencé à laisser tomber des messages. À 00:30, PostgreSQL a commencé à échouer sur des écritures temporaires puis a peiné avec des écritures liées aux checkpoints parce que même « ce n’est pas le disque de données » importe pour l’OS et les services système.
L’astreinte a fait l’habituel : inspecté le volume base de données, vu de la marge, et supposé que le problème venait d’ailleurs. Pendant ce temps, le « ailleurs » était la partition racine, qui hébergeait aussi /tmp et quelques scripts admin qui écrivaient leurs propres fichiers temporaires. La panne ressemblait à un problème de base de données, mais la cause racine était l’agencement de l’infrastructure et l’hypothèse que « disque de la base = tous les disques qui comptent ».
La récupération a été rapide une fois que quelqu’un a exécuté df -h au lieu de fixer le tableau de bord de la base. Ils ont vacuumé journald, tronqué le log incontrôlable, redémarré le service bavard, et Postgres s’est rétabli sans drame. L’action post-mortem était ennuyeuse : dimensionnement des partitions et rétention des logs. Cela a marché.
2) L’optimisation qui s’est retournée contre eux : « Gardons les binlogs plus longtemps, au cas où »
Une équipe MySQL voulait une meilleure recovery point-in-time et des rebuilds de réplica plus fluides. Le réglage le plus simple était la rétention des binlogs. Ils l’ont étendue. Personne n’a noté l’empreinte maximale nouvelle, parce que la planification de capacité se fait quand on a du temps, et personne n’a jamais de temps.
Le stockage était thin-provisionné. Le système de fichiers rapportait encore de l’espace libre, donc les alertes restaient silencieuses. Les écritures ont continué. Les binlogs se sont accumulés. Le thin pool a fléchi vers le plein. Un jour, les allocations ont commencé à échouer par rafales — d’abord dans les périodes d’écriture élevée, puis de plus en plus souvent. MySQL a affiché des erreurs d’écriture du binlog, et soudain des commits ont commencé à échouer de façon intermittente.
Le pire était le motif : le serveur ne mourait pas immédiatement, il devenait juste peu fiable. Certaines transactions commit, d’autres non. L’application a commencé à retry. Les retries ont augmenté la charge d’écriture. La charge d’écriture a généré plus de pression sur les binlogs. C’est une boucle de rétroaction que vous ne voulez pas.
Ils ont récupéré en étendant le thin pool et en purgeant les binlogs de façon conservatrice après avoir confirmé la santé des réplicas. L’optimisation qui a foiré n’était pas « garder les binlogs plus longtemps ». C’était le faire sans budget borné, sans alertes côté stockage, et sans procédure de purge pratiquée.
3) La pratique ennuyeuse mais correcte qui a sauvé la mise : séparer WAL/redo, faire respecter les budgets, répéter le nettoyage
Une autre organisation faisait tourner PostgreSQL avec le WAL sur un volume dédié et une surveillance stricte : alertes de remplissage du volume à 70/80/85 %, et un runbook incluant « vérifier replication slots, vérifier l’archivage, vérifier les transactions longues ». Ce n’était pas glamour. C’était efficace.
Un après-midi, un consommateur de réplication logique en aval s’est bloqué après un changement réseau. Le replication slot est devenu inactif. Le WAL a commencé à s’accumuler. L’alerte 70 % s’est déclenchée. L’astreinte n’a pas paniqué ; ils ont exécuté le runbook. Ils ont confirmé que le slot était la cause, validé que le consommateur était vraiment down, puis supprimé le slot et notifié l’équipe propriétaire.
La base n’est jamais passée en read-only, n’a jamais crashé, n’a jamais subi d’indisponibilité. L’« incident » n’était qu’un fil Slack et un ticket. La pratique qui les a sauvés n’était pas de l’ingéniosité. C’était la discipline silencieuse de mettre le chemin d’écriture le plus dangereux (WAL) sur un volume qui avait un budget, plus des alertes qui donnaient aux humains le temps d’agir comme des humains.
Si vous voulez une récupération disque plein plus rapide, vous ne commencez pas pendant l’incident. Vous commencez quand vous décidez où vivent WAL/binlogs et combien de marge vous gardez.
Erreurs fréquentes : symptômes → cause racine → correction
1) « df dit 100 %, mais j’ai supprimé des fichiers et rien n’a changé »
Symptôme : L’usage disque reste élevé après suppression de logs ou dumps volumineux.
Cause racine : Fichiers ouverts mais supprimés (processus tenant encore le FD).
Correction : Utiliser lsof +L1, redémarrer le service spécifique, puis confirmer que df -h baisse. Ne pas redémarrer en rafale sauf nécessité.
2) PostgreSQL : « pg_wal est énorme et ne réduit pas »
Symptôme : Le répertoire WAL croît jusqu’à presque remplir le disque ; l’archivage peut paraître « opérationnel » par moments.
Cause racine : Slot de réplication inactif, archiver bloqué, ou réplica incapable de consommer le WAL.
Correction : Inspecter pg_replication_slots, réparer le consommateur ou supprimer le slot ; vérifier la santé de archive_command ; s’assurer que le volume WAL a du headroom.
3) PostgreSQL : « No space left » mais le disque de données a de l’espace
Symptôme : Erreurs d’écriture de fichiers temporaires, échec lors de tris/joins ; le mount principal semble correct.
Cause racine : Les fichiers temporaires vont sur un autre système de fichiers (souvent /tmp ou la racine), ou la racine pleine affecte les opérations système.
Correction : Vérifier temp_tablespaces et l’usage OS des temporaires ; libérer de l’espace sur la racine ; éventuellement déplacer les tablespaces temporaires sur un mount plus grand.
4) MySQL : « Le serveur est up mais les écritures échouent aléatoirement »
Symptôme : Certaines transactions échouent, d’autres réussissent ; erreurs mentionnant binlog ou fichiers tmp.
Cause racine : Binlogs ou tmpdir sur un système plein ; thin provisioning proche du plein provoquant des échecs d’allocation intermittents.
Correction : Libérer de l’espace là où vivent les binlogs ; valider le thin pool ; déplacer tmpdir ; borner la rétention des binlogs (binlog_expire_logs_seconds) et surveiller.
5) « On a libéré 5G, mais la base le remplit immédiatement »
Symptôme : Vous supprimez des choses, récupérez de l’espace brièvement, puis il disparaît en quelques minutes.
Cause racine : Un process d’arrière-plan amplifiant les écritures : checkpoints sous pression, autovacuum qui rattrape, backlog de réplication produisant des logs, ou une requête runaway produisant des spills temporaires.
Correction : Identifier le vecteur de croissance (WAL/binlog/tmp). Arrêter l’hémorragie : annuler la requête, réparer slot/réplica, mettre en pause un job, ou limiter temporairement l’ingestion.
6) « On a purgé les binlogs MySQL et maintenant un réplica est mort »
Symptôme : Le réplica affiche des erreurs sur des fichiers binlog manquants.
Cause racine : Purge des binlogs encore nécessaires au réplica ; visibilité insuffisante sur le lag/GTID des réplicas.
Correction : Re-seeder ou utiliser la recovery basée GTID si disponible. Prévenir la récurrence : automatiser la purge des binlogs basée sur une rétention sûre et surveiller le lag des réplicas.
7) PostgreSQL : « Vacuum ne nous a pas sauvés ; le disque reste plein »
Symptôme : Des suppressions ont eu lieu, vacuum a tourné, mais le disque n’a pas rétréci.
Cause racine : MVCC libère l’espace à l’intérieur des fichiers de relation pour réutilisation ; cela ne le rend pas forcément au système de fichiers. De plus, les index gonflent.
Correction : Pour un vrai rétrécissement : VACUUM FULL (bloquant) ou stratégies de réécriture de table, plus REINDEX si nécessaire. Planifiez cela ; n’improvisez pas en pleine crise.
Listes de contrôle / plan pas à pas
Pendant l’incident : stabiliser d’abord, réparer ensuite
- Arrêter la croissance : mettre en pause les jobs batch, désactiver les logs bruyants, limiter l’ingestion, ou bloquer temporairement le pire contributeur.
- Confirmer ce qui est plein :
- Exécuter
df -hTetdf -ih. - Vérifier les quotas si votre organisation aime les murs invisibles.
- Exécuter
- Récupérer du headroom rapidement (viser 5–10 % libres, pas « quelques Mo ») :
- Vacuumer journald et tailler les vieux logs.
- Supprimer d’anciens dumps/artéfacts des emplacements connus.
- Traiter les fichiers ouverts mais supprimés et redémarrer le service spécifique.
- Triage spécifique à la base :
- Postgres : inspecter
pg_wal, slots, archivage, transactions longues, débordements temporaires. - MySQL : inspecter binlogs, emplacement tmpdir, relay logs sur les réplicas, et état du thin pool.
- Postgres : inspecter
- Ramener la base en ligne en sécurité :
- Préférer un redémarrage contrôlé plutôt que des boucles de crash répétées.
- Vérifier que les chemins de durabilité fonctionnent (WAL/binlog écrits).
- Valider la santé applicative : taux d’erreur en baisse, latence normalisée, réplication rattrapée.
Après l’incident : rendre la répétition plus difficile
- Séparer les chemins d’écriture critiques : WAL ou redo/binlog sur des volumes avec alertes et budgets de croissance prévisibles.
- Définir explicitement la rétention :
- Postgres : gérer les slots ; s’assurer que l’archivage est surveillé si utilisé.
- MySQL : définir
binlog_expire_logs_seconds; vérifier la rotation des logs.
- Placer les temporaires où ils peuvent respirer : volume temporaire dédié ou limites raisonnables ; éviter la racine pour des workloads temporaires lourds.
- Alerter sur la vitesse de croissance, pas seulement le pourcentage utilisé : « +20G/heure » bat « 92 % utilisé » à tous les coups.
- Répéter un runbook disque plein chaque trimestre. L’objectif est la mémoire musculaire, pas l’héroïsme.
FAQ
1) Si le disque est plein, dois-je redémarrer la base immédiatement ?
Non. Libérez d’abord suffisamment d’espace pour laisser la base démarrer et terminer la récupération après crash/checkpoints. Redémarrer dans un état ENOSPC peut aggraver le risque de corruption et prolonger la panne.
2) Est-il sûr de supprimer manuellement des fichiers WAL PostgreSQL pour libérer de l’espace ?
Presque jamais. La suppression manuelle peut rendre la récupération impossible. L’approche correcte est de supprimer la raison pour laquelle le WAL est retenu (slots, backlog d’archivage, retard des réplicas) ou d’ajouter de l’espace.
3) Est-il sûr de purger les binlogs MySQL pendant un incident ?
Oui, mais seulement avec une conscience de la réplication. Confirmer que tous les réplicas ont consommé les logs (GTID ou file/pos). Purger de façon conservatrice, puis surveiller les réplicas de près.
4) Pourquoi PostgreSQL consomme-t-il autant d’espace même après suppression de lignes ?
MVCC conserve d’anciennes versions de lignes jusqu’à ce que le vacuum puisse récupérer l’espace pour réutilisation. Cet espace est généralement réutilisé à l’intérieur des mêmes fichiers, pas rendu immédiatement au système de fichiers. Réduire réellement nécessite des réécritures (par ex. VACUUM FULL) ou des stratégies de rebuild.
5) Pourquoi MySQL « répond » mais les requêtes échouent sous pression disque ?
Parce que des sous-systèmes différents échouent indépendamment : tmpdir peut être plein, les écritures binlog peuvent échouer, ou les tablespaces ne peuvent pas s’étendre. Le port TCP ouvert n’est pas synonyme de « base saine ».
6) Quelle base est plus tolérante de fonctionner près du plein ?
Aucune. PostgreSQL a plus tendance à refuser des commits non sûrs quand le WAL ne peut pas être écrit. MySQL peut boiter plus longtemps mais être plus désordonné opérationnellement. Votre meilleure tolérance est de l’espace libre.
7) Quel est le meilleur contrôle préventif unique ?
Limiter la croissance avec des budgets explicites : rétention WAL/binlog, rotation des logs, quotas appropriés — et des alertes sur le taux de croissance. « On va le remarquer » n’est pas un contrôle.
8) Combien d’espace libre faut-il garder ?
Garder assez pour survivre à votre croissance pire scénario jusqu’à ce que des humains puissent réagir : typiquement 10–20 % sur les volumes de base de données, plus sur les volumes hébergeant WAL/binlogs/temp, et une marge supplémentaire si vous avez des pics d’écriture.
9) Le choix du système de fichiers change-t-il les résultats de récupération ?
Oui, il change le comportement en cas de défaillance et les outils. Les blocs réservés d’ext4 peuvent masquer un « plein » pour root ; XFS a des quotas courants en multi-tenant ; ZFS a son propre COW et des règles « ne pas remplir le pool ». Choisir intentionnellement et surveiller en conséquence.
10) Quel est le geste le plus rapide pour récupérer de l’espace qui est généralement sûr ?
Nettoyer les artéfacts hors base : logs tournés, dumps anciens, caches de paquets, vacuum journald, et nettoyage des fichiers ouverts mais supprimés. Touchez les internals de la base uniquement une fois que vous comprenez pourquoi ils ont grossi.
Conclusion : quoi faire la semaine prochaine
Les incidents de disque plein ne sont pas la preuve que les bases de données sont fragiles. Ils montrent notre optimisme. PostgreSQL a tendance à récupérer plus proprement parce qu’il est strict sur le WAL et la cohérence. MySQL peut récupérer rapidement aussi, mais il vous donne plus de leviers opérationnels — et plus d’occasions de vous blesser.
Étapes suivantes qui rapportent immédiatement :
- Séparer et budgéter vos chemins d’écriture critiques : WAL Postgres, binlogs/redo MySQL, et emplacements de débordement temporaires.
- Instrumenter des alertes sur le taux de croissance et ajouter le « temps avant plein » à vos tableaux de bord.
- Rédiger et répéter un runbook disque plein qui commence par
df,df -i, etlsof +L1avant de toucher tout fichier de base de données. - Normaliser l’hygiène ennuyeuse : rétention des logs, nettoyage des dumps, discipline des slots/binlogs, et gestion périodique de la bloat.
Si vous faites ces quatre choses, « disque plein » cesse d’être un thriller et devient un ticket de maintenance. C’est la forme de déclassement que vous devriez activement poursuivre.