MySQL transpire, vos CPU sont saturés et l’équipe produit vient de « lancer une petite fonctionnalité » qui a d’une manière ou d’une autre doublé le trafic. Vous ajoutez des réplicas. Puis vous en ajoutez encore. Puis vous découvrez que le goulot d’étranglement n’était pas les écritures — vos lectures font le travail le plus stupide imaginable, des millions de fois par heure.
Redis ne sauvera pas un schéma cassé ni des requêtes mal écrites. Mais si votre charge consiste principalement en lectures répétitives, objets calculés, consultations de sessions, vérifications de quotas, ou « est-ce que cet utilisateur a la permission X ? », Redis peut mordre une part importante du trafic MySQL. Bien fait, 80 % n’est pas un conte de fées. C’est mardi.
Le mythe : « Redis vs MySQL » n’est pas le bon combat
Quand les gens disent « MySQL vs Redis », ils veulent généralement dire « j’en ai marre que MySQL soit lent, puis-je le remplacer par quelque chose de plus rapide ? ». C’est comme remplacer votre service de comptabilité par une caisse enregistreuse parce que la caisse est plus rapide. La caisse est fantastique. Elle n’est pas non plus un service de comptabilité.
MySQL est une base relationnelle durable avec transactions, contraintes, index secondaires, jointures et des décennies de mémoire opérationnelle. Redis est un serveur de structures de données en mémoire : extrêmement rapide, avec une latence prévisible faible, et excellent pour les données éphémères ou dérivées que vous pouvez reconstruire.
Donc la bonne question n’est pas lequel « gagne ». C’est :
- Quelles données doivent être correctes, durables et interrogeables de manière riche ? Mettez-les dans MySQL.
- Quelles données sont chaudes, répétitives, dérivées, temporaires ou utilisées pour la coordination ? Mettez-les dans Redis.
- D’où vient la charge : coût CPU de l’exécution des requêtes, I/O disque, verrous, allers-retours réseau ou comportement applicatif ?
Redis n’est pas votre base de données principale — pas encore. Dans la plupart des systèmes, il ne devrait pas l’être. Mais Redis peut être votre meilleur multiplicateur de performances, car il change la forme de votre charge : moins de requêtes coûteuses, moins d’allers-retours, moins de contention et moins de pression sur votre buffer pool et vos disques.
Ce à quoi chaque système est réellement bon
MySQL : la source de vérité avec des arêtes vives
MySQL est conçu pour la correction et le stockage à long terme. Il persiste les données sur disque, journalise les changements et fournit une sémantique transactionnelle. Il vous permet aussi de poser des questions compliquées, ce qui explique exactement pourquoi les gens posent accidentellement des questions compliquées 50 000 fois par minute.
Utilisez MySQL pour :
- Données que vous ne pouvez pas perdre : commandes, soldes, permissions, état de compte, événements de facturation.
- Invariants multi-lignes : « une seule souscription active par utilisateur », clés étrangères, unicité.
- Historique des changements auditable (souvent via tables en append-only ou pipelines basés sur le binlog).
- Requêtes nécessitant des jointures ou des scans de plage sur des index secondaires.
MySQL échoue de façons familières : contention sur les verrous, mauvais index, scans énormes, misses du buffer pool et stockage lent. Il échoue aussi socialement : « juste une colonne de plus », « juste une jointure de plus » et « on le mettra en cache plus tard », ce qui veut dire « on appellera le SRE plus tard ».
Redis : l’accélérateur du chemin chaud et la couche de coordination
Redis est rapide parce qu’il est simple là où ça compte : garder les données en mémoire, rendre les opérations peu coûteuses et garder un protocole simple. C’est un endroit brillant pour :
- Mise en cache : objets calculés, fragments rendus, décisions d’autorisation, réponses d’API.
- Sessions et jetons : recherches rapides avec TTL.
- Limitation de débit : compteurs atomiques par clé avec expiration.
- Coordination distribuée : verrous (avec précaution), classements, queues/streams, clés de déduplication.
- Feature flags et snapshots de configuration : petites lectures, très fréquentes.
Redis échoue autrement : pression mémoire, évictions surprises, mauvaise configuration de persistance, accrochages de basculement et pics de latence causés par de grosses clés ou des commandes lentes. De plus, la cause la plus fréquente d’une grosse panne Redis n’est pas Redis lui-même — ce sont les applications qui supposent que « cache » signifie « toujours là ».
Une citation qui mérite d’être tatouée pour quiconque opère les deux : « Tout échoue, tout le temps. »
—Werner Vogels
Une petite blague, parce qu’on l’a bien méritée : Redis, c’est comme l’expresso — excellent pour la productivité, mais si vous basez tout votre régime dessus, vous tremblerez et le regretterez.
Faits intéressants et un peu d’histoire
- Redis est né d’un besoin produit réel : il a été créé par Salvatore Sanfilippo pour résoudre des problèmes de performance et de scalabilité dans un contexte d’analytique web, pas comme exercice académique.
- InnoDB a changé la donne opérationnelle pour MySQL : une fois InnoDB devenu le défaut, la récupération après crash et le comportement transactionnel sont devenus la norme plutôt qu’un « mode sérieux » optionnel.
- La popularité de Redis a explosé avec le stockage de sessions : les premières adoptions massives commençaient souvent par « déplacer les sessions hors de MySQL », car c’est peu risqué et ça réduit immédiatement le churn d’écriture.
- Memcached vs Redis : Memcached a promu une histoire simple de cache uniquement ; Redis a apporté des types de données plus riches et des opérations atomiques, ce qui l’a rendu utile au-delà du simple cache.
- La persistance Redis est arrivée plus tard dans l’esprit : Redis est d’abord en mémoire ; les snapshots RDB et les logs AOF existent, mais la durabilité est un compromis configuré, pas une garantie par défaut.
- La réplication MySQL a façonné les architectures : la possibilité de répartir les lectures sur des réplicas (et plus tard semi-sync et améliorations GTID) a influencé le plan de « scale reads » bien avant que Redis ne se place devant lui.
- L’invalidation de cache reste un problème humain non résolu : la partie la plus dure n’est pas l’algorithme ; c’est la discipline organisationnelle autour de l’endroit où réside la vérité et qui possède les règles d’invalidation.
- Redis a introduit le scripting Lua pour l’atomicité : c’est puissant, mais aussi une façon de créer une « logique applicative cachée » que personne ne déploie en toute sécurité.
Comment Redis réduit la charge MySQL sans vous mentir
La plupart de la charge MySQL est auto-infligée par la répétition
Dans un système web de production typique, une grande partie du trafic de lecture se répète. Le même profil utilisateur. Le même fragment de catalogue produit. Le même ensemble de permissions. La même évaluation de feature flag. Le même widget « qu’y a‑t‑il dans la barre d’en‑tête » qui touche cinq tables parce que quelqu’un voulait que ce soit « flexible ».
MySQL peut gérer beaucoup de lectures, surtout depuis le cache (buffer pool) et avec des index appropriés. Mais il effectue toujours du parsing, du planning, de la gestion des verrous et l’exécution de la logique. Multipliez cela par de nombreuses instances applicatives et vous obtenez une mort par mille SELECTs polis.
Redis aide quand votre charge de lecture est :
- Chaude : les mêmes clés sont demandées encore et encore.
- Dérivable : vous pouvez reconstruire les entrées de cache depuis MySQL, ou tolérer une recomputation occasionnelle.
- Grossièrement agrégée : un hit de cache remplace plusieurs requêtes ou une requête lourde en jointures.
- Sensible à la latence : gagner 10–30 ms compte parce que ça se multiplie sur des appels en aval.
Mais Redis n’aide pas quand vous avez besoin de :
- Analytique ad hoc avec des prédicats de requête flexibles.
- Jointures complexes qui changent à chaque sprint.
- Forte cohérence entre plusieurs entités sans conception soignée.
- Rétention longue avec un stockage peu coûteux par Go.
Le pattern de réduction à 80 % : mettre en cache la frontière coûteuse
Les plus gros gains arrivent quand vous mettez en cache à une frontière qui agrège naturellement le travail. Ne mettez pas en cache des lignes individuelles si votre goulot est une requête touchant 8 tables. Mettez en cache l’objet résultat que votre service a réellement besoin : un contexte utilisateur hydraté, un fragment rendu ou un bundle d’autorisation.
Cela fait deux choses :
- Remplace plusieurs appels MySQL par un seul GET Redis.
- Rend la clé de cache stable et facile à raisonner (pour l’invalidation et le TTL).
Patterns d’écriture : choisissez-en un, n’improvisez pas
- Cache-aside (chargement paresseux) : l’app lit Redis, en cas de miss lit MySQL, puis peuple Redis. Le plus courant ; le plus simple à introduire ; le plus dur à garder cohérent sans discipline.
- Write-through : l’app écrit dans le cache et la DB dans le même flux de requête (souvent DB d’abord, puis cache). Utile pour des lectures prévisibles, mais il faut gérer les échecs partiels.
- Write-behind : l’app écrit dans Redis et vide de manière asynchrone vers la DB. Rapide, mais risqué ; vous construisez délibérément un système de base de données.
Pour la plupart des équipes : commencez par cache-aside, ajoutez une invalidation explicite sur les écritures et appliquez des TTLs pour limiter le rayon d’impact des bugs.
Patrons de cache et de coordination qui fonctionnent en production
1) Cache-aside avec invalidation explicite
Lors des lectures :
- GET key depuis Redis
- si hit : le retourner
- si miss : interroger MySQL, sérialiser, SETEX dans Redis, retourner
Lors des écritures :
- Committez d’abord dans MySQL.
- Puis supprimez ou mettez à jour les clés cache qui dépendent des lignes modifiées.
- Utilisez des TTLs de toute façon. Les TTLs ne sont pas de la consistance, ce sont du contrôle des dégâts.
Si l’invalidation semble « trop difficile », ce n’est pas une raison pour éviter le cache. C’est une raison pour définir la propriété : quel service possède l’espace de noms des clés cache, et quelles écritures déclenchent l’invalidation.
2) Prévenir la ruée : single-flight et soft TTL
Le stampede de cache survient quand une clé chaude expire et que 5 000 requêtes se précipitent vers MySQL en même temps. La base de données ne devient pas « plus correcte » sous pression ; elle devient juste plus lente, puis elle meurt.
Réparez-le avec :
- Coalescence de requêtes (single-flight) : une requête reconstruit la clé pendant que les autres attendent.
- Soft TTL : servir des données légèrement périmées pendant une courte fenêtre pendant qu’un rafraîchissement en arrière-plan a lieu.
- Jitter : randomiser les TTLs pour éviter des expirations synchronisées.
3) Cache négatif (mettre en cache le « non trouvé »)
Si des bots ou des clients cassés continuent de demander des IDs inexistants, MySQL fera cette recherche indéfiniment. Mettez en cache les 404 avec des TTLs courts (secondes à minutes). C’est une assurance bon marché.
4) Utilisez Redis pour des « décisions », pas pour la « vérité »
De bonnes clés Redis représentent des décisions : « l’utilisateur X est limité », « la session Y est valide jusqu’à T », « le feature flag F pour la cohorte C ». Ces éléments sont temporellement bornés et dérivés.
De mauvaises clés Redis représentent la vérité : « solde du compte », « quantité en stock », « la seule copie d’un token de réinitialisation de mot de passe sans stratégie de persistance ». Vous pouvez le faire, mais vous êtes en train d’opérer une base de données en prétendant que ce n’en est pas une.
5) Compteurs et limitation de débit : atomique et ennuyeux
La limitation de débit est le terrain de prédilection de Redis parce que INCR et EXPIRE sont bon marché et atomiques. Utilisez une fenêtre fixe si nécessaire, une fenêtre glissante si vous êtes fancy, mais gardez l’implémentation assez simple pour que quelqu’un puisse la déboguer à 3 h du matin.
6) Queues et streams : sachez ce que vous achetez
Les lists, pub/sub et streams Redis peuvent implémenter des queues de travail. Ils sont excellents pour le fanout à faible latence et les pipelines légers. Mais si vous avez besoin d’un traitement exactement-once, d’une rétention durable, du rebalancement de groupes de consommateurs et de garanties multi-DC, vous n’achetez plus Redis — vous cherchez un système de log.
Durabilité de Redis : ce qu’il fait, ce qu’il ne fait pas
La persistance Redis est réelle, mais ce n’est pas le même contrat qu’une base relationnelle. Vous devez décider votre modèle de défaillance d’abord, puis configurer la persistance en conséquence.
RDB snapshots
RDB écrit des snapshots point-in-time sur disque. C’est compact et rapide pour les redémarrages, mais vous pouvez perdre des données depuis le dernier snapshot. C’est acceptable pour les caches ; discutable pour les queues ; terrifiant pour les registres comptables.
AOF (Append Only File)
AOF journalise chaque écriture. Avec des politiques fsync, vous pouvez réduire la fenêtre de perte de données au prix d’un surcoût d’écriture. La réécriture AOF compresse périodiquement le log. C’est plus proche de la durabilité, mais ce n’est toujours pas une base relationnelle avec contraintes et garanties transactionnelles entre lignes.
Replication et basculement
La réplication Redis est asynchrone dans la plupart des configurations courantes. Un failover peut faire perdre des écritures récentes. Si vous mettez la « vérité » dans Redis, vous devez accepter que cette vérité puisse être annulée par un failover. Si cette phrase vous donne des sueurs, tant mieux — laissez la vérité dans MySQL.
Deuxième petite blague (et dernière) : appeler Redis votre base de données parce que vous avez activé AOF, c’est comme appeler une tente une maison parce que vous avez acheté un bon sac de couchage.
Playbook de diagnostic rapide
Si votre système est lent et que vous soupçonnez la « base de données », vous pouvez perdre des jours à deviner. Ne le faites pas. Exécutez un triage rapide qui vous dit où passer votre prochaine heure.
Première étape : MySQL est-il surchargé ou simplement en attente ?
- Vérifiez les connexions MySQL et threads en cours : si threads_running est élevé et stable, MySQL est occupé ; s’il est bas mais que les requêtes sont lentes, vous attendez peut-être de l’I/O ou des verrous.
- Vérifiez les empreintes des requêtes principales : un mauvais pattern de requête peut dominer le CPU même si chaque appel prend « seulement 30 ms ».
- Vérifiez la latence disque : si le stockage est lent, la mise en cache ne réparera pas les écritures ou les misses du buffer pool.
Deuxième étape : Redis aide-t-il réellement ou cache-t-il la douleur ?
- Taux de hit Redis : un faible hit rate signifie que vous payez les coûts réseau et de sérialisation sans bénéfice.
- Pics de latence Redis : de grosses clés, des commandes lentes ou des fsync de persistance peuvent causer des latences en queue qui se répercutent sur MySQL (via des retries et timeouts).
- Evictions : les évictions sont le signe que votre politique de cache prend des décisions à votre place, généralement mauvaises.
Troisième étape : l’application est-elle la vraie coupable ?
- Concurrence : un déploiement qui double le nombre de workers peut doubler la charge DB même si le trafic est stable.
- Requêtes N+1 : un endpoint peut silencieusement exécuter des centaines de requêtes par requête.
- Tempêtes de retry : des timeouts mal configurés peuvent multiplier la charge quand les choses sont déjà lentes.
Tâches pratiques avec commandes : mesurer, décider, changer
Ci‑dessous se trouvent des tâches pratiques que vous pouvez exécuter sur des systèmes réels. Chacune inclut une commande, ce que signifie la sortie et la décision à prendre ensuite. Pas de magie, juste des preuves.
Task 1: Identify top MySQL statements by total time
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 5\G"
...output...
Ce que signifie la sortie : DIGEST_TEXT montre la forme normalisée de la requête ; COUNT_STAR est le nombre d’exécutions ; total_s est le temps cumulé passé.
Décision : Si un digest domine le temps total, ciblez-le pour mise en cache ou indexation avant d’ajouter du matériel. Si plusieurs digests sont à égalité, cherchez des problèmes systémiques (timeouts, N+1).
Task 2: Confirm MySQL is CPU-bound or waiting on I/O
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
...output...
Ce que signifie la sortie : Threads_running élevé implique une charge active. Buffer_pool_reads (lectures physiques) vs read_requests (lectures logiques) donne une intuition du hit rate du cache.
Décision : Si les lectures physiques augmentent et que la latence stockage est élevée, réparez l’I/O ou augmentez le buffer pool avant de tout miser sur Redis.
Task 3: Find lock contention in MySQL
cr0x@server:~$ mysql -e "SELECT * FROM sys.innodb_lock_waits ORDER BY wait_age_secs DESC LIMIT 10\G"
...output...
Ce que signifie la sortie : Montre les transactions bloquantes et en attente, et depuis combien de temps elles sont coincées.
Décision : Si les waits de lock sont courants, la mise en cache des lectures peut aider mais ne résoudra pas la contention d’écriture. Corrigez la portée des transactions, les index ou les problèmes d’isolation.
Task 4: Inspect the MySQL slow query log quickly
cr0x@server:~$ sudo pt-query-digest /var/log/mysql/mysql-slow.log --limit 10
...output...
Ce que signifie la sortie : Classes de requêtes principales par temps total, temps moyen, lignes examinées et variance.
Décision : Si rows examined est énorme pour des recherches simples, vous avez besoin d’index ou de corrections de requêtes. Si les requêtes sont prévisibles et répétitives, elles sont des candidates idéales pour le cache.
Task 5: Verify MySQL index usage for a hot query
cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 AND status='open'\G"
...output...
Ce que signifie la sortie : Regardez type (ALL est mauvais), key utilisée, rows estimées et extra (Using filesort / temporary peut nuire).
Décision : Si le plan est un scan ou utilise le mauvais index, corrigez le schéma/la requête d’abord. Redis ne doit pas être un pansement pour des scans évitables.
Task 6: Check Redis hit rate and keyspace behavior
cr0x@server:~$ redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses|instantaneous_ops_per_sec'
...output...
Ce que signifie la sortie : Hits et misses vous permettent de calculer le ratio de hit. ops/sec indique le volume de trafic.
Décision : Si le ratio de hit est faible, votre stratégie de cache est mauvaise : clés trop granulaires, TTL trop court ou vous mettez en cache les mauvais objets.
Task 7: Detect Redis evictions (the silent correctness tax)
cr0x@server:~$ redis-cli INFO stats | egrep 'evicted_keys|expired_keys'
...output...
Ce que signifie la sortie : evicted_keys > 0 signifie que Redis supprime des clés sous pression mémoire. expired_keys est normal pour l’utilisation des TTLs.
Décision : Si des évictions surviennent, augmentez maxmemory, changez la politique d’éviction, réduisez la taille des valeurs ou reconsidérez ce que vous mettez en cache. Considérez que l’éviction casse les hypothèses jusqu’à preuve du contraire.
Task 8: Check Redis memory fragmentation and allocator behavior
cr0x@server:~$ redis-cli INFO memory | egrep 'used_memory_human|used_memory_rss_human|mem_fragmentation_ratio|maxmemory_human'
...output...
Ce que signifie la sortie : RSS beaucoup plus grand que used_memory implique fragmentation ou overhead de l’allocateur ; maxmemory vous indique le plafond.
Décision : Une forte fragmentation peut nécessiter du tuning (comportement jemalloc, defrag actif) ou de remodeler les workloads (éviter un turnover massif de clés de taille similaire).
Task 9: Find Redis slow commands
cr0x@server:~$ redis-cli SLOWLOG GET 10
...output...
Ce que signifie la sortie : Liste des exécutions de commandes lentes avec durée et arguments (souvent tronqués).
Décision : Si vous voyez KEYS, de gros HGETALL, de larges requêtes de plage, ou des scripts Lua prenant des millisecondes, vous avez fabriqué une grenade de latence. Remplacez par des patterns SCAN, des valeurs plus petites ou des clés pré-calculées.
Task 10: Validate Redis persistence settings (AOF/RDB)
cr0x@server:~$ redis-cli CONFIG GET appendonly save appendfsync
...output...
Ce que signifie la sortie : appendonly on/off, save schedules pour RDB, appendfsync policy (always/everysec/no).
Décision : Pour du pur cache, vous pouvez désactiver la persistance pour réduire l’overhead. Pour des données de coordination (verrous, limites de débit), la persistance peut être optionnelle mais comprenez le comportement au redémarrage. Pour la « vérité », reconsidérez toute la conception.
Task 11: Measure Redis latency distribution under load
cr0x@server:~$ redis-cli --latency-history -i 1
...output...
Ce que signifie la sortie : Rapporte min/avg/max de latence au fil du temps. Les pics se corrèlent avec fork pour RDB, fsync AOF, ou de grosses commandes.
Décision : Si les pics de latence max s’alignent avec des événements de persistance, ajustez la persistance, passez à un stockage plus rapide ou isolez Redis sur des nœuds dédiés.
Task 12: Check Linux pressure signals that affect both MySQL and Redis
cr0x@server:~$ vmstat 1 5
...output...
Ce que signifie la sortie : si/so indiquent du swapping (mauvais) ; wa montre l’I/O wait ; r montre les threads runnable ; free/buff/cache indique la posture mémoire.
Décision : Si le swapping est non‑nul, arrêtez et corrigez la mémoire. Redis plus swap est la façon de transformer des microsecondes en minutes.
Task 13: Confirm disk latency for MySQL data volume
cr0x@server:~$ iostat -x 1 3
...output...
Ce que signifie la sortie : r_await/w_await montrent la latence lecture/écriture ; %util montre la saturation.
Décision : Si la latence est élevée et que l’util est proche de 100 %, vous êtes lié par l’I/O. La mise en cache des lectures peut aider, mais si les écritures sont le problème, vous avez besoin de stockage, de batch ou de changements de schéma.
Task 14: Watch MySQL connection churn (often a hidden tax)
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Connections'; SHOW GLOBAL STATUS LIKE 'Aborted_connects'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
...output...
Ce que signifie la sortie : Des Connections qui augmentent rapidement suggèrent l’absence de pooling ; Aborted_connects implique des problèmes d’auth/réseau.
Décision : Corrigez le pooling et les timeouts avant d’ajouter Redis. Sinon vous construirez juste un moyen plus rapide de surcharger MySQL.
Task 15: Validate Redis key distribution and hot keys
cr0x@server:~$ redis-cli --hotkeys
...output...
Ce que signifie la sortie : Estime les clés fréquemment accédées (échantillonnage best-effort).
Décision : Si une clé est extrêmement chaude, shardez-la (clés par utilisateur), ajoutez un cache local ou redesign pour éviter des compteurs globaux qui sérialisent le trafic.
Trois mini-récits d’entreprise (anonymisés, assez réels)
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
L’entreprise avait un backend MySQL monolithique et a introduit Redis « pour les sessions ». Ça a marché. Ils ont donc étendu Redis pour stocker les droits d’accès utilisateur — quelles fonctionnalités un utilisateur avait payées. Le raisonnement était simple : les droits sont lus constamment, les écritures sont rares, Redis est rapide, et ils ont activé AOF, donc c’était « assez durable ».
Puis un basculement a eu lieu pendant un événement de noisy neighbor sur l’hôte de virtualisation. Redis a promu un replica légèrement en retard. Une petite fenêtre de changements de droits — upgrades et downgrades — a disparu. Certains utilisateurs ont perdu l’accès ; quelques-uns ont eu accès alors qu’ils ne le devraient pas. Les tickets support ont commencé. Les finances ont commencé à poser des questions avec le ton que les gens utilisent quand ils essaient de rester polis.
Au début, l’équipe a chassé une « corruption de données ». Redis n’était pas corrompu. Il a fait exactement ce qu’il promettait : rapide, en mémoire, réplication asynchrone à moins d’être configuré autrement, et une persistance qui a quand même des fenêtres de perte.
Le problème racine était l’hypothèse que « AOF signifie base de données ». Ils ont corrigé cela en remettant les droits dans MySQL comme source de vérité, puis en mettant en cache le bundle d’entitlements calculé dans Redis avec des TTLs courts et une invalidation explicite sur les écritures d’entitlements. Ils ont aussi ajouté un chemin de vérification : si Redis dit qu’un utilisateur a un droit mais que MySQL n’est pas d’accord, MySQL gagne et Redis est corrigé.
Après cela, les basculements sont redevenus ennuyeux. C’est l’objectif.
Mini-récit 2 : L’optimisation qui a mal tourné
Une autre équipe était fière de leur taux de hit cache. Ils avaient un endpoint populaire : « get user dashboard ». Ils mettaient en cache toute la réponse JSON dans Redis pendant 30 minutes. La charge MySQL a chuté fortement. Les graphiques ressemblaient à une diapositive de succès.
Deux mois plus tard, le produit a livré un widget « notifications en direct » dans ce dashboard. Il devait se mettre à jour immédiatement quand une notification était lue. À la place, les utilisateurs voyaient d’anciennes notifications pendant jusqu’à 30 minutes. L’équipe a essayé d’invalider le cache sur les lectures de notification, mais le cache du dashboard dépendait de plusieurs tables sous-jacentes et types d’événements. L’invalidation est devenue une toile de « si X change, supprimez les clés A, B, C, sauf quand… ». Les bugs ont suivi. Puis incident : un déploiement a accidentellement stoppé l’invalidation pour une des sources de données et des dashboards périmés se sont répandus comme une rumeur.
Le postmortem a été inconfortable parce que l’« optimisation » de cache avait augmenté la surface de correctitude. Ils avaient mis en cache trop haut dans la pile sans un modèle clair de propriété pour l’invalidation. La charge MySQL était plus faible, mais la confiance des utilisateurs aussi.
La correction a été de scinder le dashboard en composants plus petits et cacheables avec des TTLs et des triggers d’invalidation différents : bloc profil (rarement changeant), bloc recommendation (basé sur TTL), notifications (non mises en cache, ou mises en cache avec TTL très court et invalidation forte). Ils ont aussi introduit des clés versionnées par changement d’état utilisateur, donc les nouvelles écritures faisaient naturellement basculer le trafic vers de nouvelles clés sans nécessité de supprimer immédiatement les anciennes.
La charge MySQL est remontée légèrement depuis la baseline « héros ». Les incidents ont beaucoup diminué. C’est un meilleur compromis.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une équipe plateforme mature traitait Redis comme une dépendance avec son propre SLO, dashboards et capacity planning. Pas excitant. Efficace. Ils faisaient des tests de charge routiniers incluant les modes de défaillance Redis : cold cache, cache partiel et redémarrages Redis pendant un pic.
Un après-midi, une mise à jour du kernel a déclenché une boucle de redémarrage sur un sous-ensemble de nœuds du cluster Redis. La disponibilité Redis a dégradé. L’application n’est pas tombée. La latence a augmenté, mais elle est restée dans le SLO utilisateur pour la plupart des endpoints.
Pourquoi ? Deux choix ennuyeux. D’abord, l’app avait des timeouts stricts pour Redis (chiffres en millisecondes) et basculait sur MySQL en cas de miss ou d’erreur pour un petit sous-ensemble d’endpoints. Ensuite, ils appliquaient des coupe-circuits : si les erreurs Redis dépassent un seuil, l’app arrête d’essayer Redis pendant une courte fenêtre de cooldown, empêchant les tempêtes de retry.
MySQL a vu une augmentation du trafic de lecture, mais ils avaient de la marge parce que la stratégie de cache ciblait les lectures à fort churn et ils avaient déjà corrigé les pires requêtes. Le mode « cache down » était dégradé, pas catastrophique.
L’équipe a pu réparer le cluster sans incident public. Pas d’héroïsme. Juste des systèmes qui se comportent comme des adultes.
Erreurs fréquentes : symptômes → cause racine → correctif
1) Symptom: MySQL load didn’t drop after adding Redis
Cause racine : faible hit rate, cache à la mauvaise granularité, ou logique cache-aside manquante sous concurrence.
Correctif : Mesurez keyspace_hits/misses, identifiez ce qui est mis en cache, et mettez en cache au niveau de l’objet frontière. Ajoutez de la coalescence de requêtes pour arrêter les thundering herds.
2) Symptom: Redis memory grows until evictions start
Cause racine : absence de TTLs, trop de clés uniques (cardinalité élevée), ou valeurs plus grandes que prévu (blobs JSON, tableaux non compressés).
Correctif : Ajoutez des TTLs par défaut, limitez la taille des payloads, utilisez des hashes pour des champs liés, et définissez maxmemory + une politique d’éviction adaptée au « cache » plutôt qu’au « stockage éternel ».
3) Symptom: P99 latency got worse after “caching”
Cause racine : Redis introduit de la latence en queue à cause de fsync de persistance, commandes lentes, grosses clés ou sauts réseau ; les retries applicatifs amplifient le problème.
Correctif : Vérifiez redis-cli –latency-history et SLOWLOG. Réduisez les opérations sur grosses clés, ajustez la persistance et appliquez des timeouts agressifs avec coupe-circuits.
4) Symptom: Random stale data, hard to reproduce
Cause racine : bugs d’invalidation, caches multi-writer, ou utilisation exclusive des TTLs comme stratégie de « consistance ».
Correctif : Choisissez un seul propriétaire de cache par espace de noms de clés, implémentez l’invalidation explicite sur les écritures et utilisez des clés versionnées quand c’est possible.
5) Symptom: MySQL is fine, but Redis CPU is high
Cause racine : trop d’opérations par requête, patterns chatty, ou scripts Lua effectuant des travaux lourds.
Correctif : Groupez les lectures (MGET/pipelining), mettez en cache des objets plus riches pour réduire le nombre d’appels, et gardez les scripts Lua petits et bien testés.
6) Symptom: After Redis restart, app melts down and MySQL follows
Cause racine : cold cache plus stampede, pas de limitation de rate pour la reconstruction, et pas de comportement coupe-circuit.
Correctif : Utilisez soft TTL, single-flight locking par clé, warming en arrière-plan pour les clés critiques, et limitez la concurrency de rebuild.
7) Symptom: Data loss accusations after Redis failover
Cause racine : traiter Redis comme magasin autoritatif pour un état business critique avec réplication asynchrone.
Correctif : Placez la vérité dans MySQL (ou un autre store durable), mettez en cache des vues dérivées dans Redis, et documentez la fenêtre de perte tolérable pour les clés de coordination.
8) Symptom: Redis cluster is stable but clients see timeouts
Cause racine : épuisement du pool de connexions, changements DNS/endpoints, pression sur les ports NAT, ou timeouts client mal configurés.
Correctif : Instrumentez les pools clients, gardez des timeouts Redis serrés mais réalistes, réutilisez les connexions et évitez les patterns connect/disconnect par requête.
Listes de vérification / plan pas à pas
Étapes pas à pas : introduire Redis pour réduire la charge MySQL
- Choisissez une cible : un endpoint ou un digest de requête qui domine le temps total MySQL, pas « toute la base ».
- Définissez l’objet mis en cache : ce dont le service a réellement besoin (ex. « user_context:v3:{user_id} »).
- Décidez du modèle de consistance : TTL-only, invalidation-on-write, ou clés versionnées.
- Fixez un TTL avec jitter : commencez conservateur (minutes), ajoutez ±10–20 % de jitter pour éviter des expirations synchronisées.
- Implémentez le cache-aside : GET, en miss SELECT, SETEX, retourner.
- Ajoutez une protection contre le stampede : single-flight par clé (verrou de clé avec TTL court) ou coalescence de requêtes in‑process.
- Instrumentez : ratio de hit cache, réduction QPS MySQL, latence P50/P95/P99, taux d’erreur, taux d’éviction.
- Comportement en cas de défaillance : définissez ce qui se passe quand Redis est down. Fast fail avec fallback pour les lectures critiques ; coupe-circuit pour éviter les tempêtes de retry.
- Capacity plan : estimez le nombre de clés, la taille moyenne des valeurs, le churn TTL et l’overhead mémoire. Définissez maxmemory explicitement.
- Déployez progressivement : feature flag, activation progressive par pourcentage et rollback facile.
Checklist : ces données sont-elles sûres à mettre dans Redis ?
- Pouvez-vous les recomposer depuis MySQL ou d’autres stores durables ?
- Pouvez-vous tolérer des lectures périmées jusqu’au TTL ?
- Pouvez-vous tolérer de perdre les dernières secondes d’écritures en cas de failover ?
- Y a‑t‑il un propriétaire clair pour l’invalidation ?
- La cardinalité des clés est‑elle bornée, ou peut‑elle exploser avec des entrées utilisateur ?
Checklist : readiness production pour Redis devant MySQL
- Timeouts Redis serrés et retries bornés.
- Coupe-circuit en place pour erreurs/timeouts Redis.
- maxmemory et politique d’éviction configurés explicitement.
- SLOWLOG surveillé ; grosses clés évitées ; SCAN utilisé plutôt que KEYS.
- Scénario cold-cache testé sous charge peak-like.
- MySQL a de la marge pour survivre à la dégradation du cache.
FAQ
1) Can Redis replace MySQL?
Pas pour la plupart des applications. Redis peut stocker des données, mais MySQL fournit la modélisation relationnelle, les contraintes et la durabilité que Redis n’égale pas par défaut. Utilisez Redis pour accélérer MySQL, pas pour l’usurper.
2) What’s the safest first Redis use case?
Sessions, limitation de débit et mise en cache de modèles de lecture dérivés. Ce sont des cas naturellement bornés dans le temps et ne prétendent pas être le système de référence.
3) How do I estimate whether I can cut MySQL load by 80%?
Regardez les top query digests par temps total et par nombre. Si un petit ensemble de requêtes de lecture répétitives domine et que les résultats sont cachables, de grosses réductions sont plausibles. Si les écritures dominent ou si les lectures sont très uniques, vous n’obtiendrez pas 80 %.
4) Should I cache individual rows or full objects?
Préférez des objets complets ou des vues agrégées qui correspondent aux frontières de service. Le cache au niveau des lignes conduit souvent à de nombreux appels Redis par requête et une logique d’invalidation complexe.
5) What TTL should I use?
Choisissez le TTL en fonction de la tolérance à la péremption et du risque opérationnel acceptable. Un TTL court réduit la péremption mais augmente le churn et le risque de stampede. Ajoutez du jitter. Rappelez‑vous : le TTL n’est pas un modèle de consistance ; c’est un filet de sécurité.
6) What eviction policy should I use?
Pour la mise en cache typique : une variante LRU/LFU avec prise en compte des TTLs est courante. L’essentiel est de définir maxmemory et de prévoir les évictions dans le comportement applicatif. « Noeviction » peut convenir pour les données de coordination si vous dimensionnez correctement, mais peut aussi transformer la pression mémoire en outage.
7) How do I keep Redis from becoming a single point of failure?
Exécutez Redis avec réplication et failover, mais surtout : rendez l’application résiliente. Timeouts serrés, retries bornés, coupe-circuits et un mode dégradé testé qui n’entraîne pas un stampede sur MySQL.
8) Is Redis persistence (AOF/RDB) enough for critical data?
Généralement non. La persistance réduit les fenêtres de perte mais n’offre pas les garanties relationnelles et transactionnelles que beaucoup de datasets critiques requièrent. Si les données peuvent apparaître dans un audit légal, elles appartiennent à MySQL (ou un système durable équivalent), pas à une mémoire « majoritairement durable ».
9) Why did Redis increase MySQL load during an outage?
Parce que votre chemin de repli provoque probablement un stampede : chaque miss de cache devient une requête DB, et les retries multiplient le trafic. Corrigez cela avec single-flight, rebuilds limités et coupe-circuits.
10) What about using Redis for search or analytics?
Redis peut supporter des index secondaires et des structures avancées, mais opérationnellement ce n’est pas un moteur d’analytics généraliste. Si vous avez besoin de filtrage flexible et d’agrégation à grande échelle, gardez cette charge dans des systèmes conçus pour cela.
Étapes suivantes réalisables cette semaine
Si vous voulez que Redis réduise dramatiquement la charge MySQL, arrêtez de penser en slogans et commencez à penser en contrats. MySQL c’est la vérité. Redis c’est la vitesse. La vérité sans vitesse est lente ; la vitesse sans vérité, c’est un futur rapport d’incident.
- Récupérez les 5 premiers query digests MySQL par temps total et choisissez un endpoint cible.
- Concevez une clé de cache qui représente l’objet au niveau service, pas une ligne de table.
- Implémentez le cache-aside avec invalidation explicite sur les écritures et jitter sur les TTLs.
- Ajoutez la protection contre le stampede et les coupe-circuits avant de l’activer globalement.
- Mesurez : hit rate, évictions, latence Redis et QPS/CPU MySQL avant et après.
Puis faites le test ennuyeux : redémarrez Redis en staging sous charge et confirmez que votre application ne panique pas. Si elle panique en staging, elle paniquera en production — simplement avec plus de témoins.