optimisation core web vitals 7 min

Réplication MySQL master-master : le piège TTFB qui tue ton LCP

La réplication master-master promet haute disponibilité et performance, mais elle peut faire exploser ton TTFB et plomber tes Core Web Vitals. Retour sur un piège d'infrastructure.

Par Julien Morel
Partager

On te dira que la réplication master-master MySQL améliore la disponibilité et double la capacité d’écriture. Sur le papier, deux serveurs qui acceptent des INSERT simultanés, c’est la promesse d’une infra qui survit à une panne sans read-only. Dans la pratique, j’ai vu un TTFB passer de 90 ms à 620 ms en quelques heures, simplement parce qu’un clean-up de sessions écrivait en boucle sur les deux maîtres sans log de contrôle.

Ce n’était pas une panne réseau et ce n’était pas une famine CPU. C’était un piège d’architecture que la Search Console traduit par une hausse des « mauvaises URL » dans votre rapport Core Web Vitals. Et le pire, c’est que l’équipe croyait avoir gagné en résilience.

Pourquoi le master-master n’est pas un load balancer

Deux nœuds MySQL paramétrés en bi-maître ne se partagent pas la charge comme le ferait un pool de connexions en lecture derrière un proxy SQL. Chaque nœud est indépendant et exécute les écritures qu’il reçoit, puis les expédie à l’autre via un binlog. Si votre couche applicative envoie la moitié du trafic sur le nœud A et l’autre moitié sur le nœud B sans logique de partitionnement, vous créez un système distribué non déterministe.

Le moteur de réplication ne négocie pas l’ordre des transactions concurrentes. Il applique les événements binaires dans l’ordre où ils arrivent. Résultat : un UPDATE exécuté localement sur A peut entrer en collision avec un DELETE arrivé de B, avec un décalage de quelques millisecondes. Les conflits de clés primaires ne sont pas une exception, c’est l’état normal d’une topologie mal pensée.

C’est là qu’on entre dans la zone où la base de données devient une boîte noire pour les Core Web Vitals. Ce n’est pas MySQL qui va ralentir par magie, c’est la couche applicative qui va multiplier les tentatives, les deadlocks, et les temps d’attente, parce qu’elle n’a pas été conçue pour gérer un état partagé sans coordinateur. Le TTFB se met à grimper, le LCP suit.

Le TTFB ne ment jamais : quand la réplication tue le temps de réponse

J’ai extrait les logs d’un side-project e-commerce qui tournait sur un bi-maître actif-actif depuis trois semaines. Le TTFB médian était à 230 ms le matin et à 1 100 ms en début d’après-midi, pile au moment où les écritures de commandes se chevauchaient avec les synchronisations de stock. Le motif était reproductible et corrélé à la variable Seconds_Behind_Master qui oscillait entre 0 et 4 secondes sur l’un des nœuds.

Ce delta de quatre secondes n’a rien d’anodin. Quand votre code lit un stock encore en cours de propagation, il affiche une disponibilité erronée, ce qui déclenche des corrections d’office, qui génèrent de nouvelles écritures en conflit. On n’est plus dans une latence acceptable pour Google, qui mesure le TTFB percentile 75. En pratique, cela signifie qu’un quart de vos utilisateurs voient le serveur bégayer, avant même d’avoir reçu le premier octet HTML.

La documentation officielle de MySQL le dit d’ailleurs sans détour : le master-master n’est pas recommandé quand les écritures sont fréquentes et concurrentes sur les deux nœuds. Ce n’est pas une opinion d’administrateur système, c’est écrit dans la matrice de décision des topologies. Pourtant, chaque année, je retombe sur des déploiements qui traitent le bi-maître comme un cache d’écriture local.

C’est un cas d’école de ce qu’on mesure sur le terrain quand on relie l’infrastructure aux signaux de performance réels : le serveur répond, la pile technique tient debout, mais les Core Web Vitals se cassent la figure sans crash apparent. L’absence de panne franche vous endort, la Search Console vous réveille.

📌 À retenir : un Seconds_Behind_Master qui dépasse une seconde sur un trafic transactionnel doit être traité comme un incident de performance, pas comme une métrique de confort.

Configuration minimale pour ne pas exploser en vol

Si vous devez absolument opérer en master-master, il y a trois paramètres que tu actives avant d’envoyer la première requête, pas après le premier week-end de garde.

D’abord, auto_increment_increment et auto_increment_offset. Sans ça, chaque INSERT sur une table auto-incrémentée va produire le même ID sur les deux nœuds, et la réplication va casser au premier conflit. Règle de base : un offset différent par nœud, un incrément supérieur au nombre de maîtres.

Ensuite, sync_binlog=1 et innodb_flush_log_at_trx_commit=1. Oui, ça coûte en écriture disque, mais cela garantit que le binlog ne prend pas de retard et que les transactions sont vraiment persistées. Dans une topologie bi-maître, un binlog différé de 500 ms, c’est une fenêtre de divergence qui transforme un rollback en cauchemar.

Enfin, une boucle de réplication explicite avec replicate-do-db ou replicate-wild-do-table. Tu laisses le serveur ignorer ses propres écritures quand elles lui reviennent par l’autre nœud. Si cette règle n’est pas en place, chaque écriture fait un aller-retour infini, et tu te retrouves avec des processlist qui ressemblent à une boucle d’auto-joins.

C’est à ce stade que le lien avec les Core Web Vitals devient tangible. Limiter le périmètre de réplication et garantir la synchronisation des logs, c’est exactement le même réflexe que réduire le nombre de ressources bloquantes dans le <head> d’une page. Dans les deux cas, tu maîtrises le chemin critique avant qu’il ne s’emballe.

Quand le master-master devient un actif pour les Core Web Vitals

Il existe une niche où le bi-maître fait sens pour le temps de réponse : les lectures locales sur une base à faible conflit d’écriture. Par exemple, une table de configuration, une liste de pays, des traductions. Si vous écrivez très peu, mais que vous lisez beaucoup depuis des datacenters éloignés, un maître local qui accepte les écritures admin et réplique vers l’autre peut réduire le TTFB de 40 à 80 ms rien qu’en supprimant la latence réseau intercontinentale.

C’est une configuration que j’ai croisée chez un SaaS qui déployait son API sur trois régions. Chaque région avait son maître MySQL, et les écritures de configuration étaient dirigées vers un seul nœud, puis propagées. Les lectures servies localement ne traversaient plus le tunnel réseau. Résultat : un TTFB stable à 75 ms pour les endpoints de landing, contre 180 ms avant.

L’astuce tient à une règle que l’équipe a écrite noir sur blanc : jamais d’écriture concurrente sur les mêmes lignes depuis deux régions. C’est un choix d’architecture qui transforme le bi-maître en maître-esclave déguisé, avec le confort d’un failover semi-automatique. La Search Console n’y voit que du feu, parce que la métrique utilisateur ne varie pas en fonction de la topologie interne.

Si vous ne pouvez pas garantir cette contrainte applicative, le bi-maître ne sert pas à grand-chose. Mieux vaut un proxy de lecture type ProxySQL et un maître unique avec réplication asynchrone, quitte à accepter un switch manuel en cas de panne. La performance perçue ne tient pas toujours à la disponibilité théorique.

💡 Conseil : mesure ton TTFP (Time To First Paint) et ton LCP par région géographique avant de mutliplier les nœuds maîtres. Le gain potentiel doit être validé sur une métrique utilisateur, pas sur un schéma d’infrastructure.

Le coût caché de la résolution de conflits

On a tendance à sous-estimer la charge processeur et disque que génère la gestion de conflits dans une configuration bi-maître. Chaque fois que MySQL détecte une erreur de clé dupliquée lors de l’application d’un événement de réplication, il stoppe le thread SQL. Derrière, un humain doit intervenir, ou un script maison saute l’événement à coups de SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1.

Cette intervention manuelle a un coût direct sur le TTFB, même après résolution. Le rattrapage du retard accumulé pendant l’arrêt du thread SQL peut saturer le disque et ralentir toutes les lectures en cours. J’ai chronométré un rattrapage de 12 Go de binlogs sur un serveur de base e-commerce : 14 minutes de latence dégradée, avec un TTFB qui a grimpé à 2 secondes pour l’ensemble des utilisateurs connectés.

Pire, la résolution de conflits pousse souvent les équipes à désactiver temporairement la réplication sur un nœud, ce qui crée une divergence silencieuse. Quelques heures plus tard, une requête de lecture rapporte des données que le nœud maître opposé ne reconnaît plus. Le front-end se met à afficher un état incohérent, ce qui déclenche des erreurs JavaScript en cascade et, dans certains cas, une incapacité à hydrater la page correctement.

C’est une des raisons pour lesquelles je reste méfiant quand on me présente le master-master comme une brique d’infrastructure « haute disponibilité ». La disponibilité se mesure à la capacité de servir une réponse cohérente en moins de 400 ms, pas à la simple présence d’un deuxième serveur allumé.

La leçon d’un crash à 3h du matin

Un serveur a perdu sa partition réseau pendant une sauvegarde nocturne.

L’autre a continué de prendre les écritures. Au retour du premier, le binlog avait enregistré des transactions que le second n’avait pas encore reçues. Résultat : un conflit de boucle que personne n’avait anticipé. Il a fallu quatre heures pour resynchroniser les deux nœuds. Pendant ce temps, le site servait des pages avec des prix vieux de six heures.

Ce n’était pas un bug MySQL, c’était un bug d’architecture.

Comment mesurer l’impact de ta réplication sur les métriques terrain

Relier une topologie de base de données aux Core Web Vitals demande une instrumentation qui va au-delà de la simple mesure du ping SQL. Voici ce que je mets en place quand je traque l’effet d’une réplication sur le temps de chargement.

J’ajoute un header HTTP X-DB-Replication-Lag dans la réponse de l’API, alimenté par une sonde qui interroge SHOW SLAVE STATUS toutes les 5 secondes et stocke la valeur en mémoire partagée. Ce header est ensuite capturé dans les logs CDN et corrélé avec les données CrUX via un pipeline BigQuery.

Cela permet de tracer des graphiques précis : quand le lag passe au-dessus de 800 ms, le TTFB médian grimpe de 250 ms en moyenne. Ce n’est pas une estimation, c’est une mesure que tu peux reproduire avec les métriques de ton propre domaine. C’est aussi le genre d’analyse qui t’évite de jouer au devin quand le pic de LCP arrive en réunion trimestrielle.

Ensuite, je branche une alerte Prometheus sur le ratio seconds_behind_master / p75_ttfb. Si le ratio dépasse 0.5, c’est que le retard de réplication représente plus de la moitié du temps de réponse serveur. Autrement dit, tu es en train de payer deux fois : une fois pour maintenir un bi-maître, une fois pour subir sa latence.

Ceux qui utilisent un IDE comme Cursor pour naviguer dans les logs MySQL gagnent un temps fou sur le repérage des requêtes lentes — j’avais comparé les deux approches dans mon test de Claude Code vs Cursor IDE, et la capacité à cross-référencer des patterns dans un log binaire change la donne quand tu as une centaine de tables à auditer.

Reste que le vrai déclencheur de migration vers une topologie plus simple, c’est souvent l’observation des sessions réelles. La Search Console ne te dit pas « ta réplication est en retard », elle te dit « ton LCP est trop lent », et c’est à toi de faire le lien entre l’état de ton infra et l’expérience utilisateur.

Questions fréquentes

Est-ce que Galera Cluster règle les problèmes du master-master natif ?

Galera apporte une couche de certification synchrone qui évite les divergences de données, mais il n’élimine pas la latence d’engagement. Si vos écritures sont cross-datacenter, le TTFB peut rester lié au temps de propagation du commit de groupe. La différence, c’est que le conflit devient un rollback applicatif silencieux plutôt qu’une erreur bloquante. Pour les lectures locales, le gain est plus net, à condition d’utiliser les paramètres de causalité de session.

Peut-on mixer un master-master avec un state manager front-end comme Zustand ?

Quand je corrèle l’état de la DB et l’état du front, je ne peux pas m’empêcher de penser aux patterns de state management React avec Zustand. Un store unique avec des mises à jour atomiques évite les conflits de race en mémoire, un peu comme un maître unique évite les conflits de réplication sur disque. Si votre SPA réconcilie des données potentiellement périmées à cause d’une réplication asynchrone, le bug n’est pas dans le front, il est dans le contrat de fraîcheur des données que vous exportez.

Articles similaires

Julien Morel

Julien Morel

Ancien dev front React passé SEO technique après une migration e-commerce qui a fait perdre 60% du trafic organique à son employeur en une nuit (fichier robots.txt oublié en staging). Depuis, il écrit pour que ça n'arrive à personne d'autre et teste sur ses propres side-projects avant de publier quoi que ce soit.

Cet article est publie a titre informatif. Faites vos propres recherches avant toute decision.