Aller au contenu
Ingénierie · 5 min de lecture

Invalider un cache est plus simple quand il sait expirer seul

L'affirmation Le plus difficile dans la mise en cache n'est pas de stocker une valeur ; c'est de savoir quand la valeur stockée est devenue fausse. Les équipes se tournent vers des...

A Rédigé par Administrator
Invalider un cache est plus simple quand il sait expirer seul

L'affirmation

Le plus difficile dans la mise en cache n'est pas de stocker une valeur ; c'est de savoir quand la valeur stockée est devenue fausse. Les équipes se tournent vers des schémas d'invalidation élaborés — suivre chaque endroit où une donnée est mise en cache et la supprimer à l'instant où la source change — et ces schémas sont où vivent les bogues de cache, car ils exigent d'anticiper correctement chaque chemin d'écriture pour toujours. Un court délai d'expiration est un outil plus grossier qui règle l'essentiel du problème sans aucune coordination, et pour la plupart des données de petite entreprise, c'est le bon choix par défaut.

Pourquoi l'invalidation explicite est si sujette aux erreurs

L'invalidation explicite signifie que chaque fois qu'une donnée change, du code quelque part doit se rappeler de retirer ou de mettre à jour chaque copie en cache de celle-ci. Cela fonctionne jusqu'à ce que ça ne fonctionne plus, et cela cesse de fonctionner de façon prévisible : quelqu'un ajoute un nouvel endroit qui écrit la donnée, ou un nouvel endroit qui la met en cache, et oublie de câbler l'invalidation entre eux. Désormais, le cache sert une valeur périmée indéfiniment, car rien ne lui a jamais dit d'expirer, et le bogue est invisible jusqu'à ce qu'un client remarque qu'il voit de l'information ancienne — un prix qui a changé, une disponibilité qui ne s'est pas mise à jour, un statut faux.

La difficulté croît avec le système. Chaque nouvelle fonction est un autre chemin d'écriture à retenir, un autre cache à invalider, une autre chance d'en manquer un. L'invalidation explicite vous demande de maintenir une carte complète et correcte de chaque dépendance de données de votre application, parfaitement synchronisée à mesure que l'application change, pour toujours. C'est une norme que presque aucune équipe n'atteint.

L'expiration transforme la justesse en une borne temporelle

Une durée de vie sur l'entrée de cache change la garantie. Au lieu de promettre que le cache est toujours à jour, vous promettez qu'il n'est jamais périmé de plus de N secondes, et vous obtenez cette promesse gratuitement, sans suivre aucun chemin d'écriture :

cache.set(cle, valeur, ttl=60)   # cette valeur s'autodetruit dans 60 secondes

Désormais, la péremption au pire cas est bornée et connue : soixante secondes. Si la donnée sous-jacente change, le cache n'a pas besoin qu'on le lui dise — il ira chercher des données fraîches en une minute de toute façon, car l'ancienne entrée cesse simplement d'exister. Vous avez échangé une fraîcheur parfaite contre une péremption petite, bornée et prévisible, et en retour vous avez éliminé toute la classe de bogues où une invalidation oubliée sert des données périmées pour toujours. Pour la plupart des données, c'est un excellent marché.

Choisissez la durée de vie selon la péremption tolérable

Le bon délai d'expiration est une question d'affaires, pas technique : à quel point cette donnée précise peut-elle être périmée avant de causer un vrai problème ? La réponse varie énormément par type de donnée, et ajuster la durée de vie à la tolérance est tout le savoir-faire :

DonnéePéremption tolérableDurée de vie
Description de produitMinutes à heures1 heure
Stock affiché sur une ficheSecondes à une minute30 secondes
Solde de compteDoit être à jourNe pas mettre en cache
Contenu de la page d'accueilMinutes5 minutes

La dernière ligne est la discipline importante : certaines données ne doivent jamais être périmées, et pour celles-là la réponse n'est pas un cache astucieux mais aucun cache. Un solde financier, une permission de sécurité, la réponse définitive à « ce paiement a-t-il réussi » — celles-ci sont lues de la source à chaque fois, car le coût d'en montrer une fausse dépasse toute économie de performance.

Combinez expiration et invalidation, ne remplacez pas l'une par l'autre

L'expiration et l'invalidation explicite ne sont pas rivales ; le motif le plus fort utilise l'expiration comme filet sous l'invalidation. Invalidez explicitement là où vous le pouvez, pour la correction rapide — quand un prix change, videz son entrée de cache pour que le changement paraisse immédiatement — mais fixez aussi une durée de vie, pour que toute invalidation oubliée ou échouée s'autocorrige dans la fenêtre d'expiration. L'invalidation explicite vous donne la vitesse quand elle fonctionne ; la durée de vie vous donne la justesse quand l'invalidation échoue. Vous obtenez le meilleur cas de mises à jour immédiates et le pire cas de péremption bornée, au lieu du meilleur cas de mises à jour immédiates et du pire cas de péremption permanente.

La ruée à surveiller

L'expiration introduit un mode de défaillance à connaître : quand une entrée en cache populaire expire, chaque requête qui la voulait rate maintenant d'un coup et elles frappent toutes la base simultanément pour recalculer la même valeur — une ruée qui peut submerger la source précisément parce que l'élément était populaire. Le correctif est de laisser la première requête recalculer pendant que les autres servent brièvement l'ancienne valeur légèrement périmée, ou d'ajouter un petit décalage aléatoire aux délais d'expiration pour que de nombreuses entrées n'expirent pas toutes au même moment :

ttl = 60 + aleatoire(0, 10)   # etaler les expirations pour qu'elles ne s'alignent pas

Cela ne compte que pour vos éléments en cache les plus chauds, mais pour ceux-là c'est la différence entre un rafraîchissement en douceur et un pic de charge périodique chaque fois que l'entrée populaire se renouvelle.

Le choix par défaut à adopter

Commencez par l'expiration, pas l'invalidation. Pour chaque chose que vous mettez en cache, demandez à quel point elle peut être périmée, fixez une durée de vie assortie, et n'ajoutez l'invalidation explicite par-dessus que là où la fenêtre de péremption est encore trop lente pour un cas important précis. Cela inverse l'ordre habituel — la plupart des équipes bâtissent d'abord une invalidation élaborée et n'ajoutent jamais l'expiration — et cela produit une mise en cache correcte par défaut, car le pire qui puisse arriver est une péremption bornée plutôt que la fausseté permanente qu'une invalidation manquée produit en silence. Le cache qui expire seul ne peut pas servir de données périmées pour toujours, et cette garantie vaut plus que la fraîcheur que vous cédez pour l'obtenir.

#caching #performance #architecture #correctness

À lire aussi