Une requête qui prend 200ms sur votre base de données prend toujours 200ms la millionième fois que quelqu’un l’exécute, et chacune de ces exécutions se dispute les mêmes connexions, le même I/O disque et le même CPU. Redis garde la réponse en mémoire, sur un serveur qui ne fait que ça, si bien que la millionième lecture coûte un aller-retour réseau au lieu d’un plan de requête.
Ce que Redis fait réellement comme cache
Redis est un magasin clé-valeur en mémoire. Les valeurs vivent en RAM, donc les lectures et écritures prennent moins d’une milliseconde, contre les dizaines ou centaines de millisecondes que coûte une requête relationnelle dès que des jointures, des index sous pression ou un pool de connexions chargé entrent en jeu. Il se place à côté de votre base de données, pas à sa place : votre application interroge d’abord Redis et ne va à la base de données que si Redis n’a pas la réponse.
Le pattern pour ça s’appelle cache-aside (aussi “lazy loading”), et c’est le premier vers lequel se tournent la plupart des applications :
- L’application demande une clé à Redis.
- Cache hit — Redis l’a, il la retourne. La base de données ne voit jamais la requête.
- Cache miss — Redis ne l’a pas. Il interroge la base de données, écrit le résultat dans Redis, puis le retourne.
| |
La base de données reste la source de vérité. Redis garde une copie rapide et jetable des parties que vous lisez souvent. Si Redis redémarre vide, rien n’est perdu : il se remplit tout seul au prochain lot de cache misses.
Comment lancer Redis avec Docker
Le moyen le plus rapide d’avoir une instance Redis est l’image officielle. Si vous n’avez jamais utilisé Docker, Qu’est-ce que Docker et à quoi ça sert couvre les bases avant d’aller plus loin.
| |
Cela démarre Redis 8 en arrière-plan, en publiant le port 6379 sur votre machine. Connectez-vous avec redis-cli, installé localement ou lancé depuis un second container sur le même réseau :
| |
| |
Quand TTL retourne -1, cela signifie que la clé n’a pas d’expiration : elle reste jusqu’à ce que vous la supprimiez ou que Redis l’évince sous la pression mémoire. Pour un cache, vous voulez presque toujours une expiration, pour que les données obsolètes disparaissent d’elles-mêmes.
| |
EX 300 fait expirer la clé dans 300 secondes. PX fait la même chose en millisecondes, et EXPIRE user:42:name 300 pose un TTL sur une clé qui existe déjà.
Pour tout ce qui dépasse une vérification manuelle rapide, exécutez Redis via Compose avec le reste de votre stack — voir Qu’est-ce que Docker Compose et Comment l’Utiliser :
| |
api atteint Redis via le hostname redis parce que Compose place les deux services sur le même réseau défini par l’utilisateur, le mécanisme couvert dans Qu’est-ce qu’un Réseau Docker et Comment l’Utiliser. Le volume redis-data compte moins pour un cache pur que pour une base de données primaire (voir Qu’est-ce qu’un volume Docker et comment l’utiliser) : perdre le cache signifie juste que les lectures suivantes deviennent des cache misses. Gardez-le quand même si un cache chaud après un redémarrage compte pour votre budget de latence.
Comment écrire la logique cache-aside dans votre application
Redis vous donne les commandes ; votre application doit encore les appeler dans le bon ordre. Envelopper une lecture de base de données en cache-aside ressemble à ça :
| |
Deux détails font l’essentiel du travail ici :
- Le TTL (
ex=300) n’est pas optionnel. Sans lui, une ligne qui change dans la base de données continue de servir son ancienne valeur depuis Redis pour toujours. Réglez le TTL sur la durée pendant laquelle une donnée obsolète est acceptable pour cette clé précise, pas sur un chiffre global pour toute l’application. json.dumps/json.loads. Redis stocke des chaînes, plus une poignée de types plus riches comme les hashes et les sorted sets — jamais les objets de votre langage. Tout ce qui est structuré doit être sérialisé à l’entrée et parsé à la sortie.
Quand la ligne sous-jacente change, supprimez la clé au lieu d’attendre le TTL :
| |
La lecture suivant une mise à jour est un cache miss garanti, qui repeuple Redis avec la ligne fraîche. Cache-aside n’essaie jamais de garder le cache synchronisé en temps réel. Il fait disparaître les entrées obsolètes rapidement, par TTL ou par suppression explicite à l’écriture.
Éviction : que se passe-t-il quand Redis manque de mémoire
Redis garde tout en RAM, donc la mémoire est finie d’une façon dont le disque ne l’est généralement pas. Fixez un plafond dur avec maxmemory, et dites à Redis quoi faire une fois ce plafond atteint avec maxmemory-policy :
| Politique | Comportement | À utiliser pour |
|---|---|---|
noeviction | Refuse les nouvelles écritures une fois plein, retourne une erreur | Un datastore qui ne peut pas perdre de données silencieusement |
allkeys-lru | Évince la clé la moins récemment utilisée, n’importe laquelle | Un cache pur — le choix par défaut le plus judicieux |
volatile-lru | Évince la clé la moins récemment utilisée parmi celles avec un TTL | Mélanger clés de cache et clés permanentes dans une instance |
volatile-ttl | Évince en premier la clé avec le TTL restant le plus court | Rate limiters, tokens de courte durée |
Pour une instance de cache dédiée, allkeys-lru est presque toujours le bon choix : chaque clé y est jetable par définition, alors laissez Redis jeter ce que vous avez touché le moins récemment. noeviction est la mauvaise politique pour un cache, parce qu’elle transforme “Redis est plein” en erreurs applicatives au lieu de laisser tomber silencieusement les entrées froides.
| |
Réglez les deux dans la commande de démarrage, comme dans le fichier Compose ci-dessus, pas seulement au runtime. Un redémarrage du container remet à zéro un CONFIG SET posé seulement au runtime, ramenant Redis à ses valeurs par défaut (maxmemory 0, c’est-à-dire illimité, plafonné seulement par l’hôte).
Quand le cache Redis est le mauvais choix
Le cache masque un chemin lent ; il ne le répare pas. Quatre situations où recourir à Redis en premier aggrave les choses :
- Les données changent à chaque lecture de toute façon. Un ticker boursier en direct ou un compteur incrémenté à chaque requête ne gagne rien d’un cache invalidé aussi vite qu’il est peuplé. Vous avez ajouté un aller-retour réseau sans gagner en hit rate.
- Vous avez besoin d’une cohérence forte. Cache-aside est eventually consistent par conception : entre une écriture et le cache miss suivant, il existe une fenêtre où une valeur obsolète peut encore être servie. Pour un solde de compte ou un niveau de stock où cette fenêtre cause un vrai dommage, ne le mettez pas en cache, ou mettez-le en cache avec un TTL de quelques secondes et acceptez le compromis explicitement.
- Le vrai goulot d’étranglement est ailleurs. Si votre base de données est lente à cause d’un index manquant ou d’une requête N+1, réglez ça d’abord. Mettre en cache une requête mal écrite déplace juste la même mauvaise réponse en mémoire, plus vite.
- Un thundering herd à l’expiration. Quand une clé chaude expire, toutes les requêtes concurrentes peuvent manquer le cache en même temps et marteler la base de données simultanément. Utilisez
SET key value NX EX 30comme verrou de courte durée, détenu par la requête qui repeuple le cache, pour que les autres attendent celle-là au lieu de s’entasser sur la base de données.
Par où commencer avec le cache Redis
Placez un container redis:8 devant votre lecture la plus lente et la plus répétée. Enveloppez-la en cache-aside avec un TTL que vous pouvez justifier, pas un chiffre rond choisi au hasard, et réglez maxmemory-policy allkeys-lru pour qu’un cache plein dégrade au lieu d’échouer. Ensuite, mesurez le hit rate. Le write-through caching, l’invalidation par pub/sub et Redis Streams coûtent tous en complexité, et aucun ne vaut le coup avant qu’un simple cache-aside vous ait montré précisément où il atteint ses limites.