← Retour au blog
Architecture

Une histoire de caching : d'où vient la performance d'un cache ?

Comment un cache accélère vos applications web : stockage en mémoire, proximité des données (CDN), algorithmes LRU/LFU, compression et le piège de la single source of truth.

📅 ✍️ Antoine Coulon
cachingperformancerediscdnlru-lfu

Ajouter un cache à une application, c’est ajouter un composant de plus à maintenir, à surveiller et à faire vivre. Pourtant, c’est l’un des leviers de performance les plus rentables qui soient. Comment un simple intermédiaire entre votre application et sa source de données peut-il diviser des temps de réponse par dix, voire par cent ? La rapidité d’un cache n’a rien de magique : elle découle de quelques principes physiques et algorithmiques bien identifiés.

Dans le premier volet de cette série, nous avons posé les bases du caching appliqué au web : ce qu’est un cache, où il se place, et pourquoi on en a besoin. Ce deuxième épisode s’attaque à la question du pourquoi ça va plus vite. Comprendre d’où vient réellement le gain de performance, c’est se donner les moyens de bien dimensionner son cache, et d’anticiper les pièges qu’il introduit.

Les quatre sources de la performance d’un cache

La rapidité d’un cache repose sur la combinaison de quatre facteurs : la nature du stockage, l’emplacement de la donnée, les algorithmes d’accès et la compression. Pris ensemble, ils expliquent pourquoi un cache bien conçu surpasse systématiquement un accès direct à la source de données principale.

La nature du stockage : la mémoire plutôt que le disque

Le premier facteur, c’est la donnée est physiquement stockée. Un accès en mémoire RAM est de plusieurs ordres de grandeur plus rapide qu’une opération d’entrée/sortie (I/O) sur un disque. Là où une lecture disque se compte en millisecondes, un accès mémoire se compte en nanosecondes. C’est précisément pour cette raison que les caches privilégient le stockage en mémoire.

Redis et Memcached sont les exemples les plus connus de stores in-memory. Ce sont des bases de données qui gardent l’intégralité (ou l’essentiel) de leurs données en RAM, avec des fonctionnalités complètes : structures de données variées, expiration automatique, réplication, persistance optionnelle sur disque.

Mais on n’a pas toujours besoin d’un serveur dédié. Dans des cas bien précis, ou simplement pour expérimenter, vous pouvez gérer vos propres structures de données en mémoire directement dans votre programme. Une simple HashMap (ou Map en JavaScript) initialisée dans le heap exploite déjà la RAM et offre un accès quasi instantané :

// Un cache applicatif minimaliste, en mémoire heap
const cache = new Map<string, unknown>();

function getUser(id: string) {
  if (cache.has(id)) {
    return cache.get(id); // accès mémoire : quasi instantané
  }

  const user = fetchUserFromDatabase(id); // I/O disque/réseau : coûteux
  cache.set(id, user);
  return user;
}

Ce type de cache local est imbattable en latence, mais il a ses limites : il vit et meurt avec le processus, n’est pas partagé entre plusieurs instances, et consomme la mémoire de votre application. C’est là qu’un store dédié comme Redis prend tout son sens à l’échelle.

L’emplacement de la donnée : plus c’est proche, plus c’est rapide

Le deuxième facteur est la distance. Plus une donnée est proche de celui qui la consomme, plus son accès est rapide. C’est une contrainte physique : l’information ne voyage pas plus vite que la lumière, et chaque kilomètre de réseau ajoute de la latence.

C’est tout l’objectif des CDN (Content Delivery Networks). Un CDN est un ensemble de serveurs répliqués un peu partout dans le monde, chargés de distribuer la donnée au plus près des utilisateurs. Quand un visiteur en Asie demande une ressource hébergée à l’origine en Europe, le CDN la lui sert depuis un point de présence local. La latence n’est jamais nulle, mais elle est considérablement réduite par rapport à un aller-retour transcontinental.

Et il y a encore plus rapide que le CDN : l’environnement local. Le caching le plus performant est celui effectué directement sur la machine de l’utilisateur. La seule latence qui subsiste est alors celle de la communication entre les composants matériels de la machine elle-même. C’est exactement ce que fait votre navigateur : Chrome, par exemple, maintient un cache web local pour éviter de re-télécharger les ressources déjà connues. On peut résumer cette hiérarchie ainsi :

Emplacement du cacheLatence typique
Cache local (navigateur, mémoire applicative)la plus faible
CDN (point de présence régional)faible à modérée
Serveur d’origine / base de donnéesla plus élevée

Concevoir une stratégie de cache, c’est largement décider à quel niveau de cette hiérarchie placer chaque donnée.

Les algorithmes : indexer, hacher, classer

Un cache rapide ne se contente pas de stocker en mémoire : il doit aussi savoir retrouver une donnée instantanément et gérer son contenu intelligemment. C’est le rôle des algorithmes.

Pour la recherche, les caches s’appuient sur de l’indexing et du hashing. Une table de hachage permet de localiser une entrée en temps constant, indépendamment du nombre d’éléments stockés : on calcule l’empreinte de la clé, et on accède directement à l’emplacement correspondant.

Pour la gestion du contenu, des stratégies d’analyse continue permettent de maintenir le cache à une taille raisonnable en supprimant les entrées expirées ou peu utilisées. Deux mécanismes de classement font référence :

Ces algorithmes favorisent l’accès aux données les plus utiles tout en empêchant le cache de croître indéfiniment. Ils sont au cœur des stratégies d’invalidation et d’éviction : un sujet à part entière que nous explorerons en détail dans le troisième et dernier volet de la série.

La compression : transférer et stocker moins

Le quatrième facteur est la compression des données. Compresser une donnée avant de la mettre en cache (et de la transférer) produit trois bénéfices cumulés :

Le coût de la compression (un peu de CPU à l’écriture et à la lecture) est presque toujours largement compensé par les gains sur des données volumineuses ou transférées sur le réseau.

Le revers de la médaille : la single source of truth

Tout ce qui précède explique pourquoi un cache va vite. Mais la performance n’est jamais gratuite : un cache introduit aussi une problématique de fond qu’il faut considérer dès la conception.

Dans la grande majorité des cas, un cache vient en complément d’une source de données principale, et non à sa place. Or ajouter un cache, c’est ajouter une nouvelle source de persistance, donc une nouvelle source de vérité. On se retrouve mécaniquement avec la même information stockée à deux endroits, et le principe de single source of truth (SSOT) est mis à l’épreuve.

Le risque est celui de la cohérence. La dernière chose que l’on souhaite, c’est que certains utilisateurs récupèrent depuis le cache des données périmées ou désynchronisées par rapport à la source principale. Une donnée modifiée dans la base mais toujours présente, inchangée, dans le cache, c’est une incohérence servie à vos utilisateurs.

Pour maîtriser ce risque, un cache doit donc impérativement offrir deux capacités :

Ces deux mécanismes sont ce qui sépare un cache utile d’un cache dangereux. Sans eux, on échange de la performance contre des bugs de cohérence souvent insidieux et difficiles à diagnostiquer.

Conclusion

La performance d’un cache n’a rien d’accidentel : elle vient de la mémoire plutôt que du disque, de la proximité plutôt que de la distance, d’algorithmes d’accès et d’éviction efficaces, et de la compression. Ces quatre leviers, combinés, expliquent pourquoi un cache bien conçu transforme radicalement les temps de réponse d’une application.

Mais cette rapidité a une contrepartie : en dupliquant la donnée, le cache fragilise la single source of truth et fait peser un risque de cohérence sur votre système. La maîtriser passe par une bonne stratégie d’invalidation et d’expiration : c’est précisément le sujet du troisième et dernier volet de cette série, où nous entrerons dans le détail de l’invalidation et de l’éviction de cache.