MySQL vs Percona Server : performances lors des pics — ce qui change en pratique

Cet article vous a aidé ?

On ne choisit pas une base de données au calme. On la choisit le jour où l’équipe marketing « envoie juste un e‑mail », votre API subit une tempête de connexions et la latence au p95 se transforme en danse interprétative.

MySQL et Percona Server sont des proches parents. Sous une charge stable, vous pouvez faire des benchmarks jusqu’à l’égalité. Lors des pics, les différences apparaissent dans les parties moches : contrôle d’admission, comportement de vidage, pression sur les mutex, coût de l’instrumentation et la vitesse à laquelle vous pouvez prouver ce qui est réellement cassé.

Ce qui change réellement sous les pics (pas le marketing)

Percona Server a commencé comme « MySQL, mais avec les réglages dont nous avons besoin et la visibilité que nous aurions aimé voir dans MySQL ». MySQL moderne a rattrapé plusieurs éléments, et Percona a aussi évolué au fil des changements en amont. La question pratique aujourd’hui n’est pas « lequel est plus rapide », mais « lequel se comporte de façon plus prévisible quand le monde brûle ». La prévisibilité gagne les incidents.

Les différences principales qui apparaissent lors des pics

  • Contrôle d’admission et ordonnancement des threads : Percona Server propose souvent une option de thread pool qui peut canaliser les tempêtes de connexions et les rafales OLTP à forte concurrence. MySQL Enterprise dispose d’un thread pool ; la Community généralement pas. Si votre pic est « trop de connexions simultanées », le thread pooling peut faire la différence entre un système dégradé et un système mort.
  • Comportement d’InnoDB/XtraDB et variables supplémentaires : historiquement, Percona distribuait XtraDB (un fork/amélioration d’InnoDB) avec plus d’instrumentation et d’options de réglage. Aujourd’hui, beaucoup d’améliorations sont upstream, mais Percona a tendance à exposer plus de points d’introspection et de commandes opérationnelles utiles en plein incident.
  • Posture par défaut de l’instrumentation : Percona Server rend souvent plus facile l’obtention d’un aperçu profond rapidement (compteurs supplémentaires, options de slow query enrichies, parfois moins de friction pour activer des plugins utiles). MySQL peut faire la plupart de ces choses, mais il faut souvent les prévoir : activer une instrumentation lourde pendant un pic, c’est comme décider de faire une IRM complète en sprintant.
  • Écosystème d’outils opérationnels : Percona Toolkit (pt-query-digest, pt-online-schema-change, pt-kill) n’est pas « Percona Server », mais dans la pratique les équipes adoptent souvent le bundle. Lors des pics, ça compte : vous voulez des commandes de triage reproductibles, pas du SQL artisanal tapé avec les mains qui tremblent.
  • Choix de compilation et packaging : Percona tend à livrer des builds orientés besoins opérationnels (plugins supplémentaires, defaults Performance Schema utilisables, choix de télémétrie sensés). Le packaging du fournisseur impacte votre cadence de mise à jour et ce qui est activé par défaut — et les valeurs par défaut décident du comportement lors d’une charge surprise.

Le piège : supposer que Percona Server est intrinsèquement « plus rapide » en tout. Ce n’est pas le cas. Il est souvent plus contrôlable et plus observable. Lors des pics, contrôlabilité et visibilité battent la vitesse théorique en labo.

Une petite blague, comme promis : Une base de données en pic, c’est comme une invitation à une réunion — ajouter des gens ne la fait jamais finir plus vite.

Quelques faits et historique utiles (parce que les valeurs par défaut ont une histoire)

Ce ne sont pas des anecdotes de quiz. Chacun explique pourquoi un comportement existe et pourquoi vous le verrez en production.

  1. La valeur initiale de Percona Server était XtraDB : pendant des années il livrait un fork enrichi d’InnoDB avec des compteurs et réglages supplémentaires que l’amont n’avait pas. Cet ADN se retrouve encore dans les fonctionnalités « ops‑first ».
  2. Le thread pool n’était pas toujours accessible : le thread pool de MySQL a historiquement été une fonctionnalité Enterprise ; Percona a rendu une capacité similaire disponible à plus d’utilisateurs via sa distribution et ses plugins.
  3. MySQL 5.6→5.7→8.0 a changé la donne : améliorations natives du Performance Schema, meilleur comportement du verrouillage des métadonnées et améliorations de la réplication ont réduit la pression « il faut un fork » pour de nombreuses charges.
  4. Performance Schema est passé de « à éviter » à « à utiliser » : les premières déploiements craignaient l’overhead ; les versions modernes peuvent être configurées pour la production et sont essentielles pour la forensique des pics.
  5. La réplication est devenue multi‑thread par étapes : du thread SQL unique au multi‑thread sur le replica avec différents modes d’ordonnancement ; le comportement lors des pics dépend fortement de ces réglages.
  6. Les heuristiques de vidage d’InnoDB ont évolué : flushing adaptatif et checkpointing plus stable ont amélioré les choses, mais vous pouvez encore créer des « flush storms » auto‑infligés avec de mauvais réglages ou des hypothèses de stockage irréalistes.
  7. Le comportement par défaut d’authentification et SSL de MySQL a changé : lors des pics, le coût des handshakes et le churn de connexions peuvent dominer ; la version et le plugin d’authentification comptent.
  8. L’écosystème Percona a façonné les habitudes opérationnelles : pt-query-digest et compagnons ont entraîné une génération d’équipes SRE à traiter l’analyse des requêtes comme une compétence de réponse aux incidents, pas comme un projet trimestriel.

Les pics ne sont pas une « forte charge » : la mécanique de défaillance

La charge en régime permanent teste surtout la capacité. Les pics testent les boucles de contrôle : files d’attente, backpressure et la rapidité avec laquelle les sous‑systèmes internes peuvent se remettre d’un surplus soudain de travail.

Ce qu’un pic fait à un système de type MySQL

Quand les requêtes augmentent de 5 à 20× pendant une courte période, vous obtenez généralement une combinaison de :

  • Tempêtes de connexions : nouvelles négociations TCP + TLS + auth et surcharge par thread de connexion. Même si les requêtes sont peu coûteuses, l’ordonnancement des threads et l’allocation mémoire deviennent coûteux.
  • Contention sur mutex/verrous : points chauds internes (verrous des instances du buffer pool, dict locks, cache de tables, MDL, contention sur l’Adaptive Hash Index, ou même contention du scheduler OS).
  • Accumulation de pages modifiées : les écritures qui dépassent le rythme de flushing font croître l’âge du checkpoint. Finalement, le moteur panique et flush de manière agressive, volant l’I/O aux lectures et augmentant la latence.
  • Pression sur le redo log : si le redo se remplit plus vite qu’il ne peut être checkpointé, vous subissez des blocages. C’est l’un des patterns les plus courants du type « tout allait bien jusqu’à ce que… ».
  • Retard de réplication : des rafales d’écritures créent un backlog ; les replicas prennent du retard, et votre scalabilité en lecture se transforme en amplification de lecture contre le primaire parce que l’application recourt au primaire.
  • Churn de cache : le buffer pool est pollué par des accès uniques (pensez : une page de promotion scannant une grande catégorie), ce qui pousse hors des pages « chaudes » et provoque une douleur prolongée même après la fin du pic.

Lors des pics, la bonne question est rarement « comment rendre les requêtes plus rapides », parce que la plupart des pics ne sont pas causés par une requête lente unique. Ils sont causés par trop de requêtes raisonnables en même temps, plus quelques-unes pathologiques qui deviennent létales quand les ressources sont serrées.

Contrôle de la concurrence : un thread de trop, c’est déjà trop

Dans MySQL Community standard, le mode courant est un thread par connexion. Lors d’un pic, vous obtenez une armée de threads se battant pour le CPU, les verrous et les lignes de cache. Votre machine n’est pas « occupée », elle est en thrashing.

Pourquoi le thread pool de Percona compte pendant les pics

Un thread pool change la forme de la défaillance. Au lieu de 2 000 threads exécutable essayant d’exécuter 2 000 requêtes simultanées, vous pouvez avoir un plus petit pool de workers qui limite l’exécution active et met le reste en file d’attente. Le système se dégrade plus gracieusement : la latence augmente, mais il continue de répondre.

Les thread pools ne sont pas magiques. Ils peuvent masquer les requêtes lentes (la file cache le feu), et introduire des problèmes d’équité (certains clients peuvent être affamés si le pool n’est pas bien réglé). Mais pour le mode de défaillance « tempête de connexions + effondrement du scheduler CPU », ils sont un outil pratique de contrôle d’admission.

Que faire si vous n’avez pas de thread pool

  • Rendre le pooling de connexions obligatoire : au niveau applicatif. Si votre appli ouvre de nouvelles connexions pendant des pics, vous choisissez la souffrance.
  • Utiliser max_connections comme un disjoncteur : pas comme une récompense. Réglez‑le selon ce que l’hôte peut supporter, et échouez rapidement en amont avec des retries/backoff appropriés.
  • Protéger le primaire avec un proxy : HAProxy/ProxySQL peuvent limiter le taux de connexions et fournir du multiplexage. Même une limite de connexions grossière en bordure peut empêcher la base de devenir le goulot d’étranglement.

Vidage InnoDB et checkpoints : là où les pics vont mourir

Si les pics sont votre principal problème, arrêtez de penser à InnoDB comme « un moteur de stockage ». En pic, c’est un ensemble de files concurrentes : threads de nettoyage de pages, writer du redo log, doublewrite buffer, cadence des fsync, et la capacité réelle de votre stockage à gérer de petites écritures aléatoires.

Le pattern classique de pic : les pages modifiées montent, puis le flush storm frappe

Pendant une rafale d’écritures, les pages modifiées s’accumulent dans le buffer pool. Si le flushing ne suit pas, l’âge du checkpoint augmente. Finalement InnoDB doit flush de manière agressive pour avancer le checkpoint et éviter de manquer d’espace redo. Ce flush agressif concurrence l’I/O de lecture et le CPU, d’où une explosion de latence. La chute : la pire latence apparaît souvent après la rafale.

Paramètres qui changent le profil du pic

  • innodb_log_file_size / innodb_redo_log_capacity : Une capacité redo plus grande peut absorber des rafales, mais allonge aussi le temps de récupération après crash et peut retarder le signal « vous êtes en danger ».
  • innodb_flush_log_at_trx_commit : 1 est le plus sûr ; 2 sacrifie la durabilité pour le débit. Lors des pics, la fréquence des fsync est souvent un goulot. Si vous changez ça, faites‑le comme décision business, pas en panique.
  • innodb_io_capacity / innodb_io_capacity_max : Ce sont des hints pour le flushing de fond. Des valeurs incorrectes provoquent soit un flushing paresseux (panique de checkpoint plus tard) soit un flushing agressif (pression I/O constante).
  • innodb_flush_neighbors : Sur SSD/NVMe, flusher les voisins est généralement du travail gaspillé. Sur disques tournants, ça peut aider. Si vous êtes sur du stockage moderne et que c’est activé, vous payez peut‑être pour la nostalgie.
  • innodb_lru_scan_depth et page_cleaners : Ils affectent la rapidité avec laquelle InnoDB trouve les pages modifiées à flush. Un mauvais réglage peut générer soit une surconsommation CPU soit un nettoyage insuffisant.

Deuxième blague (et c’est fini) : régler le flushing pendant un incident, c’est comme changer un pneu sur l’autoroute — possible, mais votre métrique de succès est « personne n’est mort ».

Réplication lors des rafales : le retard que vous gagnez

Le retard de réplication pendant les pics n’est rarement « la réplication est lente ». C’est généralement que vous avez produit des écritures plus rapidement que le replica ne peut les appliquer, ou que vous avez forcé la réplication à se sérialiser à cause du schéma ou de la forme des transactions.

Où les différences MySQL/Percona apparaissent

La plupart du comportement de réplication vient de MySQL upstream, mais les distributions Percona rendent souvent plus simple l’observation et le réglage. Les réalités de l’ère des pics :

  • La réplication en mode row est généralement meilleure lors des pics : la réplication par instruction peut se comporter bizarrement avec de la non‑déterminisme et être plus dure à paralléliser en toute sécurité.
  • Les grosses transactions sont des usines à retard : une transaction géante s’applique en bloc. Votre replica reste bloqué sur une seule tâche pendant que l’application réclame des lectures fraîches.
  • Les applyers multi‑thread ont besoin du bon mode : « LOGICAL_CLOCK » (en termes MySQL) et un suivi des dépendances correct peuvent aider. Mais si votre charge focalise quelques lignes, le parallélisme ne vous sauvera pas.
  • Paramètres de durabilité sur les replicas : vous pouvez souvent assouplir la durabilité sur les replicas (en connaissance de cause) pour les garder assez proches pour servir des lectures pendant les rafales.

Instrumentation et observabilité : voir le problème sans devenir le problème

Pendant un pic, vous avez besoin de réponses rapidement : est‑ce le CPU, l’I/O, les verrous ou l’application ? La différence d’observabilité entre « correct » et « bon » se compte en minutes de downtime.

MySQL vous fournit Performance Schema, sys schema, EXPLAIN ANALYZE et beaucoup de compteurs d’état. Percona Server ajoute souvent ou expose plus de détails et est livré dans une posture adaptée aux opérations. L’important n’est pas la marque. L’important est de pouvoir collecter les preuves sans provoquer une charge supplémentaire.

Une citation opérationnelle, à utiliser avec prudence : L’espoir n’est pas une stratégie. — James Cameron. Elle s’applique aux réponses aux incidents plus que de raison.

Ce que vous devriez pré‑activer avant les pics

  • Slow query log avec des seuils sensés (et échantillonnage si disponible). Les pics impliquent souvent « des requêtes modérément lentes à haute fréquence ».
  • Performance Schema avec consumers ciblés (pas tout). Vous voulez waits, stages et digests de statements — pas nécessairement des tables d’histoire complètes sur un primaire très sollicité.
  • Pipeline de digest de requêtes capable de tourner sous charge sans bloquer la base (parser les logs hors hôte, ne pas exécuter d’analyse lourde sur le primaire).
  • Télémétrie au niveau OS (iostat, vmstat, métriques CPU prêtes pour perf) parce que les bases adorent mentir par omission.

Tâches pratiques : commandes, sorties et la décision que vous prenez

Voici les actions que vous faites réellement à 02:14. Chacune inclut une commande exécutable, un extrait de sortie réaliste, ce que ça signifie et la décision qui s’ensuit. Hypothèses : hôte Linux, systemd, client MySQL disponible, vous avez les identifiants dans ~/.my.cnf ou env.

Tâche 1 : Vérifier si vous êtes CPU-saturé ou simplement thread-saturé

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
 6  1      0  31284  81264 923456    0    0   120   980 1800 4200 55 18 12 15  0
18  2      0  29876  79912 918332    0    0   110  1120 2600 9800 63 22  5 10  0
21  3      0  28740  79020 914120    0    0    90  1400 2900 12000 68 25  2  5  0

Signification : Un fort nombre de threads r en runnable et un id faible indique une pression CPU ; un cs élevé suggère une tempête de changements de contexte.

Décision : Si cs est énorme et la latence très variable, suspectez trop de threads actifs. Envisagez thread pool (Percona) ou limitez la concurrence via proxy/app. Si wa domine, orientez‑vous vers l’I/O.

Tâche 2 : Détecter la saturation I/O et la latence

cr0x@server:~$ iostat -x 1 3
Linux 6.1.0 (db01)  12/30/2025  _x86_64_  (16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          41.20    0.00   12.10   18.90    0.00   27.80

Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await  r_await  w_await
nvme0n1        220.0  950.0  3520.0 48200.0     0.0    10.0   99.2   18.4     4.2     21.8

Signification : Le device est à ~99% d’utilisation avec ~18ms d’await ; l’attente d’écriture est pire. C’est une histoire de flushing/redo/doublewrite jusqu’à preuve du contraire.

Décision : Réduisez la pression d’écriture (brider les jobs en batch, désactiver les écritures non essentielles, décharger la charge). Ensuite inspectez le checkpoint/les pages modifiées d’InnoDB.

Tâche 3 : Vérifier une tempête de connexions

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads%';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_cached    | 12    |
| Threads_connected | 1800  |
| Threads_created   | 98234 |
| Threads_running   | 280   |
+-------------------+-------+

Signification : 1800 connectés, 280 en exécution. Si ça a beaucoup augmenté, vous êtes en territoire tempête. Un Threads_created élevé indique du churn.

Décision : Si vous avez le thread pool Percona, activez/le réglez (planifié). Sinon : imposez le pooling côté appli, plafonnez via proxy, baissez max_connections pour protéger la machine et utilisez du backoff en amont.

Tâche 4 : Identifier le type d’attente principal (verrous vs I/O vs CPU)

cr0x@server:~$ mysql -e "SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT/1000000000000 AS seconds FROM performance_schema.events_waits_summary_global_by_event_name WHERE COUNT_STAR > 0 ORDER BY SUM_TIMER_WAIT DESC LIMIT 5;"
+--------------------------------------+------------+---------+
| EVENT_NAME                           | COUNT_STAR | seconds |
+--------------------------------------+------------+---------+
| wait/io/file/innodb/innodb_log_file  | 12899322   | 1824.51 |
| wait/synch/mutex/innodb/buf_pool     |  8933121   |  610.23 |
| wait/io/file/innodb/innodb_data_file |  4021932   |  488.11 |
| wait/lock/metadata/sql/mdl           |   832211   |  120.09 |
| wait/synch/rwlock/innodb/index_tree  |  1010021   |   98.77 |
+--------------------------------------+------------+---------+

Signification : Les attentes sur le redo log dominent. C’est une pression fsync/redo ; le mutex du buffer pool est aussi en souffrance.

Décision : Traitez‑le comme lié aux écritures. Envisagez des compromis de durabilité seulement si le business l’autorise. Sinon réduisez la concurrence d’écriture, réparez la latence stockage et ajustez la capacité redo/le flushing dans un changement planifié.

Tâche 5 : Vérifier les pages modifiées d’InnoDB et la pression sur le checkpoint

cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,140p'
...
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 2197815296
Buffer pool size   131072
Free buffers       128
Database pages     129880
Old database pages 47910
Modified db pages  32120
...
LOG
---
Log sequence number          112903450192
Log flushed up to            112903120991
Last checkpoint at           112901000000
...

Signification : Un grand nombre de Modified db pages signifie beaucoup de pages sales. Un large écart entre LSN et checkpoint indique la croissance de l’âge du checkpoint. Si cet écart continue de croître, une tempête de flush arrive (ou est déjà là).

Décision : Améliorez l’efficacité du flushing de fond (corrigez innodb_io_capacity), réduisez le taux d’écriture et assurez‑vous que le stockage peut gérer le pattern d’écriture. Évitez le « set io_capacity=20000 » aléatoire sauf si vous connaissez les capacités du device.

Tâche 6 : Repérer les empreintes des requêtes dominantes pendant le pic

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?
COUNT_STAR: 481220
total_s: 912.44
*************************** 2. row ***************************
DIGEST_TEXT: UPDATE inventory SET qty = qty - ? WHERE sku = ?
COUNT_STAR: 112881
total_s: 401.18
*************************** 3. row ***************************
DIGEST_TEXT: SELECT COUNT(*) FROM sessions WHERE expires_at > ?
COUNT_STAR: 98011
total_s: 380.02

Signification : Vous avez un SELECT à haute fréquence et un UPDATE chaud sur inventory. Pendant les pics, les « lignes chaudes » tuent la concurrence même si les requêtes sont indexées.

Décision : Pour l’UPDATE chaud : envisagez de sharder la clé de contention, repenser la réservation d’inventaire ou déplacer ce compteur vers un cache atomique avec write‑behind (prudemment). Pour le SELECT : assurez‑vous que l’index prend en charge l’ORDER BY ; envisagez la mise en cache des top N par utilisateur.

Tâche 7 : Confirmer le support d’index pour la requête de pic

cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 20;"
+----+-------------+--------+------+---------------+-------------------+---------+------+------+-----------------------------+
| id | select_type | table  | type | possible_keys | key               | key_len | ref  | rows | Extra                       |
+----+-------------+--------+------+---------------+-------------------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | orders | ref  | idx_user_date | idx_user_date     | 8       | const|  60  | Using where; Using filesort |
+----+-------------+--------+------+---------------+-------------------+---------+------+------+-----------------------------+

Signification : Il utilise l’index mais fait toujours des filesorts, ce qui implique que l’ordre de l’index ne correspond pas à la requête (ou problème de collation/ASC‑DESC).

Décision : Créez le bon index composite (par ex. (user_id, created_at) avec la direction de tri correspondante sur MySQL 8.0 quand applicable). Cela réduit le CPU et le travail sur tables temporaires pendant les pics.

Tâche 8 : Vérifier la pression sur les tables temporaires (les temp tables sur disque sont du poison en pic)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%';"
+-------------------------+--------+
| Variable_name           | Value  |
+-------------------------+--------+
| Created_tmp_disk_tables | 982112 |
| Created_tmp_files       | 12011  |
| Created_tmp_tables      | 1812231|
+-------------------------+--------+

Signification : Beaucoup de temp tables sur disque. En pic, cela devient une amplification d’I/O, souvent sur le même device que celui qu’InnoDB utilise pour le redo/les données.

Décision : Corrigez les requêtes qui provoquent des sorts/group‑by sans index ; augmentez tmp_table_size/max_heap_table_size prudemment ; assurez‑vous que tmpdir est sur un stockage rapide si nécessaire.

Tâche 9 : Détecter les accumulations de verrouillage des métadonnées (DDL en pic, hobby pas plan)

cr0x@server:~$ mysql -e "SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, COUNT(*) cnt FROM performance_schema.metadata_locks GROUP BY 1,2,3,4,5 ORDER BY cnt DESC LIMIT 5;"
+-------------+---------------+-------------+-----------+-------------+-----+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | LOCK_TYPE | LOCK_STATUS | cnt |
+-------------+---------------+-------------+-----------+-------------+-----+
| TABLE       | app           | users       | SHARED    | GRANTED     | 980 |
| TABLE       | app           | users       | EXCLUSIVE | PENDING     |  12 |
| TABLE       | app           | orders      | SHARED    | GRANTED     | 620 |
+-------------+---------------+-------------+-----------+-------------+-----+

Signification : Un verrou EXCLUSIVE en attente sur users alors que des centaines tiennent des verrous SHARED : quelqu’un a tenté un DDL (ou une opération lourde en verrous) et bloque désormais les statements qui requièrent l’accès aux métadonnées.

Décision : Trouvez le bloqueur et tuez‑le si c’est sûr. Replanifiez le DDL avec des outils online et des garde‑fous.

Tâche 10 : Trouver la session qui bloque réellement

cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head -n 15
Id	User	Host	db	Command	Time	State	Info
1123	app	10.0.4.21:51244	app	Query	35	Waiting for table metadata lock	ALTER TABLE users ADD COLUMN flags INT DEFAULT 0
1188	app	10.0.4.19:50122	app	Query	2	Sending data	SELECT * FROM users WHERE id = 98322
...

Signification : L’ALTER est en attente, mais il peut aussi être la cause du churn de verrous selon le type de DDL et la version.

Décision : Pendant un pic, tuez le DDL sauf s’il fait partie d’une migration contrôlée et connue comme sûre. Puis mettez en place la règle « pas de DDL sur le primaire pendant les pics » comme politique, pas comme espoir.

Tâche 11 : Vérifier le retard de réplication et l’état de l’applier

cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep -i 'Seconds_Behind|Replica_IO_Running|Replica_SQL_Running|SQL_Delay|Slave_SQL_Running_State|Last_Errno|Last_Error'
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 840
Replica_SQL_Running_State: Waiting for dependent transaction to commit
Last_Errno: 0
Last_Error:

Signification : Le lag est élevé, le thread SQL est bloqué sur des dépendances de commit — souvent dû à de grosses transactions ou des contraintes d’ordre de commit.

Décision : Cherchez les grandes transactions, réduisez la contention au commit, assurez‑vous que les applyers multi‑thread sont correctement configurés et envisagez d’assouplir la durabilité sur les replicas si le scaling en lecture est critique.

Tâche 12 : Identifier les grosses transactions qui empoisonnent les replicas

cr0x@server:~$ mysql -e "SELECT trx_id, trx_started, trx_rows_modified, trx_query FROM information_schema.innodb_trx ORDER BY trx_rows_modified DESC LIMIT 3\G"
*************************** 1. row ***************************
trx_id: 90011234
trx_started: 2025-12-30 02:11:09
trx_rows_modified: 820441
trx_query: UPDATE events SET processed=1 WHERE processed=0

Signification : Un UPDATE massif dans une seule transaction. Ça va exploser votre buffer pool, le redo et le temps d’application sur les replicas.

Décision : Arrêtez‑le (tuez la session), puis refactorez le job pour faire des updates en chunks avec transactions bornées et throttling.

Tâche 13 : Vérifier le hit rate du buffer pool et la pression en lecture

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name                         | Value     |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests      | 981220122 |
| Innodb_buffer_pool_reads              | 18222122  |
+---------------------------------------+-----------+

Signification : Les lectures physiques sont significatives. Pendant les pics, si les lectures explosent, vous êtes peut‑être à court de mémoire ou le cache est pollué.

Décision : Si la RAM le permet, augmentez le buffer pool (planifié). Plus souvent : corrigez le pattern d’accès (cache, limiter les scans), ajoutez des indexes et protégez la base contre le comportement « parcourir tout » d’un seul client.

Tâche 14 : Voir si le cache de tables et les limites d’open files provoquent du churn

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Opened_tables'; SHOW VARIABLES LIKE 'table_open_cache';"
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Opened_tables | 922001 |
+---------------+--------+
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| table_open_cache | 4000  |
+------------------+-------+

Signification : Un nombre massif d’Opened_tables peut indiquer que le table cache est trop petit ou que vous churnez à cause de nombreuses tables/partitions. En pic, cela ajoute pression sur les mutex et les descripteurs de fichiers.

Décision : Augmentez table_open_cache et les limites OS de fichiers (planifié), et réduisez le nombre de tables/partitions si c’est auto‑infligé.

Tâche 15 : Confirmer si vous swappez (si oui, arrêtez tout le reste)

cr0x@server:~$ free -m
               total        used        free      shared  buff/cache   available
Mem:           32000       30910         210         130         879         420
Swap:           4096        2048        2048

Signification : Le swap est utilisé. Pour l’OLTP à faible latence, le swap est généralement une panne en très lente.

Décision : Réduisez immédiatement l’empreinte mémoire : arrêtez les sidecars, réduisez le buffer pool seulement si nécessaire et avec précaution, corrigez l’overcommit. À long terme : fournissez plus de RAM et gardez l’hôte dédié.

Tâche 16 : Vérifier si le binary logging est le goulot (commun sur les pics d’écriture)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Binlog_cache%'; SHOW GLOBAL STATUS LIKE 'Binlog_commits';"
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Binlog_cache_disk_use         | 1221  |
| Binlog_cache_use              | 88212 |
+-------------------------------+-------+
+----------------+--------+
| Variable_name  | Value  |
+----------------+--------+
| Binlog_commits | 922112 |
+----------------+--------+

Signification : L’utilisation disque du binlog cache suggère des transactions qui débordent ; le taux de commits binlog montre la pression. Combiné aux attentes redo, vous pouvez être bound au commit.

Décision : Réparez la taille des transactions ; assurez‑vous que le binlog est sur un stockage rapide ; envisagez des compromis sur sync_binlog seulement avec une position de durabilité claire.

Feuille de route pour un diagnostic rapide

Voici la séquence « entrer dans l’incident ». L’objectif est d’identifier la classe de goulot en moins de 10 minutes, puis d’appliquer l’atténuation la moins risquée.

Première étape : décider si c’est CPU, I/O ou verrou/file

  1. Vue OS : vmstat et iostat -x. Si iowait et await sont élevés, vous êtes I/O-bound. Si l’idle CPU est bas avec de grands changements de contexte, vous êtes concurrency‑bound.
  2. Compteurs MySQL rapides : Threads connected/running et top waits du Performance Schema. Vous cherchez « redo log waits », « data file waits », « MDL » ou hotspot de mutex.
  3. Processlist : Les requêtes sont-elles bloquées sur « Waiting for table metadata lock », « Waiting for commit », « Sending data », ou simplement « Sleep » avec trop de connexions ?

Deuxième étape : classifier le type de pic

  • Tempête de connexions : Threads_connected explose, CPU thrash, nombreuses requêtes courtes. Correctif : pooling, thread pool/proxy, plafonner la concurrence.
  • Rafale d’écritures : redo waits, âge de checkpoint, beaucoup de pages sales, iostat montre write await. Correctif : brider les writers, réduire la taille des transactions, tuner le flushing et la capacité redo (planifié).
  • Inondation de lectures / cache manquant : Innodb_buffer_pool_reads monte, iostat montre des lectures, CPU peut être modéré. Correctif : cache, indexes, protéger contre les scans, ajouter des replicas de lecture (mais surveiller le lag).
  • Contention de verrous / lignes chaudes : beaucoup de sessions en attente de verrous, hautes attentes de lock. Correctif : refactorer le hotspot, réduire l’isolation quand c’est sûr, réduire la portée des transactions, implémenter des retries idempotents.
  • Événement DDL/MDL : les attentes MDL explosent. Correctif : tuer/arrêter le DDL, utiliser des outils de migration en ligne avec throttling.

Troisième étape : choisir une atténuation qui ne complique pas la récupération

  1. Alléger la charge : limiter le débit, désactiver temporairement les endpoints coûteux, mettre en file les écritures, arrêter les jobs non essentiels.
  2. Stabiliser la concurrence : réduire max_connections (oui, vraiment), imposer le pooling applicatif, activer le thread pool si disponible.
  3. Arrêter l’hémorragie : tuez le pire coupable (la grosse UPDATE, le rapport en fuite, l’ALTER accidentel), puis continuez l’investigation.

Erreurs courantes : symptôme → cause racine → correction

1) Symptôme : latence qui spike toutes les quelques minutes, même après la baisse du trafic

Cause racine : pression sur les checkpoints et flush storms ; le flushing de fond ne peut pas suivre pendant les rafales, donc il « rattrape » douloureusement ensuite.

Correction : corriger innodb_io_capacity pour correspondre aux IOPS réelles du device, vérifier la capacité redo, réduire la taille des transactions et assurer la stabilité de la latence stockage. Ne « résolvez » pas ça en baissant la durabilité sauf si c’est explicitement acceptable.

2) Symptôme : CPU saturé, mais le débit est pire

Cause racine : trop de threads exécutables, changement de contexte, contention de mutex. Souvent déclenché par des tempêtes de connexions.

Correction : imposer le pooling de connexions ; plafonner la concurrence ; utiliser le thread pool (Percona Server) ou le multiplexage ProxySQL. Auditer l’application pour les connexions par requête.

3) Symptôme : les replicas prennent du retard, puis le primaire s’effondre

Cause racine : le traffic de lecture revient au primaire quand les replicas prennent du retard ; ou l’application exige des lectures cohérentes et contourne les replicas en cas de lag.

Correction : définir les exigences de cohérence ; implémenter un routage aware du lag ; tuner le parallélisme d’application sur les replicas ; fragmenter les grosses transactions.

4) Symptôme : soudain « Waiting for table metadata lock » partout

Cause racine : changement de schéma en ligne mal fait, ou DDL exécuté sur une table chaude pendant le pic.

Correction : tuer le DDL ; utiliser des outils d’online schema change avec throttling ; planifier les migrations ; interdire le DDL en période de pic via CI/CD.

5) Symptôme : beaucoup de temp tables disque, I/O élevé, et requêtes « qui devraient être indexées »

Cause racine : indexes composites manquants pour ORDER BY/GROUP BY, ou requêtes renvoyant trop de lignes ; les temp tables déversent sur disque sous forte concurrence.

Correction : ajouter les bons indexes ; réécrire les requêtes pour limiter tôt ; envisager des indexes couvrants ; éviter SELECT * quand seules quelques colonnes sont nécessaires.

6) Symptôme : « Lock wait timeout exceeded » durant les pics sur une table unique

Cause racine : ligne chaude ou feuille d’index secondaire chaude, souvent due à des compteurs, flags d’état ou mises à jour « last_seen ».

Correction : refondre le hotspot : modèles append‑only, compteurs partitionnés, batching, ou déplacer ce compteur mutable vers un système séparé avec réconciliation explicite.

7) Symptôme : activer l’instrumentation empire le pic

Cause racine : activation de trop de consumers Performance Schema ou loggings lourds en période de pointe.

Correction : préconfigurer des digests légers et des résumés de waits. Pendant les incidents, privilégiez l’échantillonnage et les tables de synthèse. Collectez les logs hors hôte.

Trois mini‑histoires d’entreprise issues des mines de pics

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

L’entreprise avait un plan « simple » : passer d’un ancien MySQL à une version plus récente, garder le schéma et c’est tout. La charge était principalement en lecture. Ils ont supposé que les pics seraient aussi en lecture. Ils s’étaient trompés de la manière la plus ennuyeuse possible.

Un lancement produit a créé une rafale d’écritures provenant d’événements de tracking. Chaque requête écrivait une ligne et mettait à jour un agrégat par utilisateur. La mise à jour d’agrégat était « minuscule », donc personne ne s’en souciait. Un jour calme, c’était OK. Le jour du lancement, la table d’agrégats est devenue l’objet le plus chaud du bâtiment.

Quand la latence a commencé à grimper, l’équipe a mis l’appli à l’échelle horizontalement. Ça a augmenté la concurrence, ce qui a augmenté la contention sur les lignes chaudes. Le retard de réplication a monté, les replicas de lecture sont tombés en arrière et davantage de lectures ont frappé le primaire. Le primaire n’a pas « manqué de CPU ». Il a manqué de patience.

La mauvaise hypothèse était que « petites écritures = pas cher ». Les petites écritures sont peu coûteuses jusqu’à ce qu’elles se serialisent. La correction n’était pas une nouvelle instance. C’était de repenser : déplacer les agrégats dans un pipeline asynchrone, utiliser des events append‑only, et reconstruire les agrégats hors bande. Ils ont aussi ajouté un cap de concurrence dans le proxy pour que la base se dégrade sans s’écrouler.

Mini‑histoire 2 : L’optimisation qui a mal tourné

Une équipe a vu des redo log waits pendant un pic et a décidé d’accélérer les commits en relaxant la durabilité sur le primaire. Ils ont changé le paramètre de flush à commit au milieu d’un test de charge de fin de trimestre et ont obtenu un joli graphe de débit. Le SRE qui l’a autorisé regrette encore d’avoir vu le graphe.

Deux semaines plus tard, le sous‑système de stockage a eu un bref hic et l’hôte a redémarré. La base a récupéré, mais certaines transactions « récemment commises » avaient disparu. L’appli n’avait pas d’écritures idempotentes sur tous les chemins. Ils avaient construit un système qui supposait que la base était la source de vérité, puis l’avaient rendu parfois oublieuse.

Ils ont passé les jours suivants à réconcilier paiements et actions utilisateurs à partir des logs applicatifs. Personne n’a apprécié ça. L’incident n’était pas seulement qu’ils avaient perdu des données ; c’était qu’ils avaient perdu confiance. Les clients le remarquent avant même un pic de latence de 200 ms.

L’enseignement a été clair : les changements de durabilité sont des décisions produit. Si vous voulez les assouplir, vous avez besoin de contrôles compensatoires : clés d’idempotence, réconciliation et acceptation claire de RPO/RTO. Sinon, vous « optimisez » votre chemin vers une réunion de conformité.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une autre entreprise exécutait Percona Server sur les primaires et MySQL vanilla sur certains services internes. Leur différenciateur n’était pas le binaire. C’était la discipline : ils avaient un playbook, le pratiquaient et avaient pré‑activé la bonne instrumentation en configuration à faible overhead.

Quand une intégration partenaire est devenue folle et a martelé un endpoint, la base a commencé à montrer des redo waits élevés et un âge de checkpoint croissant. En quelques minutes, l’on‑call a extrait les principaux waits, identifié l’empreinte de requête fautive et l’a corrélée à un feature flag. Ils l’ont désactivé. Ensuite ils ont bridé le partenaire en bordure.

Parce qu’ils avaient une règle en place — pas de grosses transactions et tous les jobs batch chunkés — le système s’est rétabli rapidement. Les replicas ont pris du retard mais sont restés utiles. Pas de tuning héroïque. Pas de surprises de config. Juste des garde‑fous ennuyeux qui ont fonctionné comme prévu.

Le postmortem ressemblait à une liste de courses : serrer les rate limits, ajouter un cache pour cet endpoint, et ajouter une alerte pour la croissance de l’âge du checkpoint. Personne n’a reçu de médaille. Tout le monde a dormi.

Listes de contrôle / plan étape par étape

Renforcement avant le pic (faire avant d’en avoir besoin)

  1. Imposer le pooling de connexions dans chaque service. Auditez avec un canary qui logge le taux de création de connexions.
  2. Définir des max_connections sensés et tester le comportement quand il est atteint. Le comportement correct est « échec contrôlé », pas « DB OOM ».
  3. Activer une observabilité légère : slow query log (seuil raisonnable), Performance Schema digests + résumés de waits, et envoi des métriques OS.
  4. Définir la posture de durabilité : innodb_flush_log_at_trx_commit et sync_binlog ne sont pas des knobs de tuning ; ce sont des politiques de perte de données.
  5. Valider la latence du stockage sous rafales d’écriture. Mesurer fsync et petites écritures aléatoires ; ne pas faire confiance aux chiffres peak du fournisseur.
  6. Chunker tous les jobs batch par défaut : transactions bornées, pause entre les chunks et comportement « stop on replication lag ».
  7. Pratiquer le basculement et le routage de lecture avec awareness du lag. Si les replicas laguent, votre routage doit se dégrader gracieusement.

Pendant un pic (la séquence sûre)

  1. Stabiliser le patient : limiter le débit, désactiver les fonctionnalités non essentielles, arrêter les jobs batch et plafonner la concurrence.
  2. Classifier le goulot : CPU vs I/O vs verrous (Feuille de diagnostic rapide ci‑dessus).
  3. Tuer le pire coupable : la grosse transaction, la requête runaway, le DDL accidentel.
  4. Protéger la réplication : garder les replicas « assez bons » pour servir des lectures ; réduire les patterns de grosses transactions.
  5. Collecter des preuves : capturer les top waits, top digests, snapshots iostat et échantillons de processlist pour le postmortem.

Nettoyage post‑pic (ne pas sauter cette étape)

  1. Transformer les atténuations en politiques : le feature flag, le rate limit, le chunking des batchs, le gate pour DDL.
  2. Corriger la forme des requêtes : indexes, réécriture, réduction des temp tables, réduction des lignes chaudes.
  3. Relancer un drill de pic : le même scénario devrait être ennuyeux la prochaine fois.

FAQ

1) Percona Server est‑il « juste MySQL avec des extras » ?

Pour l’essentiel, oui — même lignée de base, plus des fonctionnalités propres à Percona, des choix de packaging et souvent une instrumentation plus orientée ops. Lors des pics, les « extras » comptent quand ils apportent un contrôle d’admission (thread pool) et une meilleure introspection.

2) Si je suis sur MySQL 8.0, est‑ce que Percona Server m’apporte encore quelque chose ?

Peut‑être. Les améliorations upstream de MySQL 8.0 réduisent l’écart. La décision se résume souvent à : avez‑vous besoin de la disponibilité du thread pool de Percona, de plugins spécifiques et de defaults opérationnels — et faites‑vous confiance à votre pipeline de mise à jour pour cette distribution ?

3) Quel pattern de pic favorise le plus Percona Server ?

Les tempêtes de connexions et l’OLTP à haute concurrence où le comportement du thread pool empêche l’effondrement du scheduler CPU. Si vos pics sont surtout I/O‑bound dus aux écritures, l’avantage porte davantage sur l’observabilité et l’ergonomie du tuning que sur le débit brut.

4) Un thread pool réduira‑t‑il ma latence ?

Il augmente souvent légèrement la latence moyenne tout en réduisant drastiquement la latence de queue et en évitant l’effondrement total. Pensez‑y comme « moins de chaos par seconde », pas comme « des requêtes plus rapides ».

5) Devrais‑je changer les paramètres de durabilité pour survivre aux pics ?

Seulement si le business accepte explicitement le risque et que vous avez des contrôles compensatoires. Sinon, corrigez le pattern d’écriture, la latence stockage et la concurrence. La durabilité est une exigence produit déguisée en clé de configuration.

6) Pourquoi la pire latence arrive‑t‑elle après la fin du pic ?

Parce qu’InnoDB doit encore flush les pages modifiées et avancer les checkpoints. Le pic crée une dette ; la tempête de flush est le collecteur de dette.

7) Comment distinguer si le lag de réplication est « backlog normal » ou « bloqué » ?

Si le lag diminue de façon régulière une fois le débit d’écriture réduit, c’est du backlog. S’il plafonne, inspectez l’état de l’applier pour des attentes de dépendance de commit, des lock waits ou des erreurs ; cherchez de grosses transactions et des événements MDL.

8) De meilleurs indexes peuvent‑ils résoudre les problèmes de pics ?

Parfois, oui — surtout pour les inondations de lecture et les temp tables disque. Mais les indexes ne résoudront pas les tempêtes de connexions, les limites de fsync du redo log ou la contention d’écriture sur des lignes chaudes. Diagnostiquez la classe de goulot d’abord.

9) Quel est le « quick win » le plus sûr pour réduire le risque de pic ?

Le pooling de connexions plus un cap de concurrence (proxy ou appli). C’est ennuyeux, mesurable et ne joue pas avec l’exactitude.

10) Dois‑je exécuter une instrumentation plus lourde seulement sur les replicas ?

Souvent un bon compromis. Gardez les primaires avec une instrumentation légère ; exécutez un tracing statement plus profond ou des consumers Performance Schema plus lourds sur un replica qui reflète suffisamment la charge pour être utile.

Prochaines étapes réalisables cette semaine

  1. Faire un drill de pic en staging : simuler une montée en connexion 10× et une rafale d’écriture 5×. Capturer vmstat/iostat et les top waits du Performance Schema.
  2. Décider du contrôle d’admission : si vous pouvez utiliser le thread pool Percona (ou un proxy), mettez‑le en place ; sinon imposez le pooling et plafonnez fortement les connexions.
  3. Auditer les grosses transactions : trouver et chunker les jobs batch ; définir des garde‑fous pour qu’un « UPDATE everything » ne parte pas sans surveillance.
  4. Mesurer le comportement des checkpoints pendant les tests de charge : vérifier la croissance des pages modifiées et les patterns d’attente redo ; ajuster les hints de flushing pour correspondre au stockage réel.
  5. Rédiger votre playbook on‑call en utilisant la séquence de diagnostic rapide et les commandes ci‑dessus. Pratiquez‑le une fois quand personne ne panique.

Si vous hésitez entre MySQL et Percona Server spécifiquement pour les performances en pic, choisissez celui qui vous apporte un meilleur contrôle d’admission et une vérité plus rapide pendant les incidents dans votre environnement. Les benchmarks ne vous appellent pas. Les pics oui.

← Précédent
Avant NVIDIA : ce que « graphismes » signifiait quand la 3D était un luxe
Suivant →
Proxmox vs ESXi pour ZFS : choisir le bon chemin contrôleur disque (pass-through HBA vs disques virtuels)

Laisser un commentaire