Votre base de données fonctionnait bien sur du bare metal. Puis vous l’avez conteneurisée, pointé le répertoire de données vers « quelque chose de persistant », et soudain les graphiques de latence ressemblent à un sismographe.
Les écritures se bloquent. Les checkpoints montent en flèche. Quelqu’un dit « utilisez des volumes, les bind mounts sont lents », et quelqu’un d’autre affirme le contraire avec la confiance d’un fil Twitter nocturne.
Voici la vérité depuis les tranchées de production : « bind mount vs volume » n’est pas une affirmation de performance. C’est un chemin vers un système de fichiers, plus quelques couches d’indirection qui peuvent — ou non — compter.
Ce qui importe, c’est ce qui se trouve derrière ce chemin : le kernel, le système de fichiers, la pile de stockage, le runtime de conteneur et les choix de durabilité de votre base de données.
Les vrais choix que vous faites (pas ceux dont les gens débattent)
Quand quelqu’un demande « bind mount ou volume pour MariaDB/Postgres ? », il pose en réalité un ensemble de questions qu’il ne réalise pas qu’il pose :
- Écris-je dans la couche inscriptible du conteneur (OverlayFS) ou pas ? C’est le plus gros « ne le faites pas » pour les bases de données.
- Quel est le système de fichiers réel en arrière-plan ? ext4, XFS, ZFS, btrfs et « stockage bloc réseau mystère » se comportent différemment sous la pression de fsync.
- Le stockage est-il local ou en réseau ? NVMe local et un volume CSI réseau peuvent être tous deux « un volume », et ils se comporteront comme des espèces différentes.
- Quels sont mes réglages de durabilité ? PostgreSQL
fsync,synchronous_commit, placement du WAL; MariaDB InnoDBinnodb_flush_log_at_trx_commitet doublewrite. Ce ne sont pas des boutons cosmétiques. - Quelle est mon amplification d’écriture ? Tailles de page, WAL/binlog, doublewrite, checkpoints, autovacuum/purge. Vous payez pour des écritures que vous n’avez pas réalisées consciemment.
- Suis-je contraint par les IOPS, la latence, le CPU ou l’écriture de pages sales (dirty page writeback) ? Si vous l’ignorez, vous optimiserez la mauvaise chose avec un grand enthousiasme.
Pour le dire franchement : un bind mount vers un XFS local rapide sur NVMe écrasera un « volume Docker » posé sur un stockage réseau congestionné.
À l’inverse, un volume géré par Docker soutenu par un stockage bloc local peut être aussi rapide qu’un bind mount. L’étiquette n’est pas le goulot d’étranglement.
Blague n°1 : Une base de données exécutée sur la couche inscriptible du conteneur, c’est comme garder son passeport dans un sachet plastique — techniquement possible, émotionnellement imprudent.
Faits intéressants et contexte historique (pourquoi on en est là)
Quelques faits courts et concrets expliquent pourquoi les débats sur le stockage autour des conteneurs deviennent étranges :
- Le WAL de PostgreSQL existe depuis le milieu des années 1990 (alors que PostgreSQL évoluait depuis POSTGRES), conçu pour la sécurité en cas de crash sur des disques imparfaits — bien avant que les « volumes cloud » n’existent.
- InnoDB est devenu le moteur de stockage par défaut de MySQL dans la 5.5 (époque 2010), et MariaDB a hérité de cette filiation ; la story de durabilité d’InnoDB repose fortement sur les redo logs et le comportement de doublewrite.
- Le page cache de Linux est le cheval de travail des performances pour les deux bases ; « direct I/O partout » n’est pas la stratégie par défaut pour l’un ou l’autre, et combattre le cache nuit souvent.
- Les sémantiques de fsync varient selon le système de fichiers et les options de montage ; la même config de BDD peut être sûre sur une configuration et « plutôt sûre » sur une autre, ce qui est une manière polie de dire « perte de données surprise ».
- OverlayFS est devenu courant avec le driver overlay2 de Docker parce qu’il est efficace pour le layering d’images, pas parce qu’il adore les charges de travail de bases de données qui font fsync et écritures aléatoires.
- Historiquement, les systèmes de fichiers réseau étaient un piège courant pour les bases de données ; les systèmes modernes sont meilleurs, mais le triangle « latence + fsync + jitter » ruine encore des week-ends.
- Les volumes nommés Docker ont été popularisés pour la portabilité (« déplacer l’app sans se soucier d’où vivent les données »), mais les bases de données se soucient beaucoup de l’endroit où les octets atterrissent physiquement.
- Les abstractions PV de Kubernetes ont rendu le stockage plus simple à demander et plus difficile à comprendre ; « j’ai demandé 100Gi » n’implique pas « j’ai obtenu des écritures synchrones à faible latence ».
MariaDB vs PostgreSQL : comment leurs schémas I/O punissent le stockage
PostgreSQL : WAL d’abord, puis fichiers de données, puis drame des checkpoints
PostgreSQL écrit les modifications dans le WAL (Write-Ahead Log) puis flush les buffers sales vers les fichiers de données, avec des checkpoints qui coordonnent « jusqu’où le WAL est sûr à recycler ».
Le chemin de WAL fsync est votre rythme cardiaque de durabilité. S’il ralentit, vos commits ralentissent. Et il ralentit de façon très honnête : la latence du stockage apparaît comme la latence des transactions.
Points de douleur typiques dans les conteneurs :
- WAL sur stockage lent (ou pire : sur la couche inscriptible du conteneur) : les latences de commit explosent.
- Pics de checkpoint : vous voyez des tempêtes d’écriture périodiques et des falaises de latence.
- I/O d’autovacuum : lectures/écritures aléatoires soutenues, qui mettent rapidement en évidence les limites d’IOPS.
- fsync et barrières : si votre « volume » ment sur la durabilité, PostgreSQL ne le saura que lorsqu’il sera trop tard.
MariaDB (InnoDB) : redo logs, doublewrite et flushs en arrière-plan
Le moteur InnoDB de MariaDB s’appuie sur les redo logs pour rendre les commits durables (selon innodb_flush_log_at_trx_commit) et utilise un mécanisme de doublewrite pour réduire la corruption par écriture partielle de pages.
En pratique, cela signifie une plus grande amplification d’écriture en échange de sécurité en cas de crash.
Points de douleur typiques :
- Cadence de fsync des redo logs : avec une durabilité stricte, la latence du device de log contrôle le débit.
- Doublewrite + pages de données : vous payez peut-être pour l’écriture de la même page deux fois (ou plus), ce qui pénalise les médias lents.
- Flushs en arrière-plan : si votre stockage ne suit pas, InnoDB peut bloquer le travail au premier plan.
- Binary logs (si activés) : écritures séquentielles supplémentaires qui semblent « peu coûteuses » jusqu’à ce que la politique de fsync se manifeste.
Ce que cela signifie pour « bind mount vs volume »
PostgreSQL est généralement plus sensible à la latence par commit (le chemin WAL fsync est immédiatement visible), tandis qu’InnoDB peut tamponner et étaler la douleur jusqu’à un point de rupture, puis se bloquer plus dramatiquement.
Les deux détestent la latence imprévisible. Les deux détestent le bridage par rafales. Les deux détestent que « votre fsync soit en réalité une suggestion ».
Bind mounts vs volumes Docker : ce qui change réellement
Arrêtons de traiter ces éléments comme des objets mystiques.
- Bind mount : le chemin du conteneur est mappé à un chemin existant sur l’hôte. Vous contrôlez le répertoire hôte, le système de fichiers, les options de montage, les labels SELinux et le cycle de vie.
- Volume nommé Docker : Docker gère un répertoire (généralement sous
/var/lib/docker/volumes) et le monte dans le conteneur. Le stockage de fond reste le système de fichiers de l’hôte, sauf si un driver de volume change cela.
Les différences de performance sont généralement indirectes :
- Paramètres opérationnels par défaut : les volumes ont tendance à atterrir sur le même disque que la racine de Docker, ce qui n’est souvent pas le disque que vous aviez prévu pour les bases de données.
- Options de montage et choix du système de fichiers : les bind mounts rendent plus naturel le choix de systèmes de fichiers « adaptés aux BDD » et des options de montage.
- Surcharge d’étiquetage de sécurité : sur les systèmes SELinux, le relabeling et le contexte peuvent ajouter une surcharge ou provoquer des refus ; généralement des problèmes de correction d’abord, de perf ensuite.
- Flux de sauvegarde/restauration : les bind mounts s’intègrent aux outils hôtes ; les volumes s’intègrent aux outils Docker. Les deux peuvent convenir ; les deux peuvent être abusés.
Si vous utilisez un simple système de fichiers hôte local, bind mounts et volumes contournent OverlayFS. C’est la clé. Vos fichiers de base de données ne doivent pas vivre sur la couche inscriptible de l’overlay.
OverlayFS/overlay2 : le coupable avec un alibi raisonnable
OverlayFS est conçu pour le layering d’images. Il est excellent pour ce pour quoi il a été construit : beaucoup de fichiers majoritairement en lecture seule avec des changements occasionnels en copy-on-write.
Les bases de données sont l’inverse : réécrire les mêmes fichiers, les synchroniser, le faire indéfiniment, et se plaindre bruyamment quand vous ajoutez de la latence.
Le mode de défaillance ressemble à :
- Les petites écritures aléatoires se transforment en opérations CoW plus complexes.
- La rotation des métadonnées augmente : attributs de fichiers, entrées de répertoire, copy-ups.
- Le comportement de fsync devient plus coûteux, surtout sous pression.
Si votre BDD est sur overlay2, vous mesurez le driver de stockage de Docker, pas MariaDB ou PostgreSQL.
Angle Kubernetes : HostPath, PV locaux, PV réseau, et pourquoi votre BDD les déteste différemment
Kubernetes prend votre question simple et la transforme en buffet d’abstractions :
- HostPath : en gros un bind mount vers un chemin du nœud. Rapide et simple. Lie aussi votre pod à un nœud et peut être un piège de disponibilité.
- Persistent Volumes locaux : comme HostPath, mais avec des sémantiques d’ordonnancement et de cycle de vie. Souvent la meilleure option « disque local » si vous acceptez l’affinité nœud.
- Volumes bloc réseau : iSCSI, NVMe-oF, périphériques bloc cloud — souvent bonne latence, mais avec du jitter et des possibilités de contention multi-tenant.
- Systèmes de fichiers réseau : NFS ou FS distribués. Peuvent fonctionner, mais le « mur fsync » est réel et apparaît comme de la latence de commit.
Pour des bases de données stateful, ne considérez pas « PVC lié » comme « problème résolu ». Vous devez savoir ce que vous avez obtenu : profil de latence, comportement fsync, débit sous écrivains concurrents, et comment ça échoue.
Comment benchmarker sans se mentir
Si vous voulez la vérité de performance, testez tout le chemin : DB → libc → kernel → système de fichiers → couche bloc → device.
Ne faites pas de benchmark d’une façon qui évite fsync si votre durabilité en production exige fsync.
De plus, ne lancez pas un seul outil une fois et déclarez la victoire. Les systèmes conteneurisés ont des voisins bruyants, du throttling CPU, des tempêtes de writeback et de la compaction en arrière-plan.
Vous voulez capturer la variance, pas seulement les moyennes.
Une idée paraphrasée de Werner Vogels : « Tout échoue tout le temps — concevez pour pouvoir opérer à travers ces échecs. » (idée paraphrasée)
Playbook de diagnostic rapide
Quand quelqu’un dit « Postgres dans Docker est lent » ou « MariaDB dans Kubernetes est saccadé », faites ceci dans l’ordre. Ne freestylez pas.
-
Première étape : confirmez où vivent réellement les données.
Si c’est sur overlay2, arrêtez-vous et corrigez ça avant de benchmarker quoi que ce soit. -
Deuxième étape : mesurez le chemin de latence fsync/commit.
PostgreSQL : vérifiez les métriques WAL/commit latency ; MariaDB : comportement de flush des redo logs et incidents.
Si les commits sont lents, le stockage est coupable jusqu’à preuve du contraire. -
Troisième étape : identifiez le type de contrainte.
Écritures aléatoires limitées en IOPS vs écritures séquentielles limitées en bande passante vs throttling CPU vs pression mémoire provoquant des tempêtes de writeback. -
Quatrième étape : vérifiez le système de fichiers et les options de montage.
ext4 vs XFS vs ZFS, barrières, discard, atime, mode journalisation, et si vous êtes sur un loopback file. -
Cinquième étape : cherchez les tueurs d’I/O en arrière-plan.
Checkpoints, autovacuum, purge, compaction, jobs de sauvegarde, log shipping au niveau nœud, agents antivirus/scan. -
Sixième étape : comparez bind mount vs volume seulement après.
Si les deux pointent vers le même système de fichiers sous-jacent, la différence de perf est généralement une erreur d’arrondi — sauf si vous avez introduit des options différentes ou des devices différents.
Tâches pratiques : commandes, sorties et quelle décision prendre
Ce sont de vraies tâches d’ops. Chacune inclut des commandes, ce que la sortie signifie et la décision suivante.
Exécutez-les sur l’hôte et à l’intérieur des conteneurs selon le cas.
Task 1: Prove whether the DB is on overlay2 or a real mount
cr0x@server:~$ docker inspect -f '{{.GraphDriver.Name}} {{json .Mounts}}' pg01
overlay2 [{"Type":"volume","Name":"pgdata","Source":"/var/lib/docker/volumes/pgdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Ce que cela signifie : GraphDriver est overlay2 pour la couche du conteneur, mais le répertoire de données est un volume mount vers un chemin hôte sous le répertoire de volumes de Docker. C’est bien : les données de la BDD contournent overlay2.
Décision : Si vous ne voyez pas le datadir listé sous .Mounts (ou s’il pointe vers le système de fichiers du conteneur), vous êtes sur overlay2. Déplacez le datadir vers un bind mount ou un volume immédiatement.
Task 2: Find the actual filesystem type backing your bind mount/volume
cr0x@server:~$ df -T /var/lib/docker/volumes/pgdata/_data
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Ce que cela signifie : Vos volumes Docker résident sur XFS sur /. Cela peut être NVMe (bien) ou un disque racine partagé (souvent mauvais).
Décision : Si ce n’est pas le disque prévu, déplacez le stockage BDD vers un point de montage dédié (bind mount ou driver de volume). Les disques racine deviennent bruyants rapidement.
Task 3: Check whether your “volume” is actually a loopback file (quiet disaster)
cr0x@server:~$ mount | grep -E '/var/lib/docker|/var/lib/kubelet|loop'
/dev/loop0 on /var/lib/docker type ext4 (rw,relatime)
/dev/nvme0n1p2 on / type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
Ce que cela signifie : Docker est sur un device loop. Cela signifie souvent « un périphérique bloc backé par un fichier » (comme devicemapper dans un fichier, ou un stockage imbriqué).
Le loopback ajoute une surcharge et peut transformer fsync en farce de performance.
Décision : Ne passez pas de bases de données sérieuses sur un stockage Docker backé par loopback. Corrigez d’abord la disposition du stockage hôte.
Task 4: Confirm mount options that affect durability and latency
cr0x@server:~$ findmnt -no TARGET,FSTYPE,OPTIONS /var/lib/docker/volumes/pgdata/_data
/ xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota
Ce que cela signifie : Vous pouvez voir si vous avez des options suspectes comme nobarrier (dangereux), des réglages de journalisation étranges ou des options métadonnées lourdes.
Décision : Si vous trouvez des options de montage qui réduisent la sécurité pour poursuivre la performance, arrêtez. Réparez la performance en améliorant le device et la disposition, pas en retirant les ceintures de sécurité.
Task 5: Check PostgreSQL settings that directly translate to storage behavior
cr0x@server:~$ docker exec -it pg01 psql -U postgres -c "SHOW data_directory; SHOW wal_level; SHOW synchronous_commit; SHOW full_page_writes; SHOW checkpoint_timeout;"
data_directory
-------------------------------
/var/lib/postgresql/data
(1 row)
wal_level
-----------
replica
(1 row)
synchronous_commit
-------------------
on
(1 row)
full_page_writes
------------------
on
(1 row)
checkpoint_timeout
--------------------
5min
(1 row)
Ce que cela signifie : Cette configuration privilégie la durabilité. WAL est requis pour des replicas, les commits sont synchrones, et full-page writes est activé (sécurité contre les pages déchirées).
Décision : Si la performance est mauvaise, ne commencez pas par désactiver la durabilité. Commencez par déplacer le WAL vers un stockage plus rapide et corriger la latence.
Task 6: Check MariaDB/InnoDB durability knobs that hit fsync
cr0x@server:~$ docker exec -it mariadb01 mariadb -uroot -psecret -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog'; SHOW VARIABLES LIKE 'innodb_doublewrite';"
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| innodb_doublewrite| ON |
+-------------------+-------+
Ce que cela signifie : Vous payez la « facture de durabilité réelle ». Chaque commit flush le redo log ; binlog est sync ; doublewrite est activé.
Décision : Si vous avez besoin de cette durabilité, vous devez acquérir un stockage capable de la supporter. Si vous n’en avez pas besoin (rare en production), soyez explicite sur le risque et documentez-le.
Task 7: Observe per-device latency and queue depth (host view)
cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (server) 12/31/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
6.12 0.00 2.31 8.44 0.00 83.13
Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 221.0 842.0 7824.0 29812.0 74.1 9.82 11.6 4.2 13.5 0.6 64.3
Ce que cela signifie : await ~11.6ms et avgqu-sz ~9.8 suggèrent des écritures en file d’attente. Pour une BDD occupée, 10–15ms de latence d’écriture peut absolument se traduire par des commits lents.
Décision : Si await est élevé et %util est élevé, vous êtes lié par le stockage. Corrigez la disposition du stockage ou migrez vers des médias plus rapides avant de toucher au tuning des requêtes BDD.
Task 8: Catch filesystem writeback storms (memory pressure pretending to be storage)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 81232 65488 9231440 0 0 124 980 2840 6120 7 2 86 5 0
3 1 0 42112 65284 9150020 0 0 118 8450 3120 7450 6 3 63 28 0
4 2 0 23840 65110 9121000 0 0 102 16820 3401 8012 6 4 52 38 0
Ce que cela signifie : L’augmentation de b (bloqués), la hausse de bo (blocs sortants) et la montée de wa suggèrent des pages sales qui sont flushées fort. Cela peut ressembler à « le stockage est lent » mais le déclencheur est une pression mémoire.
Décision : Si la mémoire est tendue, donnez plus de RAM au nœud, réduisez les workloads concurrents ou ajustez la mémoire de la BDD afin que le kernel ne soit pas forcé à des flushs paniques.
Task 9: Verify cgroup CPU throttling (the fake storage bottleneck)
cr0x@server:~$ docker inspect -f '{{.HostConfig.NanoCpus}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}' pg01
0 200000 100000
Ce que cela signifie : Le quota CPU est de 2 cœurs (200000/100000). Si vous atteignez ce plafond, les workers en arrière-plan (checkpoints, flushing, vacuum) ralentissent et le stockage semble « bizarre ».
Décision : Si la BDD est throttlée CPU, augmentez les limites avant de migrer le stockage. Une BDD affamée de CPU ne peut pas piloter l’I/O efficacement.
Task 10: Measure PostgreSQL commit and checkpoint behavior from stats
cr0x@server:~$ docker exec -it pg01 psql -U postgres -c "SELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, maxwritten_clean FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend | maxwritten_clean
------------------+-----------------+-------------------+----------------+------------------
118 | 43 | 9823412 | 221034 | 1279
(1 row)
Ce que cela signifie : Un buffers_checkpoint élevé indique des écritures drivées par les checkpoints. Si les pics de latence coïncident avec les checkpoints, le débit stockage et le comportement de writeback sont mis en cause.
Décision : Envisagez d’étaler l’I/O des checkpoints (tune checkpoint settings), mais seulement après avoir confirmé que le stockage n’est pas fondamentalement sous-dimensionné.
Task 11: Check PostgreSQL WAL location and consider isolating it
cr0x@server:~$ docker exec -it pg01 bash -lc "ls -ld /var/lib/postgresql/data/pg_wal && df -T /var/lib/postgresql/data/pg_wal"
drwx------ 1 postgres postgres 4096 Dec 31 09:41 /var/lib/postgresql/data/pg_wal
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Ce que cela signifie : Le WAL partage le même système de fichiers que les données. C’est courant, mais pas toujours idéal.
Décision : Si la latence de commit est votre problème, montez le WAL sur un device/volume plus rapide ou moins contendu (volume/bind mount séparé). Le WAL est petit mais exigeant.
Task 12: Confirm MariaDB datadir placement and filesystem
cr0x@server:~$ docker exec -it mariadb01 bash -lc "mariadb -uroot -psecret -e \"SHOW VARIABLES LIKE 'datadir';\" && df -T /var/lib/mysql"
+---------------+----------------+
| Variable_name | Value |
+---------------+----------------+
| datadir | /var/lib/mysql/|
+---------------+----------------+
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Ce que cela signifie : Les données MariaDB sont sur XFS à la racine. Bien si la racine est rapide et dédiée ; mauvais si la racine est partagée avec tout le reste.
Décision : Si vous voyez cela atterrir sur un disque racine générique, déplacez vers un point de montage dédié avec des caractéristiques de performance connues.
Task 13: Quick and dirty fsync reality check with fio (host path used by bind/volume)
cr0x@server:~$ fio --name=fsync-test --directory=/var/lib/docker/volumes/pgdata/_data --rw=write --bs=4k --size=256m --ioengine=sync --fsync=1 --numjobs=1 --runtime=20 --time_based --group_reporting
fsync-test: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
...
write: IOPS=820, BW=3280KiB/s (3360kB/s)(64.0MiB/20003msec)
clat (usec): min=220, max=42000, avg=1216.55, stdev=3100.12
Ce que cela signifie : Ceci approximate « petites écritures avec fsync ». Latence moyenne ~1.2ms mais max 42ms : cette latence en queue est précisément ce qui devient « commits aléatoirement lents ».
Décision : Si les latences max sont moches, corrigez la contention de stockage ou migrez vers de meilleurs médias. Le tuning BDD ne peut pas négocier avec la physique.
Task 14: Check whether your database is silently hitting filesystem limits (inodes, space, reserved blocks)
cr0x@server:~$ df -h /var/lib/docker/volumes/pgdata/_data && df -i /var/lib/docker/volumes/pgdata/_data
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 447G 118G 330G 27% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 224M 3.2M 221M 2% /
Ce que cela signifie : L’espace et les inodes sont corrects. Quand ceux-ci se remplissent, les BDD se comportent étrangement : autovacuum échoue, fichiers temporaires échouent, crashs à l’installation d’extensions, ce que vous voulez.
Décision : Si l’espace libre est bas ou l’utilisation des inodes élevée, corrigez cela avant le travail de performance. Les disques pleins rendent la performance « intéressante » dans le pire sens.
Task 15: Confirm no one is “helpfully” backing up by rsyncing the live datadir
cr0x@server:~$ ps aux | grep -E 'rsync|tar|pg_basebackup|mariabackup' | grep -v grep
root 19344 8.2 0.1 54360 9820 ? Rs 09:44 0:21 rsync -aH --delete /var/lib/docker/volumes/pgdata/_data/ /mnt/backup/pgdata/
Ce que cela signifie : Quelqu’un fait un rsync du datadir en live. C’est à la fois une taxe de performance et (pour PostgreSQL) une méthode de backup inconsistente sauf si elle est faite correctement.
Décision : Arrêtez de faire des copies au niveau filesystem d’une BDD vivante à moins d’utiliser des sémantiques de snapshot appropriées et des méthodes de sauvegarde coordonnées avec la BDD.
Erreurs courantes : symptôme → cause racine → correction
1) Symptom: « Container DB is slow, but host is fast »
Cause racine : Les données de la BDD sont sur overlay2/couche inscriptible du conteneur, pas sur un mount.
Correction : Montez le datadir comme bind mount ou volume nommé sur un vrai système de fichiers hôte. Confirmez avec docker inspect les mounts.
2) Symptom: Random 200–2000ms commit spikes
Cause racine : Latence en queue du stockage due à des voisins bruyants, jitter des PV réseau ou comportement de flush du cache du device. Souvent visible comme des stalls de fsync.
Correction : Déplacez WAL/redo logs vers un stockage à plus faible latence, isolez les disques BDD, et mesurez les distributions de latence (pas seulement le débit moyen).
3) Symptom: PostgreSQL periodic latency cliffs every few minutes
Cause racine : Checkpoints causant des tempêtes d’écriture ; des buffers sales s’accumulent puis flushent par rafales.
Correction : Ajustez le pacing des checkpoints et assurez-vous que le stockage peut absorber des écritures soutenues. Si le stockage est marginal, le tuning n’est que du vernis sur un cochon qui déclenche une alerte pagée.
4) Symptom: MariaDB stalls under load, then recovers
Cause racine : Le flushing en arrière-plan d’InnoDB ne suit pas ; redo log ou doublewrite amplifie les écritures ; blocages éventuels.
Correction : Améliorez la latence/IOPS du stockage, validez les réglages de durabilité, et évitez de partager le disque avec des services d’écriture lourde non liés.
5) Symptom: « Switching to Docker volumes made it slower »
Cause racine : Le volume vit sur le filesystem racine avec des options de montage différentes, possiblement sur un média plus lent, ou en contention avec les pulls d’images Docker et les logs.
Correction : Placez les volumes sur un stockage dédié (bind mount vers un point de montage dédié, ou configurez data-root de Docker vers un disque plus rapide).
6) Symptom: « Bind mounts are slow on my Mac/Windows laptop »
Cause racine : Docker Desktop partage les fichiers à travers une frontière de virtualisation ; les sémantiques du système de fichiers hôte sont émules et plus lentes.
Correction : Pour le dev local, préférez les volumes Docker à l’intérieur de la VM Linux. Pour la production Linux, ce problème spécifique disparaît.
7) Symptom: Database corruption after host crash, despite “fsync on”
Cause racine : La pile de stockage a menti sur les écritures durables (cache d’écriture sans protection contre la perte de puissance, options de montage dangereuses, contrôleur RAID mal configuré).
Correction : Utilisez un stockage enterprise avec protection contre la perte de puissance ; vérifiez les politiques de cache ; ne désactivez pas les barrières ; testez le comportement de récupération après crash.
8) Symptom: High iowait but low disk %util
Cause racine : Souvent latence de stockage réseau, throttling cgroup, ou verrous au niveau fichier. Le disque n’est pas « occupé », il est « loin ».
Correction : Mesurez la latence bout en bout (statistiques du device bloc, métriques PV réseau), et vérifiez le throttling CPU et la pression mémoire.
Trois mini-histoires du monde corporate
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne a migré un service de paiements de VMs vers Kubernetes. L’équipe a fait la chose sensée sur le papier : les workloads stateful ont eu des PVC, les stateless ont eu emptyDir.
Ils ont supposé que le PVC signifiait « disque réel ». Ils ont aussi supposé que la storage class nommée « fast » signifiait « faible latence ».
Le service utilisait PostgreSQL avec des commits synchrones. Pendant la fenêtre de migration, tout a passé les tests fonctionnels. Puis le lundi matin le trafic est arrivé, et la latence p99 des transactions a déraillé.
Les pods DB n’étaient ni CPU-bound ni memory-bound. Les nœuds semblaient sains. L’équipe applicative a blâmé Postgres « en conteneurs ».
L’histoire réelle : la storage class « fast » était une solution backée par un système de fichiers réseau qui allait bien pour des uploads web et des logs, mais avait une latence en queue épouvantable sous des charges fsync-intensives.
Le PVC a fait exactement ce qu’il promettait : stockage persistant. Il n’a pas promis une latence de commit prévisible.
La correction fut ennuyeuse et structurelle : déplacer le WAL vers une storage class bloc à plus faible latence, garder les fichiers de données sur la classe existante (ou aussi déplacer selon le coût), et pinner les pods DB sur des nœuds avec de meilleures voies réseau.
L’amélioration la plus importante est venue de la reconnaissance que « persistant » n’est pas synonyme de « adapté aux bases de données ».
Mini-histoire 2 : L’optimisation qui a mal tourné
Une autre organisation exécutait MariaDB dans Docker sur une flotte d’hôtes Linux. Les performances étaient correctes, mais il y avait des stalls d’écriture occasionnels.
Quelqu’un a remarqué que le sous-système disque avait un cache d’écriture et a pensé : « On peut rendre fsync moins cher. »
Ils ont changé des options de montage et des réglages de contrôleur pour réduire les barrières et le comportement de flush. Les benchmarks se sont améliorés. Tout le monde était content.
Deux semaines plus tard, un hôte a rebooté de façon inattendue. MariaDB a récupéré, mais une poignée de tables présentait une corruption subtile : pas immédiatement catastrophique, mais suffisante pour produire de mauvais résultats.
Le postmortem fut douloureux parce que rien n’était « visiblement faux » dans les métriques. La défaillance vivait dans l’écart entre ce que la base demandait (fsync signifie durable) et ce que le stockage livrait (accusé de réception rapide, persistance plus tard).
L’incident n’est pas apparu dans un benchmark au chemin heureux. Il est apparu dans la physique plus un événement d’alimentation non planifié.
Ils ont rollbacké les réglages dangereux, mis en place des backups appropriés avec des drills de récupération, et upgradé les contrôleurs de stockage avec protection contre la perte de puissance.
Les performances ont légèrement chuté. L’entreprise l’a accepté. Des données incorrectes coûtent plus cher que des commits plus lents, et les comptables comprennent ce langage.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une troisième équipe avait l’habitude qui semblait presque désuète : avant tout changement de stockage, ils exécutaient la même petite suite de tests et enregistraient les résultats.
Pas sophistiqué. Juste cohérent. Quelques profils fio, une charge d’écriture spécifique à la BDD, et une répétition de snapshot/restore.
Ils déployaient une nouvelle config de runtime de conteneur et déplaçaient le data-root de Docker vers un autre disque.
Quelqu’un a suggéré « c’est juste des métadonnées Docker, les volumes ne seront pas affectés ». L’équipe a testé quand même.
Les tests ont montré une régression de la latence en queue de fsync après le déplacement. Pas des moyennes énormes — juste des outliers pires.
Le coupable s’est avéré être la contention disque : le nouveau disque hébergeait aussi les logs système et un agent de monitoring qui faisait parfois des pics d’écriture.
Parce qu’ils ont testé avant le rollout, ils l’ont détecté tôt, déplacé les volumes BDD vers des montages dédiés et évité un incident de lenteur progressif.
Ce n’était pas héroïque. C’était de la supervision adulte.
Blague n°2 : La seule chose plus persistante qu’un volume de base de données est un ingénieur affirmant que son dernier benchmark « était définitivement représentatif ».
Listes de contrôle / plan étape par étape
Étape par étape : choisir le stockage pour Postgres ou MariaDB dans des conteneurs
- Décidez de vos besoins : durabilité stricte vs « on peut perdre la dernière seconde ». Notez-le. Faites signer l’affaire par le métier (ou au moins créez un ticket).
- Placez les données BDD hors overlay2 : utilisez bind mount ou volume nommé, mais assurez-vous que le système de fichiers de backing est connu et voulu.
- Choisissez le système de fichiers délibérément : ext4 ou XFS sont des défauts courants. ZFS peut être excellent avec précautions, mais ne le traitez pas comme magique.
- Utilisez un stockage dédié pour la BDD : point de montage séparé. Évitez le disque racine partagé. Évitez les devices loopback.
- Isolez WAL/redo logs si la latence de commit compte : device/volume séparé si nécessaire. C’est souvent le gain significatif le moins cher.
- Benchmarkez le chemin que vous utiliserez : incluez fsync. Capturez la latence en queue. Testez en concurrence réaliste.
- Confirmez les workflows opérationnels : backups, restores, drills de recovery, et comment vous migrez/redimensionnez les volumes.
- Mettez en place du monitoring ciblé : latence de commit, durée des checkpoints, temps de fsync, disk await, et pression sur le nœud.
- Documentez les points de montage et la propriété : permissions, labels SELinux, et exactement quel chemin hôte mappe quel chemin conteneur.
- Retestez après chaque changement de plateforme : mise à jour du kernel, changement de storage class, changement de runtime, ou changement d’image du nœud.
Checklist : décision « bind mount vs volume »
- Utilisez un bind mount quand vous voulez un contrôle explicite du choix du disque, des options de montage et des outils opérationnels sur l’hôte.
- Utilisez un volume nommé quand vous voulez que Docker gère le cycle de vie et que vous faites confiance à l’emplacement du data root de Docker (ou utilisez un bon driver de volume).
- Évitez les deux s’ils reposent sur un stockage réseau à latence imprévisible et que vous avez besoin de performances de commit strictes ; choisissez plutôt une meilleure classe/device de stockage.
FAQ
1) Les volumes Docker sont-ils plus rapides que les bind mounts sur Linux ?
Généralement ils sont identiques si ils résident sur le même système de fichiers et device sous-jacent. Les différences de perf viennent de l’endroit où ils atterrissent et de ce qui les backe, pas du type de montage.
2) Est-il jamais acceptable d’exécuter Postgres/MariaDB sur overlay2 ?
Pour du dev jetable et des tests, oui. Pour tout ce qui compte, non. OverlayFS est optimisé pour le layering d’images, pas pour la durabilité et les schémas d’écriture des bases de données.
3) Pourquoi PostgreSQL semble-t-il plus sensible à la latence du stockage que MariaDB ?
Le chemin de commit de PostgreSQL tape souvent immédiatement sur WAL fsync (avec des commits synchrones). Si la latence du stockage pique, vos commits piquent. InnoDB peut tamponner différemment, puis se bloquer plus tard.
4) Si je désactive fsync ou active async commit, la performance sera-t-elle « réglée » ?
Ce sera plus rapide et moins correct. Vous pourriez payer la vitesse par de la corruption future ou des transactions perdues après un crash. Si vous choisissez ce compromis, documentez-le comme une démolition contrôlée.
5) Dois-je séparer WAL et données sur des volumes différents ?
Souvent oui quand la latence de commit compte et que vous avez des I/O mixtes. WAL est séquentiel-ish et sensible à la latence ; les écritures de données peuvent être rafales lors des checkpoints. La séparation réduit la contention.
6) Dans Kubernetes, HostPath est-il une bonne idée pour les bases de données ?
Cela peut être rapide et prévisible parce que c’est local. Mais cela lie à un nœud et complique le basculement. Les PV locaux sont la version plus structurée de cette idée.
7) Pourquoi les bind mounts étaient-ils terribles sur mon laptop ?
Docker Desktop utilise une VM. Les bind mounts traversent la frontière hôte/VM et peuvent être lents. Les volumes à l’intérieur de la VM sont généralement plus rapides pour le dev.
8) Quel est le piège le plus courant de « performance de volume » en production ?
Supposer qu’un volume persistant implique faible latence et bon comportement de fsync. La persistance concerne la survie, pas la vitesse. Mesurez la latence et le comportement en queue.
9) ext4 vs XFS pour des bases de données conteneurisées ?
Les deux peuvent être excellents. Ce qui compte davantage est la qualité du device, les options de montage, la version du kernel et éviter les couches pathologiques (loopback, overlay pour les données, disque racine contendu).
10) Quelle est la façon la plus rapide de trancher un argument entre « bind mount est plus rapide » et « volume est plus rapide » ?
Placez les deux sur le même système de fichiers/device sous-jacent et exécutez une charge incluant fsync. Si elles diffèrent sensiblement, vous avez découvert une différence d’environnement, pas une vérité philosophique.
Étapes suivantes réalisables cette semaine
- Auditez le placement : listez tous les conteneurs/pods DB et prouvez que leurs datadirs sont sur des mounts, pas sur overlay2.
- Cartographiez la réalité du stockage : pour chaque BDD, enregistrez le type de système de fichiers, le device, les options de montage et s’il est local ou en réseau.
- Mesurez la latence en queue de fsync : lancez un petit test fio fsync sur le chemin exact utilisé par le datadir/WAL/redo.
- Séparez les logs si nécessaire : isolez le WAL PostgreSQL ou les logs MariaDB sur un stockage plus rapide quand la latence de commit pose problème.
- Stabilisez le nœud : retirez les services d’écriture lourde concurrents du disque BDD, et vérifiez que limites CPU/mémoire ne throttlent pas la BDD en schémas I/O étranges.
- Rédigez votre posture de durabilité : Postgres et MariaDB vous laissent échanger sécurité contre vitesse. Prenez cette décision explicitement et révisez-la trimestriellement.
La vérité de la performance n’est pas « bind mount vs volume ». C’est « quel chemin de stockage avez-vous réellement construit, et respecte-t-il fsync sous pression ? »
Réglez cela correctement, et MariaDB comme PostgreSQL se comporteront comme des systèmes adultes — prévisibles, suffisamment rapides et honnêtes sans compromis avec la physique.