Certaines sites web ne « montent pas en charge ». Ils accumulent lentement de la latence jusqu’à ce que la première vraie pointe de trafic transforme votre base de données en cratère fumant. Vous ajoutez des index. Vous ajoutez des réplicas. Vous ajoutez « un cache ». Les graphiques s’améliorent — jusqu’à ce qu’ils ne s’améliorent plus, et maintenant vous déboguez des paniers manquants, des prix périmés et des utilisateurs déconnectés au hasard.
La vérité inconfortable : la mise en cache est facile à ajouter, difficile à rendre correcte, et très facile à rendre rapidement dans la mauvaise direction. MariaDB et Redis peuvent tous deux servir de cache, mais leurs modes de défaillance diffèrent. Si vous comprenez ces modes, vous pouvez construire des caches qui accélèrent votre site sans perdre de données.
Ce que vous êtes réellement en train de décider
« MariaDB vs Redis » est rarement un choix binaire. La vraie décision est comment vous répartissez les responsabilités entre :
- Système de référence : la base de données qui doit rester correcte et récupérable après des pannes (typiquement MariaDB).
- Calcul et coordination : compteurs, verrous, limitations de débit, classements, clés de déduplication (Redis excelle ici).
- Données dérivées : résultats de requêtes en cache, pages rendues, agrégats pré-calculés (les deux peuvent le faire, mais pas de la même manière).
- Risque opérationnel : pouvez-vous tolérer la perte du contenu du cache ? Si non, il s’agit de persistance, sauvegardes, réplication et exercices de restauration — pas seulement « ajouter Redis ».
Si vous voulez de la vitesse sans perte de données, soyez honnête sur ce que « données » signifie. Perdre une page produit en cache est acceptable. Perdre le statut d’un paiement confirmé ne l’est pas. Redis peut être durable-ish. MariaDB peut être utilisé comme cache-ish. Mais aucun ne doit être forcé à jouer le rôle de l’autre sans plan.
Idée paraphrasée de Werner Vogels : « Tout échoue, tout le temps », ainsi vous concevez en supposant que des composants vont tomber en panne, et vous rendez la récupération routinière.
MariaDB comme cache : quand c’est raisonnable, quand c’est un piège
Utiliser MariaDB comme cache signifie généralement l’un des cas suivants :
- Tables matérialisées-ish : agrégats pré-calculés stockés dans des tables et rafraîchis.
- Modèles de lecture dénormalisés : « table d’index de recherche », « product_summary », « user_profile_compact ».
- Mise en cache de résultats par l’application : stockage de blobs sérialisés dans une table avec champs de type TTL.
- Réplicas de lecture : pas forcément un cache, mais délester les lectures peut donner l’impression d’en être un.
Pourquoi MariaDB peut être un bon cache
Parce que c’est ennuyeux. L’ennui est une caractéristique quand la correction compte.
- La durabilité est native : InnoDB redo logs, doublewrite buffer, crash recovery. Il est conçu pour préserver vos bits.
- SQL vous donne des leviers : vous pouvez rafraîchir une table de cache de façon incrémentale, la joindre, la filtrer, la backfiller.
- Les ops le connaissent déjà : sauvegardes, réplication, monitoring, contrôles d’accès — souvent déjà en place.
Pourquoi MariaDB comme cache peut vous nuire
La mise en cache vise à réduire du travail coûteux. Si votre « cache » coûte autant que le travail original, vous n’avez pas mis en cache — vous avez dupliqué la charge.
- Les lignes chaudes deviennent des goulets : compteurs, limites de débit, « last seen », « inventory remaining » peuvent se transformer en tempêtes de mises à jour et en contention de verrous.
- Thrash du buffer pool : mettre en cache beaucoup de blobs éphémères peut évincer des pages réellement importantes.
- Le nettoyage TTL fait mal : supprimer des lignes expirées peut provoquer des pics d’I/O, du retard de purge et de la latence de réplication.
Règle simple : utilisez MariaDB pour mettre en cache des données dérivées structurées et interrogeables que vous êtes prêt à gérer comme un vrai jeu de données. Ne l’utilisez pas pour « des millions de petites clés TTL » sauf si vous aimez expliquer à la finance pourquoi votre primaire est à 95 % CPU un mardi matin.
Redis comme cache : rapide, mais avec des bords tranchants
Redis est un serveur de structures de données en mémoire. Cette phrase porte beaucoup de sens. La valeur n’est pas seulement la vitesse ; ce sont les primitives : incréments atomiques, sets, sorted sets, streams, pub/sub, scripts Lua et expiration rapide.
Ce dans quoi Redis excelle
- Requêtes à faible latence : cache-aside pour récupération d’objets, fragments HTML, permissions, feature flags.
- État éphémère à fort turnover : sessions, tokens CSRF, liens one-time, clés d’idempotence.
- Coordination : verrous distribués (avec prudence), limitations de débit, files (avec réserves), ensembles de déduplication.
- Lutter contre les stampedes : avec TTL, jitter et patrons « single flight ».
Ce dans quoi Redis est mauvais (et dont vous pouvez aggraver la situation)
- Faire semblant que c’est une base de données sans budgéter la persistance : si vous stockez des faits business primaires dans Redis sans plan de durabilité, vous misez votre entreprise sur la RAM et des valeurs de configuration par défaut.
- Croissance mémoire non bornée : sans politiques maxmemory et hygiène des clés, il acceptera vos données jusqu’à ce que l’OOM killer du kernel arrive comme un auditeur non invité.
- Clés volumineuses et grosses valeurs : des clés uniques avec des payloads énormes provoquent des pics de latence en raison d’opérations bloquantes et d’un surcoût d’éviction.
Blague n°1 : Redis, c’est comme un espresso — formidable en petite quantité, catastrophique quand vous continuez à remplir la tasse parce que « elle tient encore ».
Faits intéressants et petite histoire qui compte
- Redis est né (2009) pour gérer des statistiques temps réel sans marteler une base relationnelle — son ADN est « compteurs rapides et ensembles », pas « durabilité parfaite ».
- MariaDB a été créé (2009–2010) comme fork de MySQL après l’acquisition de Sun par Oracle ; de nombreuses équipes l’ont adopté pour garder une voie de développement ouverte.
- Le Query Cache historique de MySQL (aussi pertinent à l’ascendance de MariaDB) était notoire pour la contention de mutex sous charge d’écriture ; il a finalement été retiré dans MySQL 8.0 car il nuisait plus qu’il n’aidait à grande échelle.
- L’expiration Redis est paresseuse + active : les clés expirent quand elles sont accédées, plus un échantillonnage en arrière-plan. Cela affecte la planification mémoire et le débogage « pourquoi il reste des éléments périmés ? ».
- Redis a deux modes principaux de persistance : snapshots RDB et journaling AOF ; les deux ont des compromis en amplification d’écriture et en temps de récupération.
- Le buffer pool d’InnoDB est un cache : MariaDB cache déjà des pages en mémoire. Parfois votre « couche de cache » duplique ce que le moteur fait déjà bien.
- Le retard de réplica est un vieux problème : utiliser des réplicas de lecture comme « cache » introduit des problèmes de cohérence qui peuvent ressembler à des écritures manquantes ou des « bugs aléatoires ».
- L’exécution monothread de Redis (pour la boucle principale) est une caractéristique pour l’atomicité, mais cela signifie que les commandes lentes et les gros payloads pénalisent tout le monde.
- L’invalidation de cache est un problème fameux dans les systèmes distribués depuis des décennies — pas parce que les ingénieurs sont mauvais, mais parce que le temps, la concurrence et les défaillances partielles sont pénibles.
Modèles de mise en cache qui ne mangent pas vos données
1) Cache-aside (chargement paresseux) : le choix par défaut
Flux : lire depuis le cache → miss → lire depuis MariaDB → mettre le cache avec TTL → retourner.
Pourquoi ça marche : MariaDB reste la source de vérité. Le cache peut être perdu à tout moment. La récupération est un « temps de warmup ».
Modes de défaillance :
- Stampede : de nombreuses requêtes manquent et frappent toutes MariaDB en même temps.
- Données périmées : cache non invalidé après écritures, ou TTL trop long.
- Clé chaude : une seule clé populaire provoque churn et contention côté applicatif.
À faire :
- Ajouter un TTL avec jitter (secondes aléatoires additionnelles) pour éviter des expirations synchronisées.
- Utiliser « single flight » dans l’application (un seul recalcul par clé) ou des verrous Redis avec TTL courts.
- Mettre en cache brièvement aussi les résultats négatifs (par ex. « utilisateur introuvable » pendant 30s) pour éviter les misses bruteforce.
2) Read-through : déléguer la gestion des misses
Certaines bibliothèques clientes ou sidecars peuvent interroger la DB en cas de miss. Opérationnellement séduisant. D’habitude une mauvaise idée sauf si éprouvé en production chez vous, car cela cache la charge depuis l’application et rend le trafic DB plus difficile à raisonner.
Cas d’usage : plateformes internes aux schémas d’accès standardisés et excellente observabilité.
3) Write-through : correction d’abord, latence ensuite
Flux : écrire dans le cache et dans la DB sur le même chemin ; les lectures accèdent au cache.
Bénéfice : le cache reste frais ; pas de complexité d’invalidation pour de nombreux cas.
Coût : latence d’écriture plus élevée, plus d’éléments mobiles dans le chemin d’écriture. Si Redis est down, les écritures peuvent échouer sauf si vous dégradez explicitement vers DB-seulement.
À faire : traitez la mise à jour du cache comme best-effort sauf si la valeur mise en cache est nécessaire pour la correction. Pour la plupart des sites, ce n’est pas le cas.
4) Write-behind (aka write-back) : la manière la plus rapide d’inventer la perte de données
Flux : écrire dans le cache, retourner le succès à l’utilisateur, écrire de façon asynchrone dans la DB plus tard.
Quand c’est acceptable : rarement. Peut-être pour des compteurs analytiques où perdre quelques mises à jour est tolérable et où vous avez idempotence et replay.
Quand c’est un désastre : commandes, soldes, permissions, droits, inventaire, contenu généré par l’utilisateur.
Write-behind est la façon dont vous transformez une panne de cache en post-mortem intitulé « pourquoi 2% des paniers ont disparu ». Cela attise aussi la curiosité des équipes conformité concernant vos plans du week-end.
5) Invalidation uniquement par TTL : peu coûteux, parfois erroné
Si vous définissez un TTL et que vous n’invalidez jamais explicitement, vous comptez sur le temps pour corriger la cohérence. C’est acceptable pour du contenu qui peut être légèrement périmé (pages d’accueil, listes trending), et inacceptable pour toute chose transactionnelle.
Mieux : combinez TTL et invalidation pilotée par événements pour les clés critiques.
6) Invalidation pilotée par événements : plus dur, mais évolutif pour la cohérence
Flux : les écritures vont à MariaDB → publier un événement « entité modifiée » → des consommateurs invalident ou rafraîchissent les clés Redis.
Bénéfice : faible obsolescence, comportement cohérent sous charge.
Risque : livraison de messages, retard des consommateurs et ordre des événements. Vous avez besoin d’idempotence et de supposer que les événements peuvent être dupliqués.
7) Clés versionnées : le pattern anti-périmé qui fonctionne vraiment
Au lieu de supprimer des clés, vous changez l’espace de noms des clés en incrémentant un numéro de version.
- Clé :
product:123:v17 - Une autre clé stocke la version courante :
product:123:ver
À la mise à jour, incrémentez la version. Les nouvelles lectures vont sur la nouvelle clé. Les anciennes clés expirent naturellement. C’est une solution simple et robuste pour les pages à fort trafic où les tempêtes de suppression ruinerait Redis.
8) Patterns de protection contre le cache stampede
- Recalcul probabiliste anticipé : rafraîchir avant l’expiration avec une probabilité basée sur le TTL restant et le coût de calcul.
- Servir du périmé pendant la révalidation : garder un soft TTL (périmé toléré) et un hard TTL (recalcul obligatoire). Le périmé vous achète du temps lorsque la DB est brûlante.
- Single-flight : un seul worker recalcul effectif ; les autres attendent ou servent du périmé.
9) Redis pour les sessions : rapide et généralement correct
Les sessions sont un bon cas d’usage pour Redis car elles sont éphémères et TTLées naturellement. Mais vous devez toujours définir ce que « pas de perte de données » signifie. Perdre des sessions gêne les utilisateurs ; en général ça ne corrompt pas l’argent.
Ne stockez pas d’état d’autorisation longue durée dans les sessions sauf si vous pouvez révoquer correctement. Cachez des permissions, oui ; stocker « cet utilisateur est admin pour toujours », non.
10) Tables de synthèse MariaDB : un « cache » qui reste interrogeable
Si le coût est une agrégation multi-join coûteuse, Redis peut mettre en cache le résultat, mais vous perdez la capacité d’interrogation. Les tables de synthèse MariaDB vous permettent d’indexer et de filtrer les données dérivées. Vous payez la complexité de rafraîchissement et le stockage, mais vous gagnez un SQL explicable et la durabilité.
Blague n°2 : l’invalidation de cache est la version adulte de « avez-vous essayé d’éteindre et rallumer », sauf que le cache se souvient que vous avez déjà essayé.
Durabilité : « sans perte de données » dans le monde réel
Quand quelqu’un dit « pas de perte de données », demandez : quelles données, et quelle est votre fenêtre de perte acceptable ?
- Données dérivées cachables : les perdre est acceptable ; on les recalcule depuis MariaDB.
- État utilisateur éphémère : le perdre est tolérable mais doit rester rare ; les sessions peuvent nécessiter une nouvelle authentification.
- Faits transactionnels : doivent survivre aux crashs de processus, pannes de nœud et erreurs d’opérateur. Stockez-les dans MariaDB (ou un autre vrai système de référence), pas seulement dans Redis.
Checklist durabilité MariaDB (baseline)
- InnoDB avec réglages de flush appropriés à votre appétit de risque.
- Binary logs activés si vous avez besoin de récupération point-in-time.
- Sauvegardes testées par restauration, pas par espoir.
- Réplication surveillée pour le lag et les erreurs.
Options de durabilité Redis (si vous insistez pour garder un état important)
Redis peut persister les données, mais la persistance n’est pas magique. Ce sont des compromis que vous devez choisir délibérément.
- RDB snapshots : snapshots périodiques. Écritures plus rapides, mais vous pouvez perdre des données entre les snapshots.
- AOF (append-only file) : journalise chaque écriture. Meilleure durabilité, plus d’I/O d’écriture, et un comportement de rewrite pour gérer la taille du fichier.
- Réplication : aide la disponibilité, pas garantie zéro perte sauf si vous concevez pour cela et acceptez la latence.
Si vous ne pouvez vraiment pas perdre les données Redis, vous n’êtes plus « en train d’utiliser un cache ». Vous exploitez une autre base de données. Traitez-la comme telle : persistance, réplication, sauvegardes, exercices de restauration et planification de capacité.
Tâches pratiques : commandes, sorties et ce que vous décidez
Voici les vérifications que vous lancez pendant un incident ou un examen de performance. Chaque tâche inclut : commande, sortie d’exemple, ce que ça signifie et la décision que vous prenez.
Task 1: Confirm MariaDB is the bottleneck (top queries)
cr0x@server:~$ sudo mariadb -e "SHOW FULL PROCESSLIST\G" | sed -n '1,60p'
*************************** 1. row ***************************
Id: 8421
User: app
Host: 10.0.3.24:41372
db: prod
Command: Query
Time: 12
State: Sending data
Info: SELECT ... FROM orders JOIN order_items ...
*************************** 2. row ***************************
Id: 8422
User: app
Host: 10.0.3.25:41810
db: prod
Command: Query
Time: 11
State: Sending data
Info: SELECT ... FROM orders JOIN order_items ...
Signification : des lectures longues dominent, et beaucoup sont identiques. C’est du territoire de mise en cache idéal.
Décision : implémenter cache-aside pour le chemin de lecture coûteux, ou créer une table de synthèse si la requête est complexe et nécessite du filtrage.
Task 2: Check MariaDB buffer pool health
cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name | Value |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests | 984332111 |
| Innodb_buffer_pool_reads | 12099122 |
+---------------------------------------+-----------+
Signification : lectures physiques vs lectures logiques. Un ratio élevé de reads par read_requests indique un buffer pool froid ou sous-dimensionné.
Décision : si le taux de misses du buffer pool est élevé, tuner MariaDB peut être plus efficace qu’ajouter Redis pour certains workloads.
Task 3: Identify slow queries you are about to “cache over”
cr0x@server:~$ sudo mariadb -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/slow.log |
+---------------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| long_query_time | 1.000 |
+-----------------+-------+
Signification : vous avez le slow logging activé avec un seuil à 1s.
Décision : avant de mettre en cache, corrigez les index manquants évidents et les patterns N+1. Le cache doit réduire la charge, pas masquer des accès négligents.
Task 4: Measure replication lag (when replicas are your “cache”)
cr0x@server:~$ sudo mariadb -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 37
Signification : le réplica accuse 37 secondes de retard. Les lectures depuis le réplica seront périmées.
Décision : n’utilisez pas le réplica pour les chemins read-after-write (login, checkout). Utilisez le primaire ou construisez une stratégie de cohérence (lectures sticky, lectures basées sur GTID, ou invalidation explicite du cache).
Task 5: Check Redis memory and eviction risk
cr0x@server:~$ redis-cli INFO memory | egrep "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
used_memory_human:7.83G
maxmemory_human:8.00G
mem_fragmentation_ratio:1.62
Signification : vous êtes proche de maxmemory, la fragmentation est élevée, la pression d’éviction est probable.
Décision : soit augmenter la mémoire, réduire la taille des valeurs, sharder, ou changer la politique d’éviction. Examinez aussi la fragmentation en revoyant le comportement de l’allocateur et le churn des clés.
Task 6: Verify Redis eviction policy (you might be evicting the wrong thing)
cr0x@server:~$ redis-cli CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
Signification : Redis refusera les écritures quand la mémoire est pleine. Les applications interprètent souvent cela comme des « erreurs aléatoires ».
Décision : pour du cache, préférez allkeys-lfu ou volatile-lfu selon si toutes les clés ont un TTL. Pour l’état critique, soyez explicite : peut-être que vous voulez noeviction pour que la défaillance soit bruyante.
Task 7: Look for big keys (latency spikes and memory waste)
cr0x@server:~$ redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as biggest overall keys per data type
Biggest string found 'pagecache:/product/123' has 5242880 bytes
Biggest hash found 'session:hash' has 183201 fields
0 keys sampled
Signification : vous avez des valeurs multi-mégas et des hashes gigantesques ; tous deux peuvent créer des pauses et une éviction inégale.
Décision : scindez les gros objets, compressez avec prudence (compromis CPU), et évitez « un hash géant de tout ». Préférez des clés par-session avec TTL.
Task 8: Check Redis persistence settings (data loss window)
cr0x@server:~$ redis-cli CONFIG GET save appendonly appendfsync
1) "save"
2) "900 1 300 10 60 10000"
3) "appendonly"
4) "no"
5) "appendfsync"
6) "everysec"
Signification : snapshots RDB activés ; AOF désactivé. Pire cas, perte jusqu’à l’intervalle du snapshot si le nœud meurt.
Décision : si Redis contient autre chose que du cache jetable, activez AOF et décidez d’une politique fsync ; sinon acceptez la perte de cache et concevez en conséquence.
Task 9: Detect blocked Redis clients (slow commands)
cr0x@server:~$ redis-cli INFO clients | egrep "blocked_clients|connected_clients"
connected_clients:812
blocked_clients:17
Signification : des clients sont bloqués ; quelque chose est lent (gros keys, scripts Lua, disque lent pour AOF, ou stalling réseau).
Décision : inspecter le slowlog, identifier la commande, et corriger la charge. « En mémoire » ne signifie pas immunité à l’I/O ou CPU.
Task 10: Inspect Redis slowlog to catch self-inflicted pain
cr0x@server:~$ redis-cli SLOWLOG GET 3
1) 1) (integer) 19042
2) (integer) 1735250401
3) (integer) 15423
4) 1) "KEYS"
2) "*"
5) "10.0.2.9:51244"
6) ""
2) 1) (integer) 19041
2) (integer) 1735250399
3) (integer) 8120
4) 1) "HGETALL"
2) "session:hash"
5) "10.0.2.10:42118"
6) ""
Signification : quelqu’un a exécuté KEYS * (bloque Redis sur de grands jeux de données) et vous utilisez HGETALL sur un hash massif.
Décision : bannir KEYS en production (utiliser SCAN), repenser le stockage des sessions pour éviter les gros hashes, et ajouter des garde-fous tooling.
Task 11: Confirm MariaDB indexing on the hot path
cr0x@server:~$ sudo mariadb -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 20\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: ref
possible_keys: idx_user_created
key: idx_user_created
key_len: 4
ref: const
rows: 20
Extra: Using where
Signification : la requête utilise l’index composite attendu et scanne seulement un petit nombre de lignes.
Décision : mettre en cache cette requête peut encore aider, mais vous ne masquez plus un index manquant. Bien. Maintenant vous pouvez dimensionner le TTL selon l’exigence de fraîcheur business.
Task 12: Check for lock contention in MariaDB (counters gone wrong)
cr0x@server:~$ sudo mariadb -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,+40p'
------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 928331, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 221 page no 9 n bits 80 index PRIMARY of table `prod`.`rate_limits`
Signification : deadlocks sur une table de rate limit. Classique « base utilisée comme service de compteurs/verrou ».
Décision : déplacer la limitation de débit et les compteurs vers Redis où les opérations atomiques sont bon marché, et garder MariaDB pour les enregistrements durables.
Task 13: Validate Redis key TTL hygiene (are you leaking memory?)
cr0x@server:~$ redis-cli INFO keyspace
db0:keys=1823492,expires=24112,avg_ttl=0
Signification : presque aucune clé n’a d’expiration ; avg_ttl=0 suggère des clés indéfinies.
Décision : pour un cache, la plupart des clés devraient avoir un TTL. Si ce n’est pas le cas, vous construisez un second datastore sans plan de migration.
Task 14: Detect a cache stampede in the application layer (Redis hit rate)
cr0x@server:~$ redis-cli INFO stats | egrep "keyspace_hits|keyspace_misses"
keyspace_hits:12099331
keyspace_misses:8429932
Signification : le taux de miss est élevé ; Redis ne sert pas efficacement comme cache, ou les TTL sont trop bas, ou les noms de clés sont incohérents.
Décision : standardiser la construction des clés, augmenter le TTL quand c’est sûr, ajouter du jitter et implémenter single-flight pour éviter les thundering herds.
Task 15: Measure OS-level pressure on Redis node (swapping is death)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 16Gi 15Gi 120Mi 1.1Gi 900Mi 300Mi
Swap: 2Gi 1.8Gi 200Mi
Signification : la machine Redis swappe. La latence deviendra non linéaire, puis l’incident deviendra « mystérieux ».
Décision : arrêter le swapping (tuning, ajouter de la RAM, réduire le dataset). Si Redis doit être fiable, désactivez le swap ou imposez des marges mémoire strictes et des alertes.
Task 16: Confirm what MariaDB is spending time on (CPU vs I/O)
cr0x@server:~$ sudo iostat -x 1 3
Linux 6.1.0 (db1) 12/30/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
22.10 0.00 5.33 41.20 0.00 31.37
Device r/s w/s rKB/s wKB/s await svctm %util
nvme0n1 820.0 310.0 52160.0 20120.0 18.2 0.9 92.5
Signification : fort iowait et forte utilisation disque ; la DB est liée à l’I/O.
Décision : mettre en cache les lectures chaudes dans Redis peut aider, mais considérez aussi le dimensionnement du buffer pool, le tuning des requêtes/index et l’amélioration du stockage. Ne vous contentez pas de cacher un disque lent si votre working set devrait tenir en mémoire.
Playbook de diagnostic rapide
Vous n’avez pas le temps pour la philosophie quand le graphe p95 de latence ressemble à un saut de ski. Voici l’ordre qui trouve rapidement les goulets, avec un minimum d’auto-tromperie.
Premier : est-ce la base, le cache ou l’app ?
- Vérifier hit/miss et latence Redis : si les misses sont élevées, Redis peut être hors-sujet ; si la latence pique, Redis peut être surchargé ou swapper.
- Vérifier les requêtes actives MariaDB et les lock waits : « Sending data » long signale des lectures coûteuses ; lock waits/deadlocks signalent de la contention d’écriture.
- Vérifier les erreurs/timeouts applicatifs : les erreurs de cache peuvent se répercuter en surcharge DB (classique). La surcharge DB peut provoquer des stampedes de cache (aussi classique).
Deuxième : chercher des dynamiques de stampede
- Une clé populaire a-t-elle expiré simultanément sur toute la flotte (pas de jitter) ?
- Y a-t-il eu un déploiement qui a changé le nom des clés ou les valeurs TTL par défaut ?
- Le pattern de trafic est-il différent (campagne, crawler, bot) ?
Troisième : confirmer la pression sur les ressources
- Mémoire Redis : proche de maxmemory ? thrash d’éviction ? fragmentation ? swapping ?
- I/O MariaDB : iowait élevé ? misses du buffer pool ? explosion du slow query log ?
- Réseau : RTT augmenté entre app et Redis/DB peut masquer un « cache lent ».
Quatrième : choisir la mitigation la moins risquée
- Servir du périmé pendant la révalidation pour le contenu sûr.
- Augmenter temporairement le TTL et ajouter du jitter.
- Limiter les recalculs (single-flight) pour protéger MariaDB.
- Pour surcharge Redis : réduire la taille des valeurs, désactiver les commandes coûteuses, scaler horizontalement, ou basculer en mode ouvert (DB-only) si sûr.
Erreurs courantes : symptômes → cause racine → correction
1) « Cache ajouté, mais la charge DB n’a pas diminué »
Symptômes : CPU Redis faible, misses élevés, QPS MariaDB inchangé.
Cause racine : mise en cache du mauvais niveau (ex. après personnalisation), clés incohérentes, TTL trop bas, ou la plupart des requêtes sont uniques.
Correction : mettre en cache à un niveau plus partagé (fragments), standardiser la construction des clés, et mesurer le hit rate par endpoint. Envisager des tables de synthèse si la requête est intrinsèquement non cachable.
2) « Redis est rapide jusqu’au moment où il ne l’est plus »
Symptômes : pics intermittents, blocked clients, timeouts.
Cause racine : grosses clés, commandes lentes, swapping, stalls AOF fsync, ou hotspot monothread.
Correction : exécuter --bigkeys, inspecter le slowlog, supprimer les commandes bloquantes, garder une marge mémoire, et éviter les valeurs gigantesques.
3) « Les utilisateurs voient des données périmées après des mises à jour »
Symptômes : les mises à jour de profil n’apparaissent pas, les prix reviennent en arrière, les changements admin sont retardés.
Cause racine : invalidation uniquement par TTL ou événements manquants ; lecture depuis réplica en retard ; clés non versionnées.
Correction : invalidation pilotée par événements pour les entités critiques, clés versionnées pour les objets chauds, et cohérence read-after-write (lectures sticky sur le primaire si nécessaire).
4) « Nous avons perdu des données quand Redis a redémarré »
Symptômes : leaderboards vides, sessions manquantes, compteurs disparus ; panique applicative.
Cause racine : Redis traité comme système de référence ; persistance désactivée ou insuffisante ; pas de plan de restauration.
Correction : déplacer les faits durables vers MariaDB ; activer AOF si Redis doit persister ; tester le comportement de redémarrage et le temps de récupération.
5) « MariaDB est devenu plus lent après l’ajout de ‘tables de cache’ »
Symptômes : taux de hit du buffer pool en baisse, I/O en hausse, purge lag, retard de réplication.
Cause racine : données éphémères floodant InnoDB, suppressions TTL causant du churn.
Correction : déplacer les clés TTL-intensives vers Redis ; si vous gardez des tables cache, partitionnez-les, nettoyez par lots, et séparez les charges par instance si nécessaire.
6) « 500 aléatoires pendant les pics de trafic »
Symptômes : erreurs corrélées à la charge ; Redis « OOM command not allowed » ou timeouts.
Cause racine : maxmemory atteint avec noeviction, ou politique d’éviction inadaptée ; stampede de cache forçant les recalculs.
Correction : définir maxmemory + une politique d’éviction adaptée aux caches ; ajouter du jitter et single-flight ; protéger MariaDB avec des coupe-circuits.
7) « Le cache rend la correction pire que sans cache »
Symptômes : lectures incohérentes, états impossibles, bugs difficiles à reproduire.
Cause racine : mettre en cache des objets à cohérence mixte (partie DB, partie autres services), mises à jour multi-clés sans atomicité, ou utiliser Redis comme queue sans sémantique d’acknowledgement.
Correction : mettre en cache des snapshots immuables ; versionner les clés ; éviter les illusions transactionnelles multi-clés ; si vous avez besoin d’un messaging durable, utilisez des queues/streams avec garanties de livraison claires.
Trois mini-histoires d’entreprise (anonymisées)
Histoire 1 : Incident causé par une mauvaise hypothèse (les réplicas sont « un cache »)
L’équipe exploitait un grand site de contenu avec MariaDB primaire + réplicas. Quelqu’un a proposé « des performances gratuites » en envoyant toutes les lectures vers les réplicas. Cela a fonctionné en staging, parce que staging n’avait pas de volume d’écrit significatif. En production si.
Ils ont routé la page « compte » (achats récents, carnet d’adresses, statut d’abonnement) vers des réplicas. Des tickets support sont arrivés : « J’ai changé mon adresse et elle n’a pas été enregistrée. » Les ingénieurs ont vérifié le primaire — les données étaient correctes. L’UI affichait toujours l’ancienne donnée parce que le réplica était en retard sous charge.
L’hypothèse erronée était subtile : ils traitaient les réplicas comme un cache où la staleness est acceptable. Mais la page compte est une surface read-after-write. Les humains remarquent quand leur adresse revient à l’ancienne.
La correction fut ennuyeuse mais efficace : lectures sticky. Après une écriture réussie, les lectures de l’utilisateur pendant les N secondes suivantes allaient au primaire. Ils ont aussi instrumenté le lag des réplicas et fait refuser les lectures sur réplica quand le lag dépassait un seuil.
La leçon : les réplicas peuvent réduire la charge de lecture, mais ils introduisent un comportement de cohérence que vous devez concevoir. Un cache miss ne coûte du temps. Une lecture périmée coûte la confiance.
Histoire 2 : Optimisation qui a mal tourné (write-behind « pour la vitesse »)
Une société e‑commerce avait un chemin « ajouter au panier » lent. Quelqu’un a constaté que la table carts dans MariaDB était un hotspot — beaucoup de petites mises à jour, beaucoup de contention. Ils ont déplacé les paniers dans Redis et mis en place un write-behind vers MariaDB dans un worker background.
La latence s’est améliorée. Tout le monde était content. Puis un nœud Redis a rebooté pendant une mise à jour de kernel de routine. La file du worker background a pris du retard, puis est tombée derrière, puis a commencé à réessayer. Certaines mises à jour ont été appliquées dans le mauvais ordre. Un sous-ensemble de paniers a été réinitialisé ou a dupliqué des articles, selon le pattern de retry.
L’incident n’a pas été un effondrement total ; il a été pire : une corruption partielle. Le système de checkout a dû ajouter des contrôles défensifs, et le support a dû gérer des clients frustrés qui juraient avoir « ajouté l’article trois fois ». Ils avaient raison.
Ils ont rollbacké le modèle : MariaDB reste le système de référence pour les paniers, et Redis est utilisé en cache-aside pour le rendu des paniers, plus une petite structure Redis pour des « flags dirty » afin de réduire les recalculs. Ils ont aussi introduit des tokens d’idempotence sur les requêtes d’écriture pour que les retries ne dupliquent pas les opérations.
La leçon : write-behind est une taxe de correction que vous payez plus tard avec intérêts. Si vous ne pouvez pas articuler l’ordre et la sémantique de retry, ne le faites pas.
Histoire 3 : Pratique ennuyeuse mais correcte qui a sauvé la mise (servir périmé + clés versionnées)
Un produit SaaS avait un tableau de bord qui frappait MariaDB avec une requête d’agrégation lourde. Ils ont construit une couche cache-aside Redis. Ça a aidé, mais un pic de trafic aligné avec une expiration massive de cache écrasait parfois la base.
Au lieu de courir après des idées brillantes, ils ont implémenté deux patrons ennuyeux : clés versionnées et « servir périmé pendant révalidation ». Chaque carte du dashboard avait un soft TTL (servir le cache) et un hard TTL (recalcul obligatoire). Un mécanisme single-flight assurait qu’un seul recalcul par clé ait lieu à la fois.
Ils ont aussi utilisé des clés versionnées pour que l’invalidation devienne un simple incrément atomique d’un numéro de version, plutôt que des tempêtes de delete. Les anciennes clés ont expiré naturellement.
Quelques semaines plus tard, MariaDB a connu un petit wobble de performance I/O pendant une maintenance de stockage. Le dashboard est resté réactif en servant des données légèrement périmées pendant quelques minutes. Le support n’a rien entendu. L’équipe l’a remarqué seulement parce que le monitoring était assez bon pour être légèrement agaçant.
La leçon : la meilleure fonctionnalité de mise en cache est la dégradation contrôlée. Quand les choses cassent, vos utilisateurs ne devraient pas le savoir.
Listes de contrôle / plan étape par étape
Étape par étape : choisir MariaDB, Redis ou les deux
- Classifier les données :
- Faits transactionnels → MariaDB.
- État éphémère → Redis (avec TTL).
- Modèles de lecture dérivés → tables MariaDB ou Redis selon le besoin d’interrogation.
- Choisir le pattern de mise en cache :
- Par défaut : cache-aside avec TTL + jitter.
- Forte exigence de fraîcheur & forte lecture : invalidation pilotée par événements ou clés versionnées.
- Éviter write-behind sauf si les pertes sont acceptables et les sémantiques éprouvées.
- Décider du budget de staleness : par endpoint, explicitement. « Quelques secondes » n’est pas un plan ; notez-le.
- Concevoir la protection contre les stampedes : single-flight, stale-while-revalidate, et/ou early refresh.
- Capacity plan Redis : maxmemory, politique d’éviction, headroom, couverture TTL des clés, sizing des valeurs.
- Garder MariaDB saine : revue d’index, revue de requêtes, dimensionnement buffer pool, monitoring I/O, contrôles de réplication.
- Définir le comportement en cas de panne :
- Si Redis est down, basculez-vous en open (DB-only) ou closed ?
- Si la DB est lente, servez-vous du périmé ou retournez-vous des erreurs ?
- Instrumenter le minimum : hit rate du cache, p95 latence pour Redis et DB, lag des réplicas, mémoire Redis, slow queries DB.
- Organiser un game day : redémarrer Redis, simuler éviction, injecter du lag de réplica. Assurez-vous que le site se dégrade comme prévu.
Checklist : configuration Redis sûre pour la mise en cache
- Définir maxmemory et une maxmemory-policy délibérée.
- Assurer que la plupart des clés de cache ont un TTL.
- Alerter sur used_memory/maxmemory, blocked_clients et evicted_keys.
- Éviter les commandes bloquantes en production (
KEYS, grosSORT, énormesHGETALLsur des hashes géants). - Garder une marge mémoire pour éviter fragmentation/tempêtes d’éviction.
Checklist : usage sûr de MariaDB avec Redis
- Rendre explicites les endpoints read-after-write ; ne pas les servir depuis des réplicas en retard.
- Utiliser des tables de synthèse pour les agrégations lourdes qui nécessitent filtrage et indexation.
- Ne pas implémenter des rate limits ou compteurs chauds comme mises à jour de lignes sur le primaire.
- Surveiller les deadlocks et lock waits ; ils indiquent où déplacer les charges de coordination vers Redis.
FAQ
1) Redis peut-il être un jour le système de référence ?
Seulement si vous êtes prêt à l’exploiter comme une base primaire : persistance, stratégie de réplication, sauvegardes, tests de restauration et gestion stricte de capacité. Pour la plupart des sites : non. Gardez la vérité transactionnelle dans MariaDB.
2) Si MariaDB met déjà en cache dans le buffer pool, pourquoi utiliser Redis ?
MariaDB met en cache des pages, pas les résultats calculés par votre application. Redis aide lorsque le travail coûteux est des joins/agrégations, la sérialisation, le rendu de template, les vérifications de permissions, ou des primitives de coordination.
3) Quel est le pattern de mise en cache par défaut le plus sûr ?
Cache-aside avec TTL + jitter, plus single-flight ou stale-while-revalidate pour les clés chaudes. Cela garde MariaDB autoritaire et rend la perte du cache survivable.
4) Comment prévenir les données périmées sans supprimer un million de clés ?
Utilisez des clés versionnées. Incrémentez un numéro de version lors des écritures, lisez depuis le nouvel espace de noms, laissez les anciennes clés expirer. C’est plus évolutif que des tempêtes de delete.
5) Le write-through est-il meilleur que cache-aside ?
Write-through peut réduire la staleness mais augmente la complexité du chemin d’écriture et le couplage à la disponibilité de Redis. Utilisez-le quand vous avez besoin de lectures mises en cache fraîches et pouvez dégrader en sécurité si Redis est down.
6) Pourquoi write-behind est-il si risqué ?
Parce que vous renvoyez le succès avant que les données ne soient durables dans MariaDB. Crashs, retries et réordonnancements deviennent des bugs de correction. Ne l’utilisez que pour des données non critiques, agrégables et avec mises à jour idempotentes.
7) Quelle politique d’éviction utiliser pour Redis en tant que cache ?
Si tout est cache : allkeys-lfu est un bon défaut. Si seules les clés TTL doivent être évincées : volatile-lfu. Évitez noeviction sauf si vous voulez des pannes dures lorsque la mémoire est pleine.
8) Comment savoir si Redis aide vraiment ?
Mesurez le hit rate par endpoint et comparez le QPS/latence MariaDB avant et après. Si les misses restent élevées ou la charge DB ne baisse pas, vous cachez la mauvaise chose ou cléz mal.
9) Puis-je stocker les sessions dans MariaDB plutôt que Redis ?
Vous pouvez, mais cela crée souvent de la contention d’écriture et du churn de nettoyage. Redis avec TTL est généralement mieux adapté. Si les sessions sont vraiment critiques, concevez la réauthentification et les attentes de persistance explicitement.
10) Et pour mettre en cache des pages HTML rendues ?
Excellente idée quand la personnalisation est limitée. Mettez en cache les pages entières ou des fragments dans Redis, et invalidez lors de changements de contenu via des événements ou de la versioning. Ne mettez pas en cache des pages par utilisateur à moins d’avoir calculé la cardinalité des clés.
Prochaines étapes réalistes pour cette semaine
- Choisir trois endpoints ayant le coût DB le plus élevé. Mesurez leurs patterns de requêtes et décidez quoi mettre en cache : objets, fragments ou tables de synthèse.
- Ajouter cache-aside avec TTL + jitter pour un endpoint, et instrumenter le hit rate, la latence p95 et l’impact sur le QPS DB.
- Implémenter la protection contre les stampedes (single-flight ou stale-while-revalidate) pour un chemin de clé chaude.
- Définir maxmemory et politique d’éviction Redis délibérément, et alerter quand la marge mémoire diminue.
- Auditer « pas de perte de données » : assurer que les faits transactionnels sont dans MariaDB avec sauvegardes et tests de restauration ; s’assurer que Redis contient seulement ce que vous pouvez perdre ou persister correctement.
- Faire un drill de redémarrage : redémarrer Redis en heures ouvrables de manière contrôlée, observer le comportement, et corriger ce qui casse avant que cela ne casse vraiment.
Si vous ne faites rien d’autre : arrêtez de traiter « cache » comme un autocollant magique de performance. Traitez-le comme un second système avec ses propres modes de défaillance — et concevez d’abord pour ces défaillances.