Pour mettre en cache une API à fort trafic, mesurez d'abord, puis placez une couche cache-aside dans Redis devant les endpoints les plus lents et les plus lus, avec des TTL explicites, une invalidation à l'écriture et une protection contre les ruées sur le cache (cache stampede). Utilisez Elasticsearch pour la recherche et les listes filtrées que la base principale gère mal, et ne mettez jamais en cache des données propres à un utilisateur ou strictement cohérentes sans une conception délibérée.
Mesurez la latence P95 avant d'ajouter le moindre cache
Les moyennes masquent les requêtes dont se plaignent les utilisateurs. Suivez la latence P95 et P99 par endpoint, ainsi que le volume de requêtes, pour voir quelles routes sont à la fois lentes et très sollicitées.
Cherchez ensuite où passe le temps. Une trace ou une simple décomposition des temps par requête montre généralement si le coût vient d'une requête lente, de requêtes répétées, d'un appel à une API externe ou de la sérialisation. Mettre en cache une réponse dont le vrai problème est un index manquant ne fait que masquer ce problème jusqu'au prochain défaut de cache.
- Enregistrez P50, P95 et P99 par endpoint, pas un seul chiffre global
- Classez les endpoints par volume de requêtes multiplié par la latence pour voir où le cache rapporte le plus
- Vérifiez le ratio lectures/écritures : les données lues bien plus souvent qu'elles ne changent sont les meilleures candidates
- Corrigez les index manquants et les requêtes N+1 avant de les contourner avec un cache
Faites du cache-aside le modèle par défaut
Avec le cache-aside, l'application interroge d'abord Redis. En cas de succès, elle renvoie la valeur en cache. En cas d'échec, elle lit la base de données, écrit le résultat dans Redis avec un TTL et le renvoie.
Ce modèle garde la base de données comme source de vérité et échoue de manière sûre. Si Redis est lent ou indisponible, l'application se rabat sur la base de données, avec un délai d'attente court et un disjoncteur (circuit breaker) pour qu'un cache en difficulté ne ralentisse pas toutes les requêtes.
Concevez les clés avec soin. Incluez le type de ressource, l'identifiant, la version de l'API et chaque paramètre qui modifie la réponse, comme la langue ou la page. Un schéma de clés prévisible est ce qui rend possible, plus tard, une invalidation ciblée.
Fixez les TTL selon la fraîcheur requise, puis invalidez à chaque modification
Un TTL est une décision métier exprimée en chiffre. Demandez-vous jusqu'à quel âge une donnée reste acceptable avant de nuire à un utilisateur ou à un système en aval, et fixez le TTL à partir de cette réponse. Le score d'un match en direct et un article archivé ne tolèrent pas du tout la même ancienneté.
Se fier uniquement à l'expiration revient à servir des données périmées jusqu'à la fin du TTL. Pour les données que votre application modifie elle-même, supprimez ou écrasez la clé une fois l'écriture validée, idéalement à partir d'un événement émis après la transaction, pour que le cache ne contienne jamais des données annulées par la base.
Ajoutez une petite variation aléatoire (jitter) aux TTL pour que des clés écrites en même temps n'expirent pas toutes au même instant.
- TTL court de quelques secondes pour les données qui changent vite et dont une légère ancienneté est acceptable
- TTL plus long et invalidation à l'écriture pour les données que vous maîtrisez et qui changent rarement
- Clés versionnées : incrémenter un numéro de version invalide d'un coup tout un groupe de clés
Protégez la base de données contre les ruées sur le cache
Une ruée se produit quand une clé populaire expire et que de nombreuses requêtes concurrentes la manquent en même temps, envoyant toutes la même requête coûteuse à la base de données. Sur une API à fort trafic, cela peut surcharger la base précisément au moment du pic de trafic.
Combinez au moins deux de ces défenses sur vos clés les plus sollicitées.
- Regroupement des requêtes : une seule requête reconstruit la clé sous un verrou Redis court posé avec NX et une expiration, pendant que les autres attendent brièvement ou servent la valeur précédente
- Stale-while-revalidate : stockez une expiration souple dans la valeur, continuez à servir la copie périmée au-delà, et rafraîchissez en arrière-plan
- Rafraîchissement probabiliste anticipé : reconstruisez de temps en temps une clé très sollicitée avant son expiration, avec une probabilité qui augmente à l'approche de l'échéance
- Préchauffage : alimentez les clés très sollicitées connues avant un pic de trafic prévu, comme un grand événement en direct
Utilisez Elasticsearch pour la recherche et les listes, pas comme cache généraliste
Redis excelle pour les accès clé-valeur : un objet unique, un fragment calculé, un compteur de limitation de débit. Elasticsearch répond à un autre besoin : la recherche plein texte, les filtres à facettes et les listes triées, coûteux à calculer dans une base relationnelle.
Traitez l'index Elasticsearch comme un modèle de lecture alimenté depuis la base principale, par des événements de modification ou une synchronisation planifiée, et acceptez qu'il soit cohérent à terme. Gardez la base de données comme référence pour les écritures et pour tout ce qui doit être exact.
Sur la plateforme de médias sportifs de NorthStar Network, qui sert 50M+ utilisateurs mensuels, nos ingénieurs ont refondu l'architecture d'API critiques et repensé le cache entre Redis et Elasticsearch, ce qui a réduit les temps de réponse P95.
Sachez ce qu'il ne faut pas mettre en cache
Le bug de cache le plus coûteux consiste à servir les données d'un utilisateur à un autre. Vérifiez que l'identité figure dans la clé de chaque endpoint mis en cache, et testez-le avec deux comptes différents avant la mise en production.
- Les réponses qui dépendent de l'identité ou des permissions de l'appelant, sauf si la clé inclut l'utilisateur ou le rôle
- Les données qui doivent être strictement cohérentes, comme les soldes, le stock au moment du paiement ou tout ce qui sert à des décisions d'autorisation
- Les endpoints d'écriture, les jetons à usage unique et tout ce qui a des effets de bord
- Les endpoints à faible trafic, où un cache ajoute de la complexité et un nouveau mode de défaillance pour un gain minime
- Les charges utiles très volumineuses, qui évincent de nombreuses clés plus petites et plus sollicitées
Rendez le cache observable, sinon vous ne pouvez pas vous y fier
Suivez le taux de succès par préfixe de clé, l'utilisation mémoire et les évictions de Redis, la latence des commandes Redis et la charge de la base de données, à côté du P95 de l'API, sur le même tableau de bord. Quand le P95 bouge, vous devez pouvoir dire en quelques minutes si la cause est le cache, la base de données ou un service en amont.
Déclenchez une alerte sur une chute soudaine du taux de succès, qui signifie souvent qu'un déploiement a changé un format de clé, et sur la hausse des évictions, qui signifie que le cache est trop petit pour son jeu de données actif.
Nous prenons en charge les travaux de performance d'API comme un lot défini : mesure, refonte de la couche de cache, puis remise des tableaux de bord et des runbooks à l'équipe qui l'exploite.
À retenir
- Mesurez le P95 par endpoint et corrigez les problèmes de requêtes avant d'ajouter un cache.
- Le cache-aside dans Redis est le choix par défaut le plus sûr, car la base de données reste la source de vérité.
- Fixez les TTL selon l'ancienneté tolérable des données, et invalidez à l'écriture pour les données que vous maîtrisez.
- Protégez les clés très sollicitées contre les ruées par verrouillage, stale-while-revalidate ou préchauffage.
- Utilisez Elasticsearch comme modèle de lecture cohérent à terme pour la recherche et les listes, pas comme cache généraliste.
FAQ
Faut-il utiliser Redis ou Elasticsearch pour mettre en cache les réponses d'API ?
Utilisez Redis pour le cache clé-valeur d'objets, de fragments calculés et de compteurs, car les accès par clé sont rapides et simples à invalider. Utilisez Elasticsearch quand le coût vient de la recherche, du filtrage ou du tri sur de nombreux enregistrements, et traitez son index comme un modèle de lecture plutôt que comme un cache.
Quel est un bon TTL pour des réponses d'API ?
Il n'existe pas de valeur universelle. Fixez chaque TTL selon l'ancienneté que la donnée peut avoir sans nuire à un utilisateur, utilisez quelques secondes pour les données qui changent vite, et associez les TTL plus longs à une invalidation à l'écriture. Ajoutez une variation aléatoire pour que des clés liées n'expirent pas ensemble.
Comment éviter une ruée sur le cache dans Redis ?
Ne laissez qu'une seule requête reconstruire une clé expirée en prenant un verrou court avec SET et l'option NX, et faites attendre brièvement les autres requêtes ou servez-leur la valeur précédente. Le stale-while-revalidate et le rafraîchissement anticipé des clés très sollicitées réduisent en amont le nombre d'expirations brutales.