Le checkout est un chemin d’écriture. Ce n’est pas une barre de recherche. Ce n’est pas une navigation catalogue favorable au cache. C’est un tas d’inserts, de mises à jour, de contraintes et de paranoïa « est‑ce qu’on les a vraiment débités deux fois ? » — tout cela arrivant en rafales modelées par des campagnes marketing.
Quand les écritures e‑commerce tournent mal, ce n’est généralement pas subtil. Pics de latence, retard de réplication, surventes d’inventaire, et soudainement votre plan « haute disponibilité » se réduit à une conversation de groupe et une prière. La question n’est pas « MariaDB ou PostgreSQL est plus rapide ». La question est : lequel s’étouffe en premier avec votre modèle d’écritures spécifique, et que faire avant que ça n’arrive un samedi.
La vérité inconfortable : les deux peuvent s’étouffer
MariaDB et PostgreSQL sont tous deux des bases sérieuses. Ils peuvent fournir un débit effrayant et une latence étonnamment civilisée. Ils peuvent aussi tous deux se désagréger de façons très prévisibles lorsque vous traitez les écritures e‑commerce comme une ruée : réservations d’inventaire, machines à états de paiement, devis d’expédition, utilisation de coupons, et « tout auditer pour toujours ».
Ce qui diffère, c’est comment ils échouent, combien d’avertissements vous avez, et combien coûte l’élimination du mode de défaillance.
- PostgreSQL est généralement limité par la dynamique du WAL et des checkpoints, la dette de vacuum et le risque d’enroulement des identifiants de transaction si vous négligez l’entretien. Quand il est mécontent, vous verrez une pression IO, du bloat, et des files d’attente de verrous qui donnent l’impression que « la base est vivante mais rien ne bouge ».
- MariaDB (InnoDB) est généralement limité par la pression du redo/binlog, la dynamique du retard de réplication, la contention des mutex/verrous autour des lignes chaudes, et parfois des réglages « on l’a optimisé pour la vitesse » qui suppriment silencieusement vos garanties de récupération. Quand il est mécontent, vous verrez les réplications prendre du retard, des pics de deadlocks, et des commits qui font la queue comme à un mauvais contrôle de sécurité d’aéroport.
Choisissez en fonction de votre modèle d’écriture, de votre maturité opérationnelle et de votre volonté d’imposer de la discipline dans le schéma et la conception des transactions. Si vous ne pouvez pas imposer de discipline, choisissez le système dont le mode de défaillance est le plus rapide à détecter et à atténuer. L’orgueil n’est pas une stratégie de monitoring.
Modèles d’écriture qui cassent l’e‑commerce
La plupart des plateformes e‑commerce ne meurent pas à cause de « trop de requêtes ». Elles meurent à cause de quelques modèles d’écriture spécifiques qui évoluent de façon non linéaire.
1) Lignes chaudes : inventaire, coupons et compteurs
Si une seule SKU devient virale, une seule ligne d’inventaire devient une ressource contestée. Si vous avez « utilisations de coupon restantes » dans une seule ligne, elle devient une ressource contestée. Si vous suivez « orders_today » comme un compteur unique, vous vous êtes créé une attaque DDoS — en interne.
Les deux bases peuvent gérer la contention, mais aucune ne défie la physique : une seule ligne ne peut être mise à jour qu’un certain nombre de fois par seconde avant que vous ne sériez tout votre checkout. La solution est rarement « plus de CPU ». C’est généralement changer le modèle de données ou isoler le chemin chaud.
2) Transactions longues pendant les workflows de checkout
Le checkout est un workflow. Les workflows invitent aux transactions longues : réserver l’inventaire, appeler le prestataire de paiement, calculer la taxe, interroger l’API d’expédition, écrire des lignes d’audit, puis commit. C’est une excellente manière de maintenir des verrous en attendant Internet.
Si votre transaction s’étend sur des appels réseau, vous prenez la base de données en otage jusqu’à ce que la sonnerie du pager de quelqu’un d’autre se déclenche.
3) Index secondaires partout
Chaque index est une écriture. Chaque index nécessite aussi de la maintenance. Dans les tables à fort trafic d’écriture (orders, order_items, payments, inventory_events), l’enthousiasme pour les index est la manière d’acheter de la latence et de l’amplification IO au prix fort.
4) Audit append‑only intensif sans partitionnement
Les tables d’audit sont les nouveaux journaux. Sauf que les journaux tournent, et les tables d’audit souvent pas. Les tables append‑only à forte écriture sans partitionnement invitent au bloat, à des index gigantesques, à des suppressions lentes, et à la tension du vacuum (Postgres) ou à la pression de purge (InnoDB).
5) Réplication et durabilité transformées en astuce perf
Inévitablement quelqu’un dit : « On peut accélérer si on relâche la durabilité. » Ils n’ont pas tort ; ils jouent juste avec l’état business. On peut relâcher la durabilité en sécurité dans certains cas. Le checkout n’en fait pas partie.
Blague #1 : désactiver la durabilité pour un checkout plus rapide, c’est comme enlever les ceintures de sécurité parce qu’on est en retard — votre ETA s’améliore jusqu’au moment où elle ne s’améliore plus.
Comment PostgreSQL s’étouffe généralement en premier
La mécanique d’écriture Postgres en un paragraphe (la version utile à 2 h du matin)
Postgres utilise MVCC avec des versions de tuples : les mises à jour créent de nouvelles versions de ligne, les anciennes versions persistent jusqu’à ce que le vacuum les récupère. Chaque transaction écrit dans le WAL pour la durabilité et la réplication. Les checkpoints flushent les pages sales. Si autovacuum prend du retard, vous obtenez du bloat et une IO croissante. Si les checkpoints sont trop agressifs ou le WAL contraint, vous obtenez des pics IO et de la latence de commit. Si vous ignorez la maintenance des identifiants de transaction, vous pouvez approcher l’enroulement et être forcé à un vacuum d’urgence au pire moment.
Mode de défaillance A : pression WAL et checkpoints → pics de latence
Sous forte charge d’écriture, le flux WAL devient le pouls. Si le WAL ne peut pas être écrit assez vite (disque lent, IOPS saturés, mauvais comportement de fsync), les commits font la queue. Ensuite les checkpoints arrivent et commencent à vider beaucoup de buffers sales, se battant avec les écritures de premier plan. Le symptôme est généralement des pics de latence p99 et une utilisation IO au maximum, pas nécessairement le CPU.
Opérationnellement, Postgres a tendance à « se dénoncer » via des métriques : fréquence des checkpoints, buffers écrits par les backends, temps d’écriture WAL. Encore faut‑il regarder.
Mode de défaillance B : dette de vacuum → bloat → tout ralentit
Le MVCC est génial jusqu’à ce que vous oubliez de nettoyer. Si vos tables à forte écriture sont constamment mises à jour et que l’autovacuum ne suit pas, vous obtenez du bloat. Le bloat signifie des index plus volumineux, un heap plus grand, plus de cache misses, plus d’IO. La base fait toujours le même travail logique, mais traîne maintenant une valise plus grosse partout.
En e‑commerce, les générateurs classiques de bloat sont : mises à jour d’état de commande, mises à jour d’état d’inventaire, tentatives de paiement, et tout pattern d’upsert qui devient une tempête de mises à jour.
Mode de défaillance C : files d’attente de verrous qui ressemblent à une base lente
Postgres est bon au verrouillage au niveau ligne, mais les transactions longues, le DDL en production et les « SELECT … FOR UPDATE » mal pensés peuvent provoquer des chaînes de verrous. Vous entendrez : « Postgres est lent aujourd’hui. » Il n’est pas lent ; il est bloqué.
Mode de défaillance D : trop de connexions (et l’illusion de montée en charge)
Postgres utilise un modèle de processus (ou de type processus) ; trop de connexions créent une surcharge mémoire et des changements de contexte. Un système à forte écriture avec 2 000 connexions actives peut ressembler à un problème CPU mais est souvent un problème de gestion des connexions. La correction est ennuyeuse : pool, timeouts sensés et backpressure.
Comment MariaDB s’étouffe généralement en premier
Mécanique d’écriture InnoDB en un paragraphe (la version « pourquoi les commits sont bloqués ? »)
InnoDB utilise des redo logs et un buffer pool. Les écritures vont en mémoire et dans le redo ; les pages sont flushées plus tard. Les binlogs enregistrent les changements pour la réplication. Le commit peut impliquer le flush du redo et/ou du binlog selon les réglages de durabilité et le comportement de group commit. Sous charge, le système est souvent limité par la bande passante d’écriture du redo log, le comportement de flush, ou l’application des changements en réplica.
Mode de défaillance A : le retard de réplication devient la vraie panne
Beaucoup d’architectures e‑commerce s’appuient sur des réplicas pour les lectures. Sous forte charge d’écriture, les réplicas peuvent prendre du retard. Alors votre application lit un état obsolète : l’inventaire semble disponible alors qu’il ne l’est plus, l’état du paiement semble « pending » alors qu’il est « captured », et les clients reçoivent des e‑mails de confirmation en double. La base peut être saine ; l’architecture ne l’est pas.
Le retard de réplication MariaDB est souvent amplifié par : transactions volumineuses, apply mono‑thread (selon la configuration), et des schémas qui forcent la contention sur le réplica lors du replay des écritures.
Mode de défaillance B : contention de lignes chaudes et deadlocks
InnoDB est généralement excellent, mais les lignes chaudes (inventaire, compteurs, lignes de coupon) créent de la contention de verrou. Avec assez de concurrence, vous obtenez des deadlocks. Les deadlocks ne sont pas intrinsèquement mauvais — InnoDB les détecte et rollback une transaction — mais des deadlocks répétés peuvent devenir un plafond de débit et un essaim de retries.
Mode de défaillance C : réglages fsync/flush rapides jusqu’au crash
MariaDB facilite le compromis durabilité/performance (et les gens le font). Des réglages comme un flush redo relaxé ou un sync_binlog relâché réduisent les fsync et augmentent le débit. Puis un nœud plante et vous découvrez que votre « petit risque » est un chariot d’ordres manquants.
Mode de défaillance D : amplification d’écriture des index secondaires et splits de pages
Les tables à fort trafic d’écriture avec plusieurs index secondaires peuvent provoquer une IO aléatoire lourde. Si la clé primaire est mal choisie (p. ex. UUID aléatoire sans stratégie d’ordonnancement), vous pouvez amplifier les splits de pages et remuer le buffer pool. Ce n’est pas unique à MariaDB, mais l’index cluster InnoDB rend le choix de la clé primaire particulièrement important.
Guide de décision : que choisir pour un checkout écritures‑lourd
Si votre modèle d’écriture est « beaucoup de mises à jour sur les mêmes lignes »
C’est le problème des lignes chaudes. Les deux systèmes en souffrent. Votre meilleur mouvement est la chirurgie du modèle de données :
- Utilisez des événements append‑only pour les mouvements d’inventaire et calculez la disponibilité via une vue dérivée (ou une projection mise en cache) au lieu de mettre constamment à jour une ligne.
- Shardez les compteurs (par minute/par bucket) au lieu d’un compteur global unique.
- Utilisez des clés d’idempotence pour que les retries ne multiplient pas les écritures.
Le choix de la base aide moins que le schéma et la conception des transactions ici.
Si votre modèle d’écriture est « append‑only, volume élevé »
Postgres est souvent plus facile à maintenir stable si vous investissez dans le partitionnement et le tuning d’autovacuum ; il offre une bonne introspection. MariaDB peut aussi très bien faire, mais vous devez surveiller la pression redo/binlog et la vitesse d’application des réplicas comme un faucon.
Si vous comptez beaucoup sur des réplicas de lecture pour des lectures liées à la correction
Méfiez‑vous de la réplication asynchrone MariaDB à moins que vous ne conçeviez pour elle. La réplication asynchrone Postgres prend aussi du retard, mais les équipes Postgres assortissent souvent cela de modèles plus clairs : lecture‑vos‑écritures sur le primaire pour les flux critiques, et un pooler plus une logique de routage. Quoi qu’il en soit : si une lecture influence une décision d’écriture (inventaire restant, validité d’un coupon), lisez à partir d’une source avec la cohérence appropriée.
En une ligne : qui s’étouffe en premier ?
- PostgreSQL a tendance à s’étouffer d’abord sur la IO et la dette d’entretien : débit WAL, flushs de checkpoints, vacuum à la traîne, surcharge de connexions.
- MariaDB a tendance à s’étouffer d’abord sur la dynamique commit/réplication et la contention : comportement de flush redo/binlog, retard des réplicas, contention sur lignes chaudes, tempêtes de deadlocks.
Ce n’est pas un verdict ; c’est un avertissement. On peut prévenir les deux. Mais on ne peut pas prévenir ce qu’on ne mesure pas.
Faits intéressants et contexte historique
- La lignée MVCC de PostgreSQL remonte aux travaux académiques des années 1980 ; son design multi‑version explique pourquoi les lectures ne bloquent pas les écritures de la manière classique.
- InnoDB n’était pas initialement « le moteur MySQL » ; il est devenu le défaut plus tard parce qu’il a corrigé l’ère des « oops, verrous au niveau table » que les premiers utilisateurs de MySQL se rappellent trop bien.
- MariaDB a été créé comme un fork après des inquiétudes sur la gouvernance de MySQL ; de nombreux comportements opérationnels restent de forme MySQL, y compris les conventions de réplication.
- La réplication Postgres a mûri plus tard que la réplication style binlog MySQL ; la réplication streaming moderne est robuste, mais l’écosystème pousse encore des bonnes pratiques comme le pooling de connexions de façon plus agressive.
- Les mises à jour Postgres ne sont pas en place ; chaque update crée une nouvelle version de tuple. C’est une fonctionnalité, et aussi la raison pour laquelle le bloat est un poste à prévoir dans les systèmes à forte écriture.
- Le primaire cluster InnoDB signifie que les données de table sont physiquement organisées par clé primaire. Le choix de la clé primaire peut fortement influencer l’amplification d’écriture et les splits de pages.
- Postgres a des « HOT updates » (heap‑only tuple updates) qui peuvent éviter la mise à jour d’index dans certains cas, ce qui peut réduire significativement l’IO d’écriture pour certains patterns.
- Les deux systèmes proposent des modes « async commit » et d’autres compromis de durabilité. Le piège est de traiter ces réglages comme des fonctionnalités de performance plutôt que comme des multiplicateurs de risque.
- Les grosses transactions nuisent disproportionnellement à la réplication dans les deux écosystèmes : elles retardent la visibilité sur les réplicas et rendent la reprise après crash/rewind plus pénible.
Mode opératoire de diagnostic rapide
Quand la latence d’écriture du checkout explose, vous avez des minutes pour décider : est‑ce le CPU, l’IO, les verrous, la réplication ou l’accumulation de connexions côté application ? Voici l’ordre qui trouve le goulot le plus vite en pratique.
Premier : confirmer que le symptôme est réel et sa portée
- Est‑ce que la latence de commit p95/p99 augmente, ou seulement un sous‑ensemble d’endpoints ?
- Est‑ce toutes les écritures, ou seulement une table (inventory/orders/payments) ?
- Le primaire est‑il impacté ou seulement les réplicas ?
Deuxième : vérifier verrous et attentes, pas seulement « requêtes lentes »
Les pannes d’écriture sont souvent des files de verrous déguisées en lenteur. Trouvez les sessions bloquées, trouvez le bloqueur, et décidez s’il faut killer une transaction ou corriger le pattern.
Troisième : vérifier la saturation IO et le comportement de fsync
Si le stockage est saturé, la base ne peut pas commit rapidement. Vérifiez l’utilisation du device, les temps d’attente (await), et les débits WAL/binlog. Si vous êtes sur un stockage réseau, supposez‑le coupable jusqu’à preuve du contraire.
Quatrième : santé et retard de réplication
Le retard n’est pas juste un problème d’affichage ; il peut casser la cohérence si vous lisez des réplicas. Si le retard monte pendant l’incident, cessez de router les lectures critiques vers les réplicas immédiatement.
Cinquième : dette d’entretien
Dans Postgres : autovacuum, bloat, churn des checkpoints. Dans MariaDB : purge lag, history list length, pression sur le buffer pool. L’entretien est l’endroit où « ça allait hier » meurt.
Idée paraphrasée de Werner Vogels (fiabilité/ops) : « Tout échoue ; concevez pour pouvoir récupérer vite. » C’est l’attitude à adopter pendant les incidents d’écriture — confinement rapide bat diagnostic parfait.
Tâches pratiques : commandes, sorties et décisions
Voici des tâches opérationnelles réelles à exécuter pendant un incident ou un audit de performance. Chaque élément inclut : commande, ce que signifie la sortie, et quelle décision prendre ensuite. Les commandes supposent Linux et un accès local à l’hôte DB ou un jump host avec les clients appropriés.
Task 1: Is the box IO-bound right now?
cr0x@server:~$ iostat -x 1 5
Linux 6.1.0 (db-primary) 12/29/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.44 0.00 6.21 22.35 0.00 59.00
Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
nvme0n1 120.0 950.0 4800.0 76000.0 150.2 9.80 10.3 0.7 78.0
dm-0 118.0 940.0 4700.0 75500.0 150.6 14.10 15.5 0.8 92.0
Signification : Un %iowait élevé et un %util du device proche de la saturation avec un await élevé suggèrent que l’IO est le facteur limitant ; les commits vont faire la queue.
Décision : Réduire la pression d’écriture (feature flags, limites de taux), s’assurer que les dispositifs WAL/binlog ne sont pas en concurrence avec les données, et envisager d’augmenter les paramètres de checkpoint/WAL (Postgres) ou le comportement de group commit redo/binlog (MariaDB) après l’incident.
Task 2: Which process is doing IO damage?
cr0x@server:~$ pidstat -d 1 5
Linux 6.1.0 (db-primary) 12/29/2025 _x86_64_ (16 CPU)
# Time UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
12:01:01 PM 999 21450 50.00 68000.00 0.00 postgres
12:01:01 PM 999 21990 0.00 4200.00 0.00 postgres
12:01:01 PM 999 18210 0.00 39000.00 0.00 mariadbd
Signification : Vous pouvez voir si le démon DB écrit massivement. Si vous voyez plusieurs démons DB écrire, vous êtes peut‑être sur le mauvais hôte ou en train d’exécuter les deux.
Décision : Si les écritures sont dominées par la DB, entrez dans la DB pour l’inspection des verrous/WAL. Sinon, trouvez le vrai écrivain (backup, logrotate, job batch hors contrôle).
Task 3 (Postgres): Are we waiting on locks or IO?
cr0x@server:~$ psql -X -c "SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE state <> 'idle' GROUP BY 1,2 ORDER BY 3 DESC;"
wait_event_type | wait_event | count
----------------+---------------------+-------
Lock | transactionid | 23
IO | WALWrite | 7
LWLock | BufferMapping | 4
| | 2
Signification : De nombreuses sessions en attente sur Lock/transactionid indiquent de la contention ou des transactions longues. IO/WALWrite pointe vers un goulot d’écriture WAL.
Décision : Si les attentes de verrous dominent, identifiez les bloqueurs et raccourcissez les transactions. Si WALWrite domine, concentrez‑vous sur le stockage/les paramètres WAL et le comportement des checkpoints.
Task 4 (Postgres): Who is blocking whom?
cr0x@server:~$ psql -X -c "SELECT a.pid AS blocked_pid, a.usename, a.query AS blocked_query, pg_blocking_pids(a.pid) AS blocking_pids FROM pg_stat_activity a WHERE cardinality(pg_blocking_pids(a.pid)) > 0;"
blocked_pid | usename | blocked_query | blocking_pids
------------+---------+--------------------------------------+--------------
18722 | app | UPDATE inventory SET reserved = ... | {18410}
18740 | app | UPDATE inventory SET reserved = ... | {18410}
Signification : Un PID bloqueur tenant un verrou bloque de nombreuses écritures de checkout — classique ligne chaude ou transaction longue.
Décision : Inspecter la session bloqueuse ; si c’est sûr, l’annuler. Puis corriger le pattern applicatif (réduire la portée du verrou, éviter les appels réseau dans la txn, redesigner l’inventaire).
Task 5 (Postgres): Are checkpoints thrashing?
cr0x@server:~$ psql -X -c "SELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, checkpoint_write_time, checkpoint_sync_time FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend | checkpoint_write_time | checkpoint_sync_time
------------------+-----------------+-------------------+-----------------+-----------------------+----------------------
122 | 480 | 9823412 | 2210344 | 8931201 | 1203310
Signification : Un checkpoints_req élevé par rapport aux timed suggère que la pression WAL force des checkpoints. Un buffers_backend élevé suggère que les backends font leur propre flushing — souvent mauvais pour la latence.
Décision : Après le confinement, tuner max_wal_size, checkpoint_timeout et checkpoint_completion_target, et valider le débit de stockage pour WAL et données.
Task 6 (Postgres): Is autovacuum keeping up on hot tables?
cr0x@server:~$ psql -X -c "SELECT relname, n_dead_tup, n_live_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;"
relname | n_dead_tup | n_live_tup | last_autovacuum
--------------+------------+------------+--------------------------
orders | 18422012 | 22100410 | 2025-12-29 11:02:14+00
payments | 9221011 | 8012200 | 2025-12-29 10:41:02+00
inventory | 5120099 | 210440 | 2025-12-29 07:12:55+00
Signification : Beaucoup de tuples morts par rapport aux tuples vivants suggèrent que le vacuum est à la traîne ou que votre pattern de mise à jour est agressif. Qu’une table inventory ait plus de morts que de vivants est révélateur.
Décision : Tuner les seuils d’autovacuum par table, réduire l’impact des mises à jour, ou passer à des événements append‑only. Envisager le partitionnement pour les tables à fort renouvellement.
Task 7 (Postgres): Are we drowning in connections?
cr0x@server:~$ psql -X -c "SELECT state, count(*) FROM pg_stat_activity GROUP BY 1 ORDER BY 2 DESC;"
state | count
---------------------+-------
idle | 820
active | 160
idle in transaction | 44
Signification : Beaucoup d’idle peut être acceptable, mais idle in transaction est dangereux : cela maintient des snapshots/verrous et bloque le vacuum.
Décision : Imposer des timeouts côté application, killer les sessions « idle in transaction », et utiliser un pooler. Corriger aussi les chemins de code qui laissent des transactions ouvertes.
Task 8 (MariaDB): Are we blocked or deadlocking?
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
...
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 98234123, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 18220, OS thread handle 140201, query id 921103 app updating
UPDATE inventory SET reserved=reserved+1 WHERE sku_id=771 AND warehouse_id=3;
*** (2) TRANSACTION:
TRANSACTION 98234124, ACTIVE 0 sec fetching rows
...
Signification : Vous avez de vrais deadlocks sur des lignes chaudes (inventory). Les deadlocks entraînent des retries ; les retries amplifient la charge.
Décision : Réduire la concurrence pour la SKU chaude (service de file/réservation), assurer un ordre de verrouillage cohérent dans les chemins de code, et ajouter l’idempotence pour réduire les tempêtes de retry.
Task 9 (MariaDB): Is replication lag the real fire?
cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Last_SQL_Error"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 148
Last_SQL_Error:
Signification : Le réplica a 148 secondes de retard. Les lectures depuis lui sont obsolètes ; si l’app l’utilise pour vérifier inventaire/coupons, vous vendrez des fantômes.
Décision : Router les lectures critiques vers le primaire ; dégrader les lectures non critiques vers le cache ; enquêter sur le débit d’application et les transactions longues sur le primaire.
Task 10 (MariaDB): Are we committing safely, or “fast and loose”?
cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_flush_log_at_trx_commit','sync_binlog');"
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_flush_log_at_trx_commit | 2 |
| sync_binlog | 0 |
+------------------------------+-------+
Signification : innodb_flush_log_at_trx_commit=2 et sync_binlog=0 sont des boosters classiques de débit qui réduisent les garanties de durabilité.
Décision : Pour le checkout, se diriger vers 1/1 (ou un compromis choisi volontairement). Si vous ne pouvez pas vous le permettre, vous avez besoin de changements architecturaux (batching, eventing, ou stockage différent) plutôt que de prétendre que la physique négociera.
Task 11 (Postgres): Is WAL generation exploding?
cr0x@server:~$ psql -X -c "SELECT now() as ts, pg_current_wal_lsn();"
ts | pg_current_wal_lsn
-----------------------------+--------------------
2025-12-29 12:05:13.112+00 | 3A/9F2C1B80
Signification : Un échantillon ne suffit pas ; comparez les LSN dans le temps. S’il monte vite, vous générez du WAL rapidement — souvent à cause d’updates, d’un churn d’index, ou d’un job en masse.
Décision : Identifier les tables générant le plus de WAL (via stats/outils d’extension dans votre environnement), mettre en pause les écritures bulk, ou réduire la maintenance des index (supprimer les index inutiles après validation).
Task 12 (Linux): Is the filesystem lying about flush latency?
cr0x@server:~$ sudo blktrace -d /dev/nvme0n1 -w 3 -o - | blkparse -i -
8,0 1 1 0.000000000 21450 Q WS 0 + 8 [postgres]
8,0 1 2 0.000210000 21450 G WS 0 + 8 [postgres]
8,0 1 3 0.004900000 21450 C WS 0 + 8 [0]
Signification : Vous voyez des complétions de sync d’écriture ; si les timestamps de complétion (C) dérivent vers le haut, la latence de flush du stockage nuit aux commits.
Décision : Valider la classe de stockage, le firmware, les politiques RAID/cache. Pour les disques cloud, envisager des IOPS provisionnés ou NVMe local pour WAL/redo. Si vous ne pouvez pas améliorer le stockage, réduire la fréquence de fsync uniquement là où la cohérence l’autorise (pas pour le checkout).
Task 13 (Postgres): Find “idle in transaction” offenders with query text
cr0x@server:~$ psql -X -c "SELECT pid, usename, now()-xact_start AS xact_age, state, left(query,120) AS q FROM pg_stat_activity WHERE state='idle in transaction' ORDER BY xact_age DESC LIMIT 10;"
pid | usename | xact_age | state | q
-------+---------+-------------+----------------------+------------------------------------------------------------
19221 | app | 00:12:14.22 | idle in transaction | SELECT * FROM carts WHERE cart_id=$1 FOR UPDATE;
Signification : Quelqu’un a démarré une transaction, pris un verrou, puis est devenu idle. Cela bloque le vacuum et peut bloquer les écrivains.
Décision : Kill la session pendant l’incident. Ensuite corriger le code applicatif : portée des transactions, timeouts, et éviter les lectures verrouillantes sauf si vraiment nécessaires.
Task 14 (MariaDB): Check InnoDB history list length and purge health
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | egrep -i "History list length|Log sequence number|Last checkpoint"
History list length 923441
Log sequence number 1123341234412
Last checkpoint at 1123340031129
Signification : Une grande history list length peut indiquer un retard de purge, souvent dû à des transactions longues. Cela augmente la rétention undo et peut dégrader les performances.
Décision : Trouver et éliminer les transactions longues, surtout sur les réplicas ou les sessions d’analyse. Envisager de séparer OLTP et reporting.
Task 15 (Both): Are we accidentally doing huge transactions?
cr0x@server:~$ sudo journalctl -u checkout-worker -n 50 --no-pager
Dec 29 12:06:01 app-1 checkout-worker[3112]: bulk_reservation job started: cart_id=... items=420
Dec 29 12:06:02 app-1 checkout-worker[3112]: db transaction open for cart_id=...
Dec 29 12:06:55 app-1 checkout-worker[3112]: db transaction commit cart_id=... duration_ms=52981
Signification : Une transaction de 52 secondes n’est pas « un peu lente ». C’est une usine à verrous/undo/WAL. Cela fait du mal à Postgres comme à MariaDB.
Décision : Fractionner le travail, déplacer les appels externes hors des transactions, et concevoir des étapes idempotentes pour pouvoir commit fréquemment.
Trois mini‑histoires d’entreprise depuis les tranchées du chemin d’écriture
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
Ils exploitaient un marketplace e‑commerce avec des pics saisonniers. L’équipe a migré le trafic de lecture vers les réplicas et a dit au business que le primaire « ne gérerait que les écritures ». Ça paraissait propre : primaire pour écritures, réplicas pour lectures, tout le monde content.
L’hypothèse erronée était subtile : ils supposaient que « les lectures sont sûres partout ». Mais leur flux de checkout lisait la disponibilité d’inventaire et la validité des coupons depuis le réplica pour réduire la charge. Avec un délai de réplication normal — des secondes — ça fonctionnait la plupart du temps. Lors d’une vente flash, le retard de réplication a grimpé. Le réplica affichait encore la disponibilité. Les clients ont continué à valider. Le primaire rejetait correctement certaines mises à jour, mais l’application avait déjà promis la disponibilité et appliqué des réductions basées sur des lectures obsolètes.
Support a été submergé par des « pourquoi j’ai été débité si vous disiez que c’était en stock ? » L’ingénierie regardait des graphes. Le primaire n’était pas en panne. Le CPU allait bien. Le réplica était « vert » sauf pour une métrique que personne ne mettait en alarme : le lag.
La correction n’a pas été un tuning héroïque. Ils ont changé la politique : toute lecture qui influence une décision d’écriture (inventaire, rédemption de coupon, état de paiement) doit être « read‑your‑writes » cohérente. En pratique : lire depuis le primaire ou depuis un store avec cohérence de quorum, et mettre en cache seulement après commit. Ils ont aussi ajouté une coupure stricte : si le lag du réplica dépasse un seuil, l’app arrête d’utiliser les réplicas pour ces endpoints.
La leçon postmortem a été claire : les lectures obsolètes sont un bug de cohérence, pas une fonctionnalité de performance. Les réplicas servent à l’échelle, pas à faire comme si le temps était optionnel.
Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux
Un détaillant sur MariaDB avait un vilain p99 au checkout. Quelqu’un a trouvé les suspects habituels : trop de fsync, latence de commit, et un stockage qui tenait mais sans enthousiasme. Ils ont « optimisé » en relâchant les réglages de durabilité. Les commits sont devenus plus rapides. Tout le monde s’est félicité et est reparti livrer des fonctionnalités.
Deux mois plus tard, un panic du kernel a emporté le primaire. Le basculement s’est produit. Le site est resté en ligne. Puis les bizarreries ont commencé : quelques commandes figuraient dans les logs applicatifs mais pas dans la base ; certains paiements ont été capturés mais la machine à états de commande était incomplète ; le support a dû réconcilier manuellement l’état via les rapports du prestataire de paiement.
Rien de tel que de découvrir que vous avez construit un petit système comptable où « eventual consistency » signifie « quelqu’un pleure finalement ».
Ils ont remis la durabilité pour le schéma checkout et déplacé le travail de performance vers des couches plus sûres : moins d’index sur les tables chaudes, transactions plus petites, et mise en file explicite des réservations d’inventaire. Le vrai gain est venu de la réduction de la contention et de l’amplification d’écriture — pas de prétendre que les disques ne tombent jamais en panne.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise d’abonnement tournait sur Postgres. Ils n’étaient pas fancy. Ils étaient disciplinés. Chaque trimestre, ils faisaient un « fire drill » du chemin d’écriture : simuler un pic, vérifier les alertes et répéter la procédure. Ils avaient aussi une règle terre‑à‑terre : pas de DDL pendant les pics, et chaque changement de schéma doit être planifié avec un plan réversible.
Un après‑midi, une campagne marketing a très bien marché. Les écritures ont monté en flèche. La latence a augmenté mais est restée bornée. Puis un réplica a commencé à prendre beaucoup de retard. L’ingénieur d’astreinte n’a pas débattu la philosophie ; il a suivi la checklist : router les lectures critiques vers le primaire, désactiver les fonctionnalités d’écriture non essentielles (événements wishlist, historique de navigation), et surveiller WAL/checkpoints.
Ils ont trouvé la vraie cause : un job en arrière‑plan avait commencé à mettre à jour une large fraction d’une table chaude, créant une dette de vacuum. Parce qu’ils suivaient les tuples morts et le comportement d’autovacuum, c’était évident. Ils ont mis le job en pause, laissé autovacuum récupérer, puis réintroduit le job en le batchant et en lui donnant une priorité plus basse.
L’incident n’a jamais atteint les clients. Pas parce que Postgres est magique. Parce que quelqu’un a fait le travail ennuyeux : visibilité, exercices et changements contrôlés.
Erreurs courantes : symptômes → cause racine → correction
1) Symptom: “Database is slow” but CPU is low
Cause racine : files de verrous ou attentes IO. La base ne calcule pas ; elle attend.
Correction : Dans Postgres, inspecter les wait events et les PIDs bloqueurs dans pg_stat_activity ; tuer le bloqueur si nécessaire. Dans MariaDB, vérifier le status InnoDB pour les waits/deadlocks. Puis raccourcir les transactions et réduire les mises à jour de lignes chaudes.
2) Symptom: p99 spikes every few minutes
Cause racine : comportement checkpoint/flush périodique (checkpoints Postgres, flush storms MariaDB), ou jobs background périodiques.
Correction : Échelonner l’IO avec le tuning des checkpoints (Postgres) et séparer WAL/données quand possible. Pour MariaDB, vérifier le dimensionnement des redo logs et les réglages de durabilité, et auditer les jobs background pour les mises à jour en rafale.
3) Symptom: replicas are “up” but customers see inconsistent state
Cause racine : retard de réplication et l’app lit les réplicas pour la logique critique.
Correction : Faire respecter le read‑your‑writes pour le checkout et l’inventaire. Ajouter un routage conscient du lag. Ajouter l’idempotence pour que les retries n’écrivent pas en double.
4) Symptom: deadlocks spike during promotions
Cause racine : lignes chaudes + ordre de verrouillage incohérent + retries agressifs.
Correction : Standardiser l’ordre de verrouillage, réduire la portée des transactions, déplacer la contention dans une file/allocateur, et implémenter un backoff exponentiel avec jitter sur les retries.
5) Symptom: Postgres disk usage climbs, queries get slower week over week
Cause racine : bloat dû aux mises à jour ; autovacuum à la traîne ; index qui grossissent.
Correction : Tuner autovacuum par table, réduire le churn des updates, envisager le partitionnement et ajuster le fillfactor. Si le bloat est sévère, planifier une réécriture contrôlée (vacuum full / reindex / rebuild de table) avec downtime ou stratégie en ligne.
6) Symptom: MariaDB commits slow, but reads are fine
Cause racine : pression redo/binlog, latence fsync, ou group commit binlog qui ne suit pas.
Correction : Valider les performances fsync du stockage. S’assurer que redo/binlog ne sont pas partagés avec des voisins bruyants. Garder les transactions petites. Si la réplication est activée, vérifier que les réglages binlog correspondent aux exigences de durabilité.
7) Symptom: Postgres autovacuum runs “all the time” and still can’t catch up
Cause racine : amplification d’écriture due à des index excessifs et des mises à jour fréquentes ; too‑small maintenance_work_mem ; transactions longues empêchant le nettoyage.
Correction : Supprimer les index inutilisés, fractionner les jobs de mise à jour, corriger les transactions longues, et tuner le nombre de workers/costs d’autovacuum. Vérifier aussi la présence d’idle in transaction.
Blague #2 : si votre correction est « augmenter max_connections », vous résolvez un embouteillage en ajoutant plus de voitures.
Checklists / plan pas à pas
Étape par étape : rendre les écritures e‑commerce ennuyeuses (l’objectif)
- Définir les frontières de cohérence. Identifier les lectures qui influencent les décisions d’achat (inventaire, rédemption de coupon, statut de paiement). Forcer leur cohérence avec les écritures.
- Raccourcir les transactions. Pas d’appels réseau à l’intérieur des transactions DB. Si coordination nécessaire, utiliser des clés d’idempotence et une machine à états avec commits fréquents.
- Supprimer la vanité des index. Pour les tables à fort trafic d’écriture, ne garder que les index qui rapportent. Valider avec les stats de requêtes, pas des opinions.
- Partitionner les monstres append‑only. Orders/events/audit devraient être partitionnés par temps ou par tenant s’ils grossissent vite et sont interrogés par plages récentes.
- Rendre les lignes chaudes rares. Remplacer les compteurs globaux par des compteurs bucketés. Remplacer « mise à jour d’une ligne d’inventaire par article » par une allocation de réservation ou de l’event sourcing + projection.
- Backpressure plutôt que backlog. Limiter le débit des checkouts en périphérie quand la DB est stressée. Un « réessayez plus tard » contrôlé vaut mieux qu’une panne en cascade.
- Instrumenter le chemin d’écriture. Mesurer latence de commit, temps d’attente de verrous, retard de réplication et profondeur de queue côté application.
- Concevoir les retries comme partie du système. Les retries doivent être idempotents et avoir du jitter, sinon vous créerez votre propre panne.
- Séparer OLTP et analytics. Les requêtes de reporting et les jobs « exporter tout » ne doivent pas partager le même budget lock/IO que le checkout.
- Exercer la bascule et la réponse au lag. Vous ne voulez pas inventer votre réaction au lag pendant une promotion.
Ce que je ferais aujourd’hui en choisissant pour un cœur e‑commerce à forte écriture
- Si vous avez une forte maturité ops et souhaitez une introspection poussée et des sémantiques transactionnelles robustes : PostgreSQL, avec une attention sérieuse au WAL/checkpoints et au vacuum dès le premier jour.
- Si votre organisation est déjà native MySQL/MariaDB et que vous pouvez imposer la discipline de transaction et l’hygiène de réplication : MariaDB, mais traitez le retard de réplication comme un SLO de première classe et n’« optimisez » pas la durabilité pour les écritures qui déplacent de l’argent.
FAQ
1) PostgreSQL est‑il toujours meilleur pour les charges d’écritures lourdes ?
Non. Postgres est souvent plus facile à raisonner quand vous l’instrumentez bien, mais il peut absolument s’effondrer à cause de limites WAL/IO, de dette de vacuum, ou de surcharge de connexions. La réussite sur des écritures lourdes dépend surtout de la forme des transactions et de la capacité IO.
2) MariaDB est‑il toujours plus rapide pour les inserts ?
Parfois, surtout avec des schémas simples et les bons compromis de durabilité. Mais les écritures e‑commerce ne sont pas que des inserts — ce sont des inserts plus des index, des contraintes, de la réplication et des mises à jour de lignes chaudes. « Inserts plus rapides » n’est pas synonyme de « checkout stable sous contention ».
3) Quelle est la raison n°1 pour laquelle les écritures checkout Postgres ralentissent ?
En production : pression IO autour du WAL/checkpoints combinée au bloat ou à un autovacuum à la traîne. Les chaînes de verrous sont un très proche second, généralement causées par des transactions longues ou des lectures verrouillantes mal pensées.
4) Quelle est la raison n°1 pour laquelle les écritures checkout MariaDB ralentissent ?
Pression commit/flush plus contention. Ensuite le retard de réplication transforme l’architecture en usine à bugs de cohérence si vous lisez des réplicas pour des décisions.
5) Dois‑je utiliser des clés primaires UUID pour les commandes ?
Vous pouvez, mais comprenez le coût en écriture. Les UUID aléatoires peuvent augmenter les splits de pages et réduire la localité (surtout douloureux avec un stockage clusterisé comme InnoDB). Si vous avez besoin d’IDs globalement uniques, envisagez des variantes UUID ordonnées dans le temps ou des clés substituts séparées quand approprié.
6) Puis‑je « résoudre » les lignes d’inventaire chaudes avec SELECT FOR UPDATE ?
Vous pouvez sérialiser correctement, mais vous pouvez aussi sérialiser le débit. Le verrouillage est cohérence, pas performance. La solution évolutive est de réduire la contention : allouer l’inventaire par lots, sharder par entrepôt/bucket, ou utiliser un service de réservation qui contrôle la concurrence.
7) Ai‑je besoin d’un pool de connexions ?
Pour Postgres : presque toujours, oui, à l’échelle. Pour MariaDB : c’est aussi bénéfique, mais le seuil de douleur est souvent différent. Dans tous les cas, la croissance incontrôlée des connexions est la manière dont la latence devient un roman policier.
8) La réplication asynchrone est‑elle acceptable pour l’e‑commerce ?
Oui, si vous la traitez comme asynchrone. Cela signifie : ne pas lire depuis les réplicas pour des décisions qui touchent l’argent ou l’inventaire à moins d’accepter la staleness et d’avoir des contrôles compensatoires. Beaucoup d’équipes disent qu’elles l’acceptent ; leurs queues de support ne sont pas d’accord.
9) Quelle est l’amélioration de performance la plus sûre pour un checkout à forte écriture ?
Réduire l’amplification d’écriture : moins d’index sur les tables chaudes, transactions plus petites, et déplacer les écritures non essentielles hors du chemin critique (queues d’événements). Les améliorations de stockage aident aussi, mais ne corrigent pas de mauvais patterns d’écriture.
10) Quelle base offre les meilleurs outils pour diagnostiquer les goulets d’écriture ?
Postgres dispose généralement d’une introspection intégrée plus riche autour des waits, WAL et comportement du vacuum. MariaDB a aussi de bons outils, mais vous vous appuierez souvent davantage sur des snapshots du status InnoDB et des métriques de réplication. De toute façon, les outils comptent seulement si vous alarmez sur les bonnes choses.
Prochaines étapes actionnables
Si vous exécutez aujourd’hui un e‑commerce à forte écriture et voulez moins de surprises, faites ceci dans l’ordre :
- Cartographier vos frontières de transaction de checkout et supprimer tout appel externe à l’intérieur. Rendre le workflow idempotent.
- Identifier les lignes chaudes (inventaire, coupons, compteurs) et les redesigner pour éviter la contention sur une seule ligne.
- Auditer les index sur les 5 tables les plus actives en écriture. Supprimer ce qui n’est pas nécessaire pour les lectures critiques.
- Instrumenter les goulets : events/attentes WAL/checkpoints/vacuum pour Postgres ; deadlocks/redo/binlog/retard de réplication pour MariaDB.
- Implémenter un routage conscient du lag pour que les réplicas ne puissent jamais silencieusement corrompre la logique business.
- Lancer un test de charge réaliste : en rafales, biaisé sur quelques SKU chauds, avec retries et timeouts. Votre base ne se soucie pas de votre QPS moyen.
Après cela, le choix MariaDB vs PostgreSQL devient plus clair. Pas parce que l’un est « meilleur », mais parce que vous saurez enfin quel type de douleur vous achetez — et comment l’empêcher d’atteindre le checkout.