Vous ne remarquez pas le CPU tant qu’il ne se met pas à hurler. Le graphe a l’air correct, puis un e-mail promo atterrit, le QPS triple,
et vos hôtes de base de données se transforment en petits radiateurs. Les latences montent en flèche. Les timeouts s’empilent. Quelqu’un pose la pire
question en exploitation : « C’est la base de données ou l’application ? »
Ce n’est pas un article de concours pour décider quel moteur est « plus rapide ». Sous charge maximale, MariaDB et PostgreSQL peuvent
tous deux cramer des cœurs rapidement — simplement de manières différentes et prédictibles. L’objectif est d’identifier le mode de défaillance,
de le prouver avec des commandes, et d’appliquer une correction qui résiste au prochain pic sans transformer la rotation on-call en passe-temps.
Ce que « brûler les cœurs » signifie réellement sous charge maximale
Un CPU élevé n’est pas un seul problème ; c’est une réunion de famille de problèmes. En pratique, les « pics CPU » dans les bases de données proviennent de :
(1) faire du vrai travail utile (exécution de requêtes), (2) faire du travail que vous n’aviez pas l’intention de faire (mauvais plans, tris excessifs,
re-parsages répétitifs), ou (3) ne rien faire de productif tout en tournant en rond (contention, attentes de verrou avec boucles actives,
commutations de contexte excessives).
Sous charge maximale, la même requête peut passer de « correcte » à « mangeuse de cœurs » parce que la forme du système change :
les ensembles chauds grandissent, les caches ratent, des files de verrous se forment, ou un plan bascule d’un accès par index à un scan
à cause de statistiques obsolètes ou d’un paramètre lié différent. Votre base de données n’est pas devenue stupide. Elle est devenue honnête.
Quand les gens demandent « qui brûle les cœurs plus vite », ils veulent généralement dire : « Lequel atteint la saturation CPU plus tôt et s’effondre
plus fortement quand la concurrence augmente ? » La réponse inconfortable : cela dépend de la manière dont vous l’alimentez. MariaDB (InnoDB) souffre souvent
quand vous ajoutez trop d’écrivains concurrents ou trop de connexions sans pooling ; PostgreSQL souffre souvent quand la dette d’autovacuum s’accumule
ou quand une régression de plan transforme une requête étroite en un scan large qui multiplie le travail sur plusieurs cœurs. Les deux peuvent être réglés.
Les deux peuvent être mal réglés jusqu’à un incendie.
Un modèle mental utile : les problèmes de CPU en charge de pointe concernent rarement le débit brut. Ils concernent la surcharge de coordination.
Votre appli demande du travail ; la base de données coordonne l’accès à l’état partagé. C’est dans cette coordination que vous perdez des cœurs.
Une citation utile à garder sur un post-it, car elle décrit la plupart des pics CPU en production :
la latence est un symptôme, pas une cause racine
(idée paraphrasée souvent attribuée à la méthodologie de performance de Brendan Gregg).
Votre travail est de trouver la cause racine, pas de discuter avec le graphe.
Comment MariaDB dépense du CPU quand il panique
MariaDB en production est généralement MariaDB + InnoDB (ou XtraDB historiquement). InnoDB est performant sur beaucoup de points :
récupération après crash, verrouillage au niveau des lignes, et gestion des patterns OLTP courants. Mais la charge de pointe a le don de trouver les coutures.
Schémas courants de pics CPU sous MariaDB
1) Surcharge de concurrence : threads, mutex et « trop de clients »
MariaDB utilise un modèle thread-par-connexion. Quand vous augmentez les connexions linéairement avec le trafic, vous n’ajoutez pas seulement
du travail de requête — vous ajoutez du travail d’ordonnancement. Sous forte concurrence, le CPU est consommé par les commutations de contexte et
la coordination des verrous à l’intérieur du moteur. Si vous observez un CPU élevé avec un QPS modéré, suspectez le thrash : beaucoup de threads
exécutables, peu de progrès utile.
Si vous exécutez sans thread pool (ou avec une mauvaise configuration), MariaDB peut sembler « utiliser du CPU » tout en accomplissant de moins en moins.
Vous le verrez dans la file exécutable au niveau OS et dans les compteurs d’état propres à la base.
2) Contention InnoDB : lignes chaudes, index chauds et douleur des verrous internes
Sous une forte charge d’écriture, des points chauds de contention apparaissent : une clé primaire auto-incrémentée sous inserts intensifs, une seule ligne « latest »
frappée par des mises à jour, ou des index secondaires à maintenir pour chaque écriture. C’est du vrai travail plus de la coordination. Si l’appli crée
une seule ligne chaude ou une feuille d’index chaude, vous pouvez saturer le CPU alors que le débit plafonne.
InnoDB s’est amélioré au fil des années avec un verrouillage plus fin, mais il a encore des endroits où la contention apparaît comme des pics CPU et des blocages.
Beaucoup de ces problèmes sont façonnés par la charge de travail, pas par la version.
3) Exécution des requêtes : mauvais plans et tris coûteux
MariaDB peut effectuer un excellent travail rapidement, mais il peut aussi commettre des erreurs classiques sous pression :
trier de grands ensembles intermédiaires, scanner à cause d’index manquants, ou répéter le même travail parce que l’application
envoie des requêtes textuellement différentes qui contournent le cache de requêtes (et vous ne devriez pas compter sur le query cache de toute façon).
4) Réplication et surcharge du binlog
La charge maximale coïncide souvent avec un lag de réplication. Si votre primaire est saturé CPU, il ne peut pas vider les binlogs, ne peut pas alimenter
les réplicas et ne peut pas commettre rapidement. Les améliorations de group commit aident, mais vous payez toujours le CPU pour le logging row-based
et pour la machinerie transactionnelle autour. « C’est juste écrire un log » sont des derniers mots célèbres.
Mode d’échec signature de MariaDB sous charge maximale
Quand MariaDB s’effondre sous CPU en charge maximale, cela ressemble souvent à « tout le monde est occupé, personne n’est content » :
des tonnes de connexions, beaucoup de threads, un CPU système élevé, et une courbe de débit qui cesse d’évoluer quand vous ajoutez des cœurs.
Votre correction consiste généralement à réduire la concurrence et à éliminer les points chauds de contention, pas à ajouter des cœurs en priant.
Première blague (permise, et oui, pertinente) : une base de données sans pool de connexions est comme un bar ouvert sans barmans — tout le monde est
techniquement servi, mais rien de bon n’arrive rapidement.
Comment PostgreSQL dépense du CPU quand il panique
L’architecture de PostgreSQL est réputée conservatrice et profondément travaillée : process-par-connexion, shared buffers,
MVCC, un planificateur robuste, et des workers en arrière-plan pour l’entretien. Sous charge maximale, il échoue souvent d’une manière plus
« logique » que MariaDB : il va dépenser du CPU à faire exactement ce qu’il pense que vous avez demandé, ce qui est parfois le vrai problème.
Schémas courants de pics CPU sous PostgreSQL
1) Régression de plan : même requête, plan différent, soudainement coûteux
Le planner de PostgreSQL est puissant et parfois trop confiant. Si les statistiques dérivent, la distribution des données change, ou
les paramètres liés varient largement, un plan peut basculer d’un index nested loop à un hash join ou à un sequential scan. Sous pic,
ce basculement peut multiplier le travail par des ordres de grandeur, et le CPU suit.
PostgreSQL vous donne d’excellents outils pour voir le plan et le temps d’exécution. Le piège est de ne pas regarder avant l’incident, puis
de découvrir que vous n’avez jamais capturé de baselines.
2) Dette d’autovacuum : vous ne payez pas, elle continue à charger des intérêts
MVCC signifie que des tuples morts s’accumulent jusqu’à ce que vacuum les nettoie. Si autovacuum ne suit pas — à cause de limites IO,
de mauvais réglages, ou simplement parce que vous avez subi un pic d’écriture — alors les scans touchent plus de pages, les index s’enflent,
et le CPU augmente parce que chaque requête traverse plus de déchets. Vous verrez un CPU plus élevé même pour des « lectures », car elles font
désormais plus de travail par ligne renvoyée.
3) Surcharge de concurrence : beaucoup de connexions signifie beaucoup de processus
Le modèle process-par-connexion de PostgreSQL est stable et facile à déboguer, mais chaque backend est réel. Trop de connexions signifie
plus de commutations de contexte, une empreinte mémoire plus grande, et du CPU perdu pour la coordination, avant même d’exécuter du SQL.
Ajoutez un trafic de pointe et vous pouvez vous heurter à un mur où le CPU brûle pour la planification des processus et la gestion des verrous.
4) Tri, hash et pression mémoire (les mines de work_mem)
PostgreSQL peut brûler du CPU en effectuant des tris et des opérations de hash. Si work_mem est trop bas, les opérations déversent sur disque
et vous obtenez un mélange d’attente IO et de surcharge CPU. Si work_mem est trop élevé et que vous avez beaucoup de requêtes concurrentes,
vous obtenez une pression mémoire, puis le noyau commence ses propres « optimisations », et l’utilisation CPU devient une installation artistique chaotique.
Mode d’échec signature de PostgreSQL sous charge maximale
Quand PostgreSQL s’effondre sous CPU en pic, il ressemble souvent à : quelques formes de requêtes dominent, elles s’exécutent plus longtemps que d’habitude,
autovacuum prend du retard, la bloat augmente, et alors tout ralentit encore. C’est une boucle de rétroaction : des requêtes plus longues retiennent
les ressources plus longtemps, ce qui augmente la contention et la dette de vacuum, ce qui rend les requêtes plus longues. Votre correction consiste généralement
à corriger les pires requêtes et à maintenir un vacuum sain, pas à « bidouiller des réglages au hasard ».
Mode d’emploi de diagnostic rapide (premiers/deuxièmes/troisièmes contrôles)
Premier : le CPU est-il du « vrai travail » ou du « thrash » ?
- File d’exécution OS élevée (load average bien au-dessus du nombre de CPU, beaucoup de tâches exécutables) : probablement thrash ou concurrence excessive.
- Un ou quelques threads/processus chauds : probablement des requêtes spécifiques, vacuum, réplication, ou un verrou hotspot.
- CPU système élevé : commutations de contexte, surcharge noyau (réseau, fs), spinlocks.
- CPU utilisateur élevé : exécution de requêtes, tris, hash, évaluation d’expressions.
Deuxième : identifier les formes de requêtes principales et les causes d’attente
- MariaDB : regardez processlist, InnoDB status, métriques handler/read, et si vous êtes lié par l’ordonnancement des threads.
- PostgreSQL : regardez
pg_stat_activity, le top SQL danspg_stat_statements, et les indicateurs de vacuum/bloat.
Troisième : décider si le goulot est uniquement CPU ou « CPU comme symptôme »
- Si l’IO est saturé, le CPU peut grimper à cause des retries, du churn de buffers, et de la surcharge noyau. Réparez l’IO d’abord.
- Si les verrous dominent, les pics CPU ne sont que la partie visible. Corrigez l’ordre des verrous, les lignes chaudes et la durée des transactions.
- Si une régression de plan est survenue, rien d’autre n’a d’importance tant que le plan n’est pas corrigé.
Tâches pratiques : commandes, sorties et décisions (12+)
Ce sont les tâches que j’exécute réellement pendant un incident ou juste après. Chacune contient : commande, sortie d’exemple, ce que cela signifie,
et la décision que vous prenez. Elles sont volontairement ennuyeuses. L’ennui, c’est réduire les pages.
Task 1: Confirm whether you’re CPU-saturated or just noisy
cr0x@server:~$ uptime
14:22:01 up 31 days, 3:07, 2 users, load average: 42.18, 39.77, 28.54
Ce que cela signifie : Une load average d’environ 42 sur une machine 16 cœurs signifie que vous avez bien plus de tâches exécutrices ou non interruptibles que de CPU.
Décision : Cessez de deviner. Inspectez immédiatement la file d’exécution et les processus top. Si les connexions sont énormes, commencez par limiter la concurrence (pooling, max connections) pendant que vous diagnostiquez.
Task 2: See CPU breakdown (user vs system) and run queue
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
32 1 0 812344 92324 6231880 0 0 120 410 9800 42000 78 18 2 2 0
29 0 0 805112 92328 6239024 0 0 40 210 9600 39000 81 16 1 2 0
Ce que cela signifie : r proche de 30+ indique une pression sur la file exécutable ; un cs élevé signifie de fortes commutations de contexte ; un sy élevé suggère une surcharge noyau.
Décision : Si cs est énorme et que la DB a des milliers de connexions, priorisez le pooling et les limites. Si wa est élevé, vérifiez le stockage/IO.
Task 3: Identify the top CPU consumers and whether it’s many or few
cr0x@server:~$ ps -eo pid,comm,%cpu,%mem,stat --sort=-%cpu | head
2314 mariadbd 690.3 18.2 Sl
4481 postgres 189.4 1.7 R
4520 postgres 176.0 1.6 R
911 node 42.1 0.8 R
Ce que cela signifie : Un gros processus mariadbd brûlant de nombreux cœurs suggère une contention interne ou une forte charge de requêtes dans un seul processus ; de nombreux backends postgres brûlant du CPU suggèrent quelques requêtes coûteuses s’exécutant concurremment.
Décision : Si vous voyez un processus BD dominant : concentrez-vous sur la contention du moteur et l’ordonnancement des threads (MariaDB) ou sur un worker spécifique (Postgres). Si beaucoup de processus backend dominent : trouvez les formes SQL principales.
Task 4: Measure context switching and migrations (thrash indicator)
cr0x@server:~$ pidstat -w -p $(pgrep -o mariadbd) 1 3
Linux 6.1.0 (server) 12/30/2025 _x86_64_ (16 CPU)
01:22:10 PID cswch/s nvcswch/s Command
01:22:11 2314 1200.0 38000.0 mariadbd
01:22:12 2314 1105.0 40120.0 mariadbd
Ce que cela signifie : Des commutations de contexte involontaires massives (nvcswch/s) signifient généralement que l’ordonnanceur jongle avec des threads/processus exécutables. Cela consume du CPU sans accomplir un travail proportionnel.
Décision : Réduisez la concurrence : appliquez du pooling, baissez max connections, activez/configurez le thread pool (MariaDB), ajustez la taille du pool (Postgres via pgbouncer), et cherchez la contention de verrous.
Task 5 (MariaDB): Who is running what right now?
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G" | head -n 30
*************************** 1. row ***************************
Id: 81423
User: app
Host: 10.2.4.18:51214
db: prod
Command: Query
Time: 12
State: Sending data
Info: SELECT ... FROM orders WHERE customer_id=? ORDER BY created_at DESC LIMIT 50
*************************** 2. row ***************************
Id: 81451
User: app
Host: 10.2.4.19:52108
db: prod
Command: Query
Time: 12
State: Sorting result
Info: SELECT ... FROM orders WHERE status='open' ORDER BY priority DESC
Ce que cela signifie : Des états comme Sorting result et un long Time sous charge indiquent généralement des index manquants ou inefficaces ; Sending data peut signifier de grands jeux de résultats ou des lectures clients lentes.
Décision : Capturez les 2–3 requêtes principales. Exécutez EXPLAIN, validez les index, et envisagez des limites/timeouts de requête à court terme pour arrêter l’hémorragie.
Task 6 (MariaDB): Check InnoDB contention and purge pressure
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
=====================================
2025-12-30 14:24:51 0x7f1f6c0a9700 INNODB MONITOR OUTPUT
=====================================
Mutex spin waits 1203328, rounds 3821140, OS waits 44211
RW-shared spins 922110, rounds 1102100, OS waits 21011
History list length 884321
...
Ce que cela signifie : Des spin waits et OS waits élevés peuvent indiquer de la contention ; une très grande history list length suggère que le purge est en retard (transactions longues ou progression de purge insuffisante), ce qui peut gonfler le travail pour les lectures et les index.
Décision : Trouvez les transactions longues, réduisez la durée des transactions, et évaluez si la charge d’écriture ou les lignes chaudes causent la contention. Si la history list explose, chassez la session qui détient un ancien snapshot.
Task 7 (MariaDB): Verify thread/connection pressure quickly
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads%'; SHOW VARIABLES LIKE 'max_connections';"
+-------------------+--------+
| Variable_name | Value |
+-------------------+--------+
| Threads_connected | 1850 |
| Threads_running | 280 |
| Threads_created | 992134 |
+-------------------+--------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 4000 |
+-----------------+-------+
Ce que cela signifie : Des milliers de threads connectés et un grand Threads_created coïncident souvent avec du CPU dépensé dans la gestion des connexions et l’ordonnancement.
Décision : Limitez les connexions à ce que l’hôte peut réellement ordonnancer. Implémentez du pooling. Si vous devez accepter de nombreux clients, utilisez un thread pool et gardez Threads_running proche du nombre de cœurs, pas 10× plus.
Task 8 (PostgreSQL): See active queries and their wait events
cr0x@server:~$ psql -XAt -c "SELECT pid, state, wait_event_type, wait_event, now()-query_start AS age, left(query,120) FROM pg_stat_activity WHERE state<>'idle' ORDER BY age DESC LIMIT 10;"
4481|active|||00:00:17.182913|SELECT ... FROM orders WHERE customer_id=$1 ORDER BY created_at DESC LIMIT 50
4520|active|LWLock|buffer_mapping|00:00:12.504991|SELECT ... FROM events WHERE tenant_id=$1 AND ts>$2 ORDER BY ts DESC LIMIT 200
4602|active|Lock|transactionid|00:00:09.991221|UPDATE accounts SET balance=balance-$1 WHERE id=$2
Ce que cela signifie : Pas d’événement d’attente et état active signifie travail pur CPU. Les LWLock waits peuvent indiquer une contention interne (buffer mapping, WAL, etc.). Les lock waits signifient que vous êtes bloqué derrière la concurrence, pas par le calcul.
Décision : Si la plupart sont CPU-actives : trouvez le top SQL et les plans. Si les lock waits dominent : réduisez la durée des transactions et corrigez l’ordre des verrous/les lignes chaudes. Si des hotspots LWLock apparaissent : examinez le churn des shared buffers, la forte concurrence, ou des patterns frappant des structures partagées.
Task 9 (PostgreSQL): Identify top CPU-expensive queries via pg_stat_statements
cr0x@server:~$ psql -X -c "SELECT calls, round(total_exec_time::numeric,1) AS total_ms, round(mean_exec_time::numeric,2) AS mean_ms, rows, left(query,120) FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 8;"
calls | total_ms | mean_ms | rows | left
-------+----------+---------+--------+------------------------------------------------------------
93210 | 982344.6 | 10.54 | 186420 | SELECT ... FROM orders WHERE customer_id=$1 ORDER BY created_at DESC LIMIT 50
11234 | 621990.2 | 55.36 | 11234 | SELECT ... FROM events WHERE tenant_id=$1 AND ts>$2 ORDER BY ts DESC LIMIT 200
2011 | 318221.0 | 158.25 | 2011 | SELECT ... FROM line_items JOIN products ON ...
Ce que cela signifie : Le temps total met en lumière ce qui consomme la machine, pas ce qui est « le plus lent à la fois ». En période de pic, les éléments en tête par temps total sont vos destructeurs de budget CPU.
Décision : Prenez les deux requêtes principales, exécutez EXPLAIN (ANALYZE, BUFFERS) hors pointe ou sur un replica, et corrigez les plans/index en priorité.
Task 10 (PostgreSQL): Check autovacuum and bloat pressure signals
cr0x@server:~$ psql -X -c "SELECT relname, n_live_tup, n_dead_tup, last_autovacuum, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;"
relname | n_live_tup | n_dead_tup | last_autovacuum | last_autoanalyze
-----------+------------+------------+----------------------------+----------------------------
orders | 81203421 | 22190311 | 2025-12-29 03:12:44+00 | 2025-12-29 03:22:01+00
events | 190233442 | 80311220 | | 2025-12-28 22:10:08+00
Ce que cela signifie : Une table avec un énorme nombre de dead tuples et aucun autovacuum récent est un piège CPU : les scans et les parcours d’index font plus de travail par ligne utile.
Décision : Améliorez le débit d’autovacuum (limits de coût, workers, IO), réduisez les transactions longues, et envisagez des vacuum ciblés pendant des fenêtres contrôlées. Si c’est extrême, planifiez une maintenance (VACUUM FULL ou reindex) avec précaution.
Task 11: Confirm whether IO is the real bottleneck (CPU as symptom)
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rKB/s wKB/s await svctm %util
nvme0n1 820.0 610.0 98240.0 110820.0 7.10 0.32 92.40
Ce que cela signifie : Un %util élevé et un await en hausse indiquent une saturation IO. Les pics CPU peuvent accompagner ceci à cause du churn de buffers et de l’augmentation du travail due aux cache misses.
Décision : Si le stockage est saturé, arrêtez les « réglages CPU ». Réduisez l’IO : ajoutez des index pour éviter les scans, augmentez le hit rate du cache, corrigez la bloat, ou scalez le stockage/les réplicas.
Task 12: Validate memory pressure and swapping (silent CPU killer)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 125Gi 96Gi 1.2Gi 2.1Gi 27Gi 18Gi
Swap: 16Gi 3.8Gi 12Gi
Ce que cela signifie : L’utilisation de swap pendant un pic peut transformer les graphiques CPU en mensonges : le CPU est « occupé » mais il attend des pages mémoire tirées du swap.
Décision : Réduisez l’empreinte mémoire (moins de connexions, dimensionnez correctement les buffers), stoppez des work_mem hors de contrôle (Postgres), et corrigez les patterns de requêtes provoquant de grands tris/hashes.
Task 13: Capture kernel-level hotspots with perf (when you need proof)
cr0x@server:~$ sudo perf top -p $(pgrep -o mariadbd)
Samples: 915 of event 'cycles', 4000 Hz, Event count (approx.): 145392102
18.40% mariadbd [.] btr_cur_search_to_nth_level
12.15% mariadbd [.] row_search_mvcc
7.92% libpthread-2.31.so [.] pthread_mutex_lock
6.71% mariadbd [.] lock_rec_lock
Ce que cela signifie : Voir des verrous mutex élevés dans la pile suggère de la contention ; voir une recherche B-tree dominer suggère des traversées d’index en lecture lourde (possiblement dues à des cache misses ou des patterns d’accès inefficaces).
Décision : Si les mutex dominent : réduisez la concurrence et les points chauds. Si la recherche B-tree domine : améliorez les index, réduisez les recherches aléatoires, et augmentez le hit rate du cache (buffer pool/shared buffers et working set).
Task 14 (PostgreSQL): Prove a plan problem with EXPLAIN (ANALYZE, BUFFERS)
cr0x@server:~$ psql -X -c "EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id=123 ORDER BY created_at DESC LIMIT 50;"
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Limit (cost=0.56..42.89 rows=50 width=312) (actual time=0.212..1.903 rows=50 loops=1)
Buffers: shared hit=108 read=24
-> Index Scan using orders_customer_created_idx on orders (cost=0.56..71234.12 rows=84123 width=312)
Index Cond: (customer_id = 123)
Planning Time: 0.295 ms
Execution Time: 1.982 ms
Ce que cela signifie : Les lectures vs hits vous renseignent sur le comportement du cache. Si vous voyez un sequential scan avec de grandes lectures alors que vous attendiez un index scan, c’est votre pic CPU déguisé.
Décision : Corrigez les index, les stats (ANALYZE), et la forme de la requête. Si cette requête est critique, stabilisez-la : index stables, évitez la sensibilité aux paramètres si nécessaire, et ajoutez des garde-fous.
Trois mini-récits d’entreprises tirés des tranchées
Incident 1: The outage caused by a wrong assumption
Une entreprise SaaS de taille moyenne a migré un sous-système de facturation de MariaDB vers PostgreSQL. La migration s’est bien passée en staging.
La production aussi — jusqu’à la première exécution de facturation de fin de trimestre. Le CPU a plafonné, les API ont timeouté, les tickets ont afflué.
Le postmortem avait une odeur familière : « Nous avons supposé que la base monterait en charge comme l’ancienne. »
L’hypothèse était que plus de connexions signifiaient plus de débit. Sur MariaDB ils avaient appris, douloureusement, à plafonner les connexions et
utiliser du pooling. Pendant la migration, un nouveau service est arrivé avec « ouvre juste une connexion par requête, c’est bon », parce que l’équipe
ne voulait pas gérer le pooling sous pression de délai. Les backends Postgres se sont multipliés. Les commutations de contexte sont devenues folles. Le CPU a brûlé
et la latence des requêtes a explosé.
La partie la plus agaçante : les requêtes n’étaient pas lentes isolément. Elles étaient lentes ensemble. En pointe, la surcharge d’ordonnancement et les files de verrous
ont amplifié tout. L’autovacuum a aussi pris du retard pendant la tempête d’écritures de facturation, ajoutant de la bloat et rendant les lectures pires,
justement quand les portails clients étaient les plus occupés.
La correction n’était pas exotique. Ils ont introduit un pooler de connexions, mis un plafond strict sur les sessions actives par service, et fait de la
rétro-pression sur la concurrence une fonctionnalité prioritaire au lieu d’un secret honteux. Le CPU a chuté suffisamment pour que les vrais goulets d’étranglement
apparaissent, et ceux-ci étaient solubles.
Incident 2: The “optimization” that backfired
Une plateforme e‑commerce sur MariaDB avait un service panier très écrit. Quelqu’un a « optimisé » en ajoutant trois index secondaires composites pour supporter
de nouvelles requêtes de reporting. Les index ont rendu les endpoints de reporting plus rapides en test. C’était célébré sur Slack puis fusionné discrètement.
Le jour de pointe est arrivé. Les pics CPU ont frappé juste après la vague de trafic du matin. Les écritures ont ralenti d’abord, puis les paiements ont timeouté. La BD
n’était pas IO-bound. Elle était CPU-bound dans le moteur de stockage, effectuant la maintenance d’index et se battant contre la contention dans les mêmes feuilles
d’index chaudes que chaque mise à jour de panier touchait. La plateforme avait accidentellement transformé une table optimisée pour l’écriture en taxe d’écriture multi-index.
Le plan de rollback manquait. Ils ne pouvaient pas supprimer instantanément les index sans risquer des verrous prolongés et plus de douleur. La mitigation d’urgence a été
de rediriger le reporting vers un replica et de couper temporairement les fonctionnalités de reporting. Puis, pendant une fenêtre contrôlée, ils ont retiré le pire index
et l’ont remplacé par une forme différente qui supportait la requête nécessaire tout en réduisant l’amplification d’écriture.
La leçon n’était pas « ne pas indexer ». C’était « l’indexation est une caractéristique du chemin d’écriture ». Si vous ne pouvez pas expliquer le coût d’écriture d’un index
sous haute concurrence, vous n’avez pas fini.
Incident 3: The boring but correct practice that saved the day
Une fintech utilisant PostgreSQL avait une règle : tout changement de schéma doit inclure (a) le changement de plan attendu,
(b) une voie de rollback, et (c) une requête canari de vérification. Personne n’aimait la règle. C’était de la paperasserie avec une alarme.
Un vendredi, un changement apparemment anodin a été déployé : un nouveau prédicat sur une grande table. La requête canari s’est exécutée automatiquement après le déploiement
et a comparé la forme du plan à la baseline. Elle a signalé un sequential scan surprise. Pas encore dans le trafic de production — juste dans le canari.
Le déploiement s’est auto-pausé.
L’ordinateur du développeur montrait l’index utilisé. Les statistiques de production étaient obsolètes ; la distribution différait ; le planner a fait un choix différent.
L’équipe a exécuté un ANALYZE sur la table affectée de manière contrôlée, ajusté un index unique pour coller au nouveau prédicat, et relancé le canari.
Le plan est redevenu prévisible.
Rien de dramatique n’est arrivé. C’est le but. L’incident qui n’a pas eu lieu est le meilleur type — silencieux, légèrement fastidieux, et financièrement invisible.
Faits intéressants et contexte historique (8 points)
- L’histoire de la scission MySQL compte : MariaDB a été créée par les fondateurs originels de MySQL après l’acquisition de Sun par Oracle, en partie pour maintenir un fork piloté par la communauté.
- InnoDB n’a pas toujours été « par défaut partout » : Les premières déploiements MySQL utilisaient beaucoup MyISAM ; InnoDB est devenu dominant à mesure que les charges transactionnelles et les attentes de fiabilité augmentaient.
- La lignée de PostgreSQL est académique : Il descend du projet POSTGRES à l’UC Berkeley, avec une longue tradition d’ingénierie axée sur la correction.
- MVCC est à la fois cadeau et facture : Le design MVCC de PostgreSQL évite beaucoup de verrous de lecture mais nécessite un vacuum continu ; sautez l’entretien et les coûts CPU apparaissent plus tard.
- Les modèles thread vs process façonnent le comportement en pointe : Les threads de MariaDB rendent la surcharge par connexion plus légère que des processus complets, mais restent vulnérables à l’ordonnancement/la contention à haute concurrence.
- Postgres s’est beaucoup amélioré en parallélisme : Les PostgreSQL modernes peuvent utiliser l’exécution parallèle des requêtes dans plus de cas, ce qui peut améliorer le débit mais aussi brûler plus de cœurs plus vite si un mauvais plan devient parallèle.
- MariaDB et MySQL ont divergé sur les fonctionnalités : Selon les versions, MariaDB peut offrir des comportements d’optimiseur différents, des options de thread pool et une instrumentation différente de MySQL upstream, ce qui change la manière dont les pics CPU se manifestent.
- Les deux écosystèmes ont appris à la dure à propos des « trop nombreuses connexions » : La bonne pratique opérationnelle de pooling et de rétro-pression est maintenant un folklore partagé, parce que la physique est persuasive.
Deuxième blague (et la dernière, selon les règles) : régler une base de données pendant un incident, c’est comme changer des pneus d’avion en plein vol.
C’est possible, mais tout le monde se souviendra de votre expression.
Erreurs courantes : symptôme → cause racine → correction
1) Symptom: CPU 90–100%, QPS flat, latency rising
Cause racine : Thrash de concurrence (trop de connexions/sessions), surcharge de l’ordonnanceur, contention interne.
Correction : Limiter les connexions ; imposer du pooling ; réduire la concurrence applicative ; ajouter de la rétro-pression. Sur MariaDB, envisager un thread pool ; sur Postgres, utiliser un pooler et définir un max_connections raisonnable.
2) Symptom: CPU spikes align with a single endpoint or job
Cause racine : Une forme de requête est devenue coûteuse (régression de plan, index manquant, mauvais ordre de jointure).
Correction : Capturez la requête, exécutez EXPLAIN/ANALYZE, ajoutez/ajustez l’index, corrigez la sargabilité des prédicats, rafraîchissez les stats. Mettez un garde-fou : statement timeout, limite de requête, ou feature flag.
3) Symptom: CPU high, lots of lock waits, throughput collapses
Cause racine : Transactions longues ; lignes chaudes ; inversions d’ordre de verrou ; patterns « mise à jour de la même ligne ».
Correction : Raccourcissez les transactions ; corrigez les points chauds (sharder les compteurs chauds, éviter les patterns « latest » sur une seule ligne) ; assurez un ordre de verrous cohérent ; regroupez les écritures de manière sensée.
4) Symptom (Postgres): CPU gradually worsens over days/weeks, not minutes
Cause racine : Dette d’autovacuum et bloat. Les requêtes font plus de travail par résultat à mesure que les dead tuples s’accumulent.
Correction : Tunez autovacuum pour les tables lourdes ; évitez les transactions longues ; surveillez les dead tuples ; planifiez la maintenance ; envisagez fillfactor ou partitionnement pour les tables à fort churn.
5) Symptom (MariaDB): CPU high during writes, replication lag grows
Cause racine : Amplification d’écriture due aux index/binlog ; overhead du chemin de commit ; pression fsync ; pages d’index chaudes.
Correction : Supprimez les index secondaires inutiles ; optimisez le batching des transactions ; validez les réglages de durabilité ; assurez un stockage rapide ; tunez le group commit ; déplacez la lecture/reporting hors primaire.
6) Symptom: CPU high, but clients see “Sending data” / slow reads
Cause racine : Grands jeux de résultats ; pagination inefficace ; lenteur côté client forçant le serveur à tenir des ressources plus longtemps.
Correction : Limitez la taille des résultats ; pagination par clé (keyset) ; ne sélectionner que les colonnes nécessaires ; corrigez les patterns N+1 ; ajoutez des timeouts et des max rows.
7) Symptom: CPU spikes after deploying a “minor” change
Cause racine : Changement de plan dû à modification de prédicat ou dérive des stats ; nouvel index modifiant les décisions du planner ; distribution de paramètres différente.
Correction : Requêtes canari pour les plans ; comparer les baselines ; analyser les tables affectées ; ajuster les index ; si nécessaire, réécrire la requête pour une stabilité de plan.
Listes de contrôle / plan étape par étape pour une charge de pointe soutenue
A. During the incident (stabilize first)
- Arrêtez la ruée : activez la rétro-pression dans l’application ; réduisez le trafic non critique ; appliquez un rate-limit sur les endpoints lourds.
- Limitez la concurrence à la frontière DB : pooler de connexions ; abaissez temporairement les sessions actives max ; priorisez les services critiques.
- Trouvez les deux formes de requête principales : processlist/pg_stat_activity + top SQL stats. Ne chassez pas la 20ᵉ requête.
- Vérifiez les accumulations de verrous : si les lock waits dominent, tuer des requêtes au hasard n’est pas une stratégie — trouvez le bloqueur.
- Validez l’IO : si le stockage est saturé, les corrections CPU ne tiendront pas ; réduisez d’abord la pression IO.
B. Within 48 hours (make it not happen again next week)
- Baseliner les plans : capturez les EXPLAIN des requêtes critiques et stockez-les avec le service.
- Fixez la santé du vacuum/purge : tuning autovacuum Postgres ; contrôle des transactions longues et visibilité du purge MariaDB.
- Dimensionnez correctement les index : gardez ce qui aide le chemin de lecture, retirez ce qui taxe le chemin d’écriture en pic.
- Définissez des timeouts raisonnables : statement timeouts, lock timeouts, et deadlines applicatives. Une requête bloquée est un agent pathogène.
- Testez la concurrence de pointe : les tests de charge doivent inclure des comptes de connexions similaires à la production et une distribution de données réaliste.
C. Architecture choices that affect “who burns cores faster”
- Si vous pouvez pooler agressivement : PostgreSQL devient plus calme en pointe car vous limitez les backends et protégez l’ordonnanceur.
- Si vous ne pouvez pas contrôler les clients : MariaDB avec thread pool peut être indulgent, mais vous avez toujours besoin de plafonds ; la concurrence incontrôlée finira par gagner.
- Si votre charge est fortement tournée vers l’écriture : Postgres a besoin de discipline sur vacuum ; MariaDB a besoin de discipline d’index et d’un design de schéma conscient de la contention.
- Si vous avez quelques requêtes analytiques complexes sur de l’OLTP : les deux brûleront du CPU ; isolez les charges (réplicas, systèmes séparés) au lieu de prier.
FAQ
1) So, who burns cores faster: MariaDB or PostgreSQL?
En concurrence non contrôlée, les deux peuvent incinérer le CPU. MariaDB atteint souvent durement les limites de contention et d’ordonnancement des threads quand
vous lui balancez des milliers de connexions actives. PostgreSQL brûle souvent plus vite le CPU quand un mauvais plan ou une dette d’autovacuum multiplie le travail
par requête, et les plans parallèles peuvent amplifier cela. Le « gagnant » est celui que vous gérez le plus négligemment.
2) Why does CPU spike when QPS hasn’t increased much?
Parce que le travail par requête a augmenté. Causes typiques : régression de plan, hausse du miss rate du cache, bloat/dead tuples, ou contention de verrous
allongeant la durée des requêtes. Même QPS, plus de CPU-secondes par requête, utilisation plus élevée.
3) Is high CPU always bad?
Non. Un CPU élevé avec une latence stable et un débit prévisible peut être acceptable ; vous utilisez ce pour quoi vous avez payé. Un CPU élevé avec une latence
en hausse et un débit en baisse est le mauvais type : surcharge de coordination ou travail amplifié.
4) Can adding cores solve peak CPU spikes?
Parfois, mais c’est la solution la moins fiable. Si vous êtes CPU-bound sur l’exécution pure des requêtes et que l’échelle est linéaire, plus de cœurs
achètent de la marge. Si vous êtes limité par la contention, les verrous ou les commutations de contexte, plus de cœurs vous donneront surtout une facture
plus grosse et le même incident.
5) What’s the single best practice to prevent CPU collapse?
Pooling de connexions avec plafonds stricts et rétro-pression. Cela force le système à se comporter sous pic. Sans cela, vous jouez à la loterie de la concurrence.
6) For PostgreSQL, how do I know it’s autovacuum debt?
Regardez la montée de n_dead_tup, des scans lents, l’augmentation des lectures de buffers, et un vacuum à la traîne (ou bloqué par des transactions longues).
Le CPU grimpera à mesure que les requêtes touchent plus de pages et effectuent plus de contrôles de visibilité.
7) For MariaDB, what’s the fastest indicator of connection/thread pain?
Un Threads_connected élevé, de fortes commutations de contexte au niveau OS, et un Threads_running bien au-dessus du nombre de cœurs.
Associez cela à un InnoDB status montrant de la contention ou une history list length longue pour une confirmation supplémentaire.
8) Do replicas help CPU spikes?
Les réplicas de lecture aident quand les lectures dominent et que vous pouvez les router proprement. Ils ne résolvent pas la saturation CPU du chemin d’écriture
sur le primaire. De plus, si votre primaire est à court de CPU, la réplication peut prendre du retard et les réplicas deviennent obsolètes quand vous en avez besoin.
9) Should I tune kernel parameters first?
Seulement après avoir prouvé le goulot dans la base. Le tuning du noyau peut aider (ordonnanceur, réseau, files IO), mais ce n’est pas un substitut à la correction
de la concurrence, des plans de requêtes et de la santé vacuum/purge.
10) What if CPU is high but perf shows mostly mutex/lock functions?
C’est de la contention. Vous payez pour la coordination, pas pour le calcul. La correction est de réduire la concurrence et d’éliminer les points chauds :
raccourcir les transactions, sharder les compteurs chauds, repenser les patterns « single-row », et plafonner les sessions actives.
Conclusion : étapes pratiques suivantes
Sous charge maximale, MariaDB et PostgreSQL ne « pètent » pas le CPU au hasard. Ils suivent des schémas. MariaDB a tendance à punir la concurrence non contrôlée
et l’amplification du chemin d’écriture ; PostgreSQL a tendance à punir la négligence du vacuum et les surprises de plan. Si vous voulez moins de pics,
cessez de traiter le CPU comme le problème et traitez-le comme le reçu.
Étapes suivantes qui rapportent immédiatement :
- Imposez des limites strictes sur la concurrence BD (pooling + plafonds) et faites en sorte que l’application les respecte.
- Instrumentez le top SQL par temps total et alertez sur les changements brusques de forme de plan ou de distribution de latence.
- Maintenez l’entretien : discipline d’autovacuum sur Postgres ; contrôle des transactions longues et visibilité du purge sur MariaDB.
- Faites de l’indexation une décision de production : chaque nouvel index doit justifier son coût d’écriture en situation de pointe.
- Exercez le playbook sur un test de charge en staging. La première fois que vous exécutez ces commandes ne doit pas être pendant un incident.