MAISON CODE .
/ Tech · Backend · Redis · Caching · Performance · Scale

Mise en cache Redis : l'architecture des millisecondes

Pourquoi votre base de données est le goulot d'étranglement. Un guide technique approfondi sur les modèles Redis (Cache-Aside, Write-Through), l'invalidation de balises et HyperLogLog.

AB
Alex B.
Mise en cache Redis : l'architecture des millisecondes

Dans la hiérarchie des latences, le réseau est lent, le disque est lent, mais la RAM est instantanée.

  • Requête PostgreSQL (SSD) : 10 ms - 100 ms.
  • Requête Redis (RAM) : 0,2 ms.

Cette différence de vitesse 500x est la seule raison pour laquelle les applications Web à grande échelle restent en ligne. Si Amazon accédait à sa base de données SQL pour chaque vue de produit lors du Prime Day, tout Internet tomberait en panne.

Chez Maison Code Paris, nous traitons Redis non seulement comme un « Cache », mais comme un serveur principal de structure de données. C’est la couche tactique qui protège la couche stratégique (la Base de Données).

Pourquoi Maison Code en parle

Chez Maison Code Paris, nous agissons comme la conscience architecturale de nos clients. Nous héritons souvent de stacks “modernes” construites sans compréhension fondamentale de l’échelle.

Nous abordons ce sujet car il représente un point de pivot critique dans la maturité de l’ingénierie. Une mise en œuvre correcte différencie un MVP fragile d’une plateforme résiliente de niveau entreprise.

Les modèles : comment mettre en cache correctement

La mise en cache est simple. L’invalidation du cache est impossible. Nous utilisons deux principaux modèles de mise en œuvre.

1. Cache-Aside (chargement paresseux)

C’est la valeur par défaut. L’application est responsable de la lecture/écriture.

// db/repo.ts
importer { redis } depuis './redis' ;

fonction asynchrone getProduct (id : string) {
  clé const = `produit :${id}` ;
  
  // 1. Vérifier le cache
  const cached = wait redis.get(key);
  if (mis en cache) return JSON.parse(cached);
  
  // 2. Cache Miss -> Vérifier la base de données
  const data = wait db.product.findUnique({ id });
  
  // 3. Remplir le cache
  si (données) {
     // Cache pendant 60 secondes (TTL)
     wait redis.set(key, JSON.stringify(data), 'EX', 60);
  }
  
  renvoyer des données ;
}
  • Avantages : Résilient. Si Redis meurt, l’application fonctionne (lentement).
  • Inconvénients : fenêtre de données périmées. L’utilisateur peut voir l’ancien prix pendant 60 s.

2. Write-Through (invalidation basée sur des événements)

Nous préférons cela pour les données à haute cohérence (comme l’inventaire). Nous écoutons les modifications de la base de données (CDC ou événements d’application) et supprimons le cache.

// services/produit.ts
fonction asynchrone updatePrice(id : chaîne, prix : nombre) {
  // 1. Mettre à jour la base de données (Source de vérité)
  wait db.product.update({ où : { id }, data : { price } });
  
  // 2. Invalider le cache immédiatement
  attendre redis.del(`product:${id}`);
}

Le problème le plus difficile : l’invalidation par relation (Tags)

Imaginez que vous cachez :

  1. produit : 123
  2. « catégorie : chaussures » (Contient le produit 123)

Si vous mettez à jour le produit 123, vous supprimez « product:123 ». Mais category:shoes contient toujours l’ancienne version du produit ! Vous devez également savoir quelles catégories contiennent la clé 123.

Nous implémentons l’Invalidation basée sur les balises à l’aide des ensembles Redis.

  1. Lors de la création de « category:shoes », nous suivons les dépendances : Balises SADD : produit : 123 catégorie : chaussures
  2. Lors de la mise à jour du produit 123 :
    • Recherchez toutes les clés qui en dépendent : SMEMBERS tags:product:123 -> Renvoie ['category:shoes'].
    • Supprimez-les : DEL catégorie : chaussures.
    • Nettoyez le jeu de balises.

Cette logique est complexe mais résout le problème « Pourquoi la page d’accueil affiche-t-elle toujours l’ancienne image ? » bug pour toujours.

Sérialisation : le coût caché du processeur

Le stockage des chaînes JSON (JSON.stringify) est standard mais inefficace.

  1. Stockage : JSON est détaillé. "active": true prend 15 octets.
  2. CPU : JSON.parse bloque la boucle d’événements. Pour une charge utile de 1 Mo (grande page d’accueil), l’analyse prend 20 ms de temps CPU.

Solution : MessagePack (msgpack). Il s’agit d’un format de sérialisation binaire. C’est plus rapide et plus petit.

importer {pack, unpack } depuis 'msgpackr' ;
importer Redis depuis 'ioredis' ;

const redis = nouveau Redis ({
  // ioredis prend en charge les tampons binaires
  keyPrefix : 'v1:',
});

wait redis.set('key', pack(largeObject)); // Stocke le tampon
const buffer = wait redis.getBuffer('key');
const obj = décompresser (tampon);

Les benchmarks montrent que msgpack est 3 fois plus rapide à encoder/décoder que le JSON natif pour les grandes structures.

Structures avancées : pas seulement des clés et des valeurs

Redis est un « serveur de structure de données ». Arrêtez de l’utiliser comme un Map<String, String>.

1. HyperLogLog (Comptage approximatif)

Problème : « Comptez les visiteurs uniques aujourd’hui. » Naïf : stockez chaque adresse IP dans un ensemble. SADD {ip}. Pour 100 millions d’utilisateurs, cela nécessite 1,5 Go de RAM. Solution Redis : Visiteurs PFADD {ip}. HyperLogLog utilise des mathématiques probabilistes (hachages). Il compte 100 millions d’éléments uniques avec 12 Ko de RAM. La fiabilité est de 99,19 %. Avez-vous besoin d’un nombre exact de visiteurs ? Non. Vous devez savoir “Est-ce 10 000 ou 100 000 ?”. HLL est parfait.

2. Index géospatiaux

Problème : « Trouver des magasins à proximité de chez moi ». Naïf : formule SQL Haversine (lent). Solution Redis : Magasins GEOADD 2.3522 48.8566 "Paris". « GEORADIUS stocke 2,35 48,85 10 km » renvoie les résultats en microsecondes en utilisant le tri Geohash.

3. Limitation du débit (le compartiment de jetons)

Nous protégeons nos API contre les DDoS à l’aide de compteurs Redis. Nous utilisons un script Lua (pour assurer l’atomicité) qui implémente l’algorithme “Sliding Window”. Clé INCR. Touche EXPIRER 60. Si > 100, Rejetez.

Le problème du troupeau tonitruant

Si une fonctionnalité de cache est vitale, son échec est catastrophique. Scénario :

  1. La clé home_page expire à 12h00:00.
  2. À 12:00:01, 5 000 utilisateurs demandent la page d’accueil.
  3. 5 000 requêtes obtiennent un « Cache Miss ».
  4. 5 000 requêtes parviennent simultanément à la base de données Postgres.
  5. Le tampon de sortie de la base de données se remplit. La base de données plante.
  6. Le site Web tombe en panne.

Solution : Verrouillage du cache (Le Mutex). Lorsqu’un « Miss » se produit, le code tente d’acquérir un verrou (SETNX lock:home_page).

  • Gagnant : génère la page. Écrit dans le cache. Libère le verrouillage.
  • Perdants : attendez 100 ms. Vérifiez à nouveau le cache. Résultat : seule 1 requête atteint la base de données.

10. Redis Cluster vs Sentinel (haute disponibilité)

Single Node Redis est un point de défaillance unique. Sentinel : surveille le maître. Bascule vers Replica. Cluster : partage les données sur plusieurs nœuds. Pour le e-commerce, on a rarement besoin de Cluster (le sharding est complexe). Une instance Redis peut gérer 100 000 opérations/s. Nous préférons Sentinel. Mais attention : la configuration du client pour Sentinel est délicate. redis = new Redis({ sentinels : [{ hôte : 'sentinel-1', port : 26379 }], nom : 'mymaster' }). Si vous codez en dur l’adresse IP principale, le basculement ne fonctionnera pas.

11. Lua Scripting : L’atomicité est reine

Vous souhaitez « Vérifier le solde, déduire de l’argent ». Faire cela dans Node.js (Get -> Logic -> Set) est une condition de concurrence. Nous écrivons des Lua Scripts qui s’exécutent dans Redis. Les scripts Lua sont atomiques. Aucune autre commande ne s’exécute pendant l’exécution du script. Cela garantit que la « déduction d’inventaire » est parfaitement cohérente, même avec 500 acheteurs simultanés.

13. Stratégies de réchauffement du cache intelligent

Attendre que le premier utilisateur réussisse un « Miss » et paie la pénalité de latence est une mauvaise UX. Nous implémentons Active Cache Warming.

  1. Lors du déploiement : Déclenche un script warm-cache.ts.
  2. Logique : récupère les 500 principales URL de Google Analytics (via l’API).
  3. Action : accède à l’API interne localhost:3000/api/render?url={top_url}. Cela oblige le serveur à générer la page et à remplir Redis avant le trafic. Le résultat est une latence de 0 ms pour 80 % des utilisateurs immédiatement après un déploiement.

14. Au-delà de la valeur-clé : Redis Stack (Recherche et JSON)

La nouvelle Redis Stack vous permet d’exécuter des requêtes de type SQL sur des documents JSON. FT.SEARCH productIdx "@title:Shoes @price:[0 100]" cela s’exécute en mémoire. Il est 50 fois plus rapide qu’Elasticsearch pour les petits ensembles de données (<1 million d’éléments). Nous l’utilisons pour les fonctionnalités de “Recherche instantanée” où Algolia est trop cher ou trop lent.

15. Conclusion

Redis est l’outil le plus puissant de la ceinture utilitaire de l’ingénieur backend. Il comble le fossé entre le monde rigide et lent des bases de données ACID et le volume de requêtes chaotique et rapide du Web moderne.

Mais cela demande de la discipline. Un cache sauvage (sans TTL, sans politique d’expulsion et avec d’énormes clés) est une fuite de mémoire attendant de faire planter votre serveur.


**[Engagez nos Architectes](/contact)**.