On a vu un site e-commerce perdre 40 % de son crawl en une nuit. Pas de pic de charge, pas de bug applicatif visible. Juste une chute brutale du nombre de pages indexées, accompagnée d’un TTFB qui oscillait entre 1,2 s et 4,8 s selon la requête. Le coupable n’était ni le bundler ni un middleware mal ficelé. C’était la réplication MySQL, silencieusement corrompue depuis 72 heures.
Le sujet paraît éloigné des Core Web Vitals, mais le TTFB est le premier kilomètre de chaque métrique de performance côté client. Si votre base répond de travers, Googlebot encaisse des timeouts, vos URL sortent de l’index, et vous pouvez passer une semaine à optimiser du lazy-loading pour rien. On a déjà raconté comment une simple erreur de configuration pouvait plomber un site. Ici, on parle de ce qui arrive quand vos données ne sont plus alignées entre le maître et le réplicat, et comment réparer sans arrêter la boutique.
Pourquoi une désynchro MySQL tape directement dans vos Core Web Vitals
Le LCP et l’INP captent l’attention, mais ils démarrent tous au même endroit : le premier octet. Si votre réplicat de lecture, qui alimente les pages produits et le sitemap dynamique, renvoie des incohérences, l’application multiplie les tentatives de connexion, les requêtes échouent silencieusement et le Time to First Byte décolle. Le navigateur du visiteur attend. Googlebot attend aussi, et son budget crawl fond plus vite qu’un cache non réchauffé.
Ce qui rend le phénomène vicieux, c’est que la réplication peut être fonctionnelle selon SHOW SLAVE STATUS : les logs binaires sont appliqués, pas d’erreur de connectivité. Pourtant, des lignes ont divergé suite à une écriture directe sur le réplicat lors d’une maintenance, à une opération sans SET SQL_LOG_BIN=0, ou à une restauration partielle mal cadrée. Le monitoring classique, focalisé sur les métriques système, passe à côté. Le vôtre vous dit que tout roule. Google, lui, voit des pages molles et retire son robot.
C’est exactement ce qui nous est arrivé. L’équipe infra nous remontait un Threads_connected stable et une latence disque normale. Pendant ce temps, la Search Console affichait une courbe d’indexation qui ressemblait à une pente de ski, et nos sondes de TTFB domiciliées chez différents FAI confirmaient une dégradation. La première règle dans ces situations : ne pas faire confiance à un seul indicateur. Si vous n’avez que Seconds_Behind_Master dans vos dashboards, vous êtes aveugle à la moitié des dérives.
Le symptôme qui nous a trompés : des erreurs 500 aléatoires sur les pages à forte jointure
Le vendredi, les premières alertes remontaient des pages de catégorie avec filtres. En surface, des erreurs 500 intermittentes, impossibles à reproduire en staging. Les logs PHP pointaient un PDOException sur une contrainte d’intégrité, mais la table incriminée semblait saine en production. On a d’abord soupçonné un cache de session corrompu, une race condition dans notre couche d’abstraction, puis un problème de configuration réseau entre l’app et la base.
En fin de matinée, on a sorti le pt-table-checksum de Percona sur la base de production. L’outil découpe la table en morceaux, exécute des sommes de contrôle bit à bit et compare sans poser de verrous bloquants. Résultat : 4300 lignes divergentes dans la table product_variants, précisément celle que notre JOIN à sept branches tentait d’interroger. La cause était une migration de schéma exécutée une semaine plus tôt : le script avait tourné sur le maître, mais une défaillance du relais log avait omis quelques UPDATE sur le réplicat.
La leçon n’est pas seulement technique. Tant que vous ne comparez pas le contenu réel des lignes, un Seconds_Behind_Master à zéro ne garantit rien. La mesure du drift doit devenir aussi routinière que la vérification des certificats SSL, surtout quand vos pages dynamiques dépendent de dizaines de jointures.
Détecter la divergence avant qu’elle ne vous explose en prod
Exécute un CHECKSUM TABLE sur une table de 5 millions de lignes en pleine journée, et tu verras les connexions s’empiler. Pour la détection, on utilise pt-table-checksum parce qu’il travaille par segments, avec un throttle ajustable, et qu’il stocke les résultats dans la table percona.checksums. Une simple lecture suffit pour repérer les écarts.
La commande qui nous a sauvés ressemble à ça :
pt-table-checksum --replicate=percona.checksums --tables=product_variants --max-lag=2 --chunk-size=2000
L’option --max-lag=2 interrompt automatiquement le traitement si le délai de réplication dépasse 2 secondes, ce qui évite de transformer un diagnostic en incident. Une fois le scan terminé, un SELECT sur la table de résultats isole les this_crc <> master_crc. C’est là que tu mesures l’ampleur du problème.
Pour les équipes qui n’ont pas Percona Toolkit sous la main, une alternative consiste à comparer des sommes MD5 sur des plages de id avec des scripts bash et mysql en lecture seule, mais l’approche est plus artisanale et difficile à scénariser en environnement de production. L’investissement dans les bons outils évite les nuits blanches. On en parle d’ailleurs dans notre article sur l’état du state management avec Zustand : quand les données sont corrompues, même le store le plus propre ne peut pas faire de miracle côté front.
La méthode de resynchronisation en conditions réelles
Corriger une désynchro sur un site en production, c’est comme opérer un tendon sans éteindre le patient. La première règle : ne jamais lancer un dump massif suivi d’une réinjection pendant les heures de trafic. On a découpé l’opération en quatre phases.
D’abord, on a identifié les tables froides où les écritures sont rares : les référentiels de catégories, les listes de paramétrage. Sur ces tables, on a exécuté pt-table-sync --execute --sync-to-master après avoir posé un lock léger pendant une fenêtre de faible activité. Le temps d’interruption n’a pas dépassé 30 secondes, et l’impact sur le TTFB était imperceptible.
Ensuite, les tables chaudes à fort trafic, comme product_variants. Impossible de bloquer, même quelques secondes. On a procédé par lots de 1000 lignes, en comparant les checksums de chaque chunk, et en ne corrigeant que les chunks déviants. pt-table-sync supporte cette granularité grâce au paramètre --chunk-size combiné avec --dry-run pour valider l’ampleur des modifications avant exécution. On a espacé chaque lot de 15 secondes pour laisser l’application respirer.
Troisième phase, la plus critique : la table des sessions utilisateurs. On a fait le choix de purger les sessions anciennes et de forcer les utilisateurs actifs à se reconnecter, plutôt que de risquer une désynchro persistante. Une décision assumée, communiquée par une bannière de maintenance sur le front, parce que la cohérence transactionnelle prime sur le confort immédiat.
Enfin, on a rejoué un pt-table-checksum complet pour confirmer l’alignement. Le crawl a mis trois jours à remonter dans la Search Console, preuve que Googlebot a besoin de temps pour digérer un signal amélioré. Mais le TTFB est redescendu à 380 ms en moyenne dès le lendemain matin, et la tendance d’indexation s’est inversée.
Le détail qui tue : pendant toute l’opération, on a surveillé les métriques via un script Python qui interrogeait l’API Percona en temps réel et poussait les données dans un notebook. On aurait pu faire tourner ce suivi depuis n’importe quel IDE, nous on a utilisé Cursor pour éditer les scripts et Claude Code pour itérer sur la logique de contrôle de flux. On a comparé les deux outils dans cet article dédié ; pour ce genre de tâche, la rapidité d’exécution locale a fait la différence.
Ce qu’on a mis en place pour ne plus jamais revivre ça
La détection de drift, on l’a intégrée dans le pipeline de déploiement. Un job pt-table-checksum tourne toutes les six heures, avec une tolérance de 3 % de divergence max sur les tables critiques. Si le seuil est dépassé, le pipeline bloque le déploiement suivant et alerte le canal Slack #db-ops. On a aussi rendu obligatoire le flag read_only sur les réplicats, pour empêcher toute écriture accidentelle même par un DBA pressé.
Un autre levier a été de remonter le TTFB client-side dans notre outil de RUM, en corrélant les mesures avec l’état de la réplication. Dès que la latence côté base dépasse un seuil, une alerte part avant que l’utilisateur ne remarque quoi que ce soit. Ce pilotage par le ressenti utilisateur est plus fiable que des indicateurs internes que personne ne regarde. Si vous voulez creuser la boucle performance/monitoring, notre dossier sur l’optimisation des Core Web Vitals détaille la mise en place de ce type de corrélation.
Questions fréquentes
Est-ce qu’une désynchro MySQL peut vraiment faire disparaître des pages de l’index Google ?
Oui. Quand Googlebot reçoit des erreurs 500 ou des temps de réponse supérieurs à 5 secondes de façon répétée, il suspend l’exploration des URL concernées. Si l’état dégradé persiste, les pages peuvent être temporairement désindexées et mettent des jours à revenir, même une fois le problème corrigé.
Quelle différence entre une panne de réplication et une divergence silencieuse ?
Une panne de réplication se voit dans SHOW SLAVE STATUS avec une erreur explicite et un Slave_IO_Running ou Slave_SQL_Running à No. Une divergence silencieuse, elle, survient quand la réplication est active mais que le contenu des tables diffère. Les logs binaires sont appliqués sans erreur, mais les données ne sont pas identiques. Seuls des outils de checksum par morceaux peuvent le détecter.
Pourquoi ne pas utiliser mysqldump pour resynchroniser rapidement ?
Un mysqldump classique verrouille les tables ou ajoute une charge I/O massive qui dégrade les performances en lecture pendant la durée de l’export. Et si vous importez le dump sur le réplicat, vous écrasez toutes les données, ce qui casse la réplication. Les outils comme pt-table-sync modifient uniquement les lignes divergentes et préservent l’intégrité de la réplication.