Éloge de memcached
(jchri.st)- Un cache est introduit pour réduire la charge de la base de données, mais un outil facile à utiliser comme Redis tend avec le temps à devenir une dépendance assimilée à un stockage persistant
- Le problème n’est pas la fonctionnalité de persistance de Redis, mais le fait qu’un composant introduit au départ comme cache volatil se retrouve mêlé à l’état central de l’application dans les opérations courantes
- memcached est défini officiellement comme un système de cache d’objets en mémoire distribué et ne repose pas sur le stockage disque, ce qui le rend plus facile à traiter comme une charge de travail de cache sans état
- Avec plusieurs instances memcached, ce n’est pas le serveur mais le client qui répartit les données à partir d’une liste d’URL et du hachage des clés ; les nœuds en panne sont retirés du hachage puis des tentatives de reconnexion sont effectuées plus tard
- Avant d’ajouter un cache sous prétexte que « la base de données est lente », il faut d’abord vérifier les requêtes lentes et les index manquants
Le moment où Redis passe de cache à stockage
- Quand on exploite une infrastructure, la demande « il nous faut un cache » revient souvent, et Redis vient facilement à l’esprit en premier parce qu’il est familier et riche en fonctionnalités
- La page d’accueil de Redis met désormais en avant Redis Iris, un moteur de contexte temps réel pour les applications d’IA, mais c’est une orientation compréhensible pour une entreprise qui doit générer des revenus
- Une fois Redis déployé et la chaîne de connexion fournie, il se comporte au départ comme un cache fiable
Les problèmes qui apparaissent quelques mois plus tard
- Avec le temps, comme
cache.set("key", "value")est bien plus simple queINSERT INTO table VALUES ('key', 'value'), les gens commencent à traiter Redis de la manière suivante- comme un composant toujours présent, un endroit où conserver les données ; de fait, une base de données
- REmote DIctionary Server finit par être perçu comme un stockage permanent plutôt que comme un cache volatil
- Ni vous ni vos collègues de l’équipe d’exploitation ne vous en rendez compte, et comme vous pensez que tout le monde considérera le cache comme volatil, le système d’alerting ne le détecte pas non plus
- Le problème ne devient visible qu’au moment d’une mise à niveau, d’un déplacement de nœud, ou d’un incident comme un tiroir de disque dur RAID0 qui se détache du serveur Redis
- Le vrai problème n’est pas que Redis dispose ou non de fonctions de persistance, mais le décalage entre l’hypothèse d’un Redis introduit comme cache et le fait que les gens ne le traitent pas comme un cache
- Lorsqu’on découvre cette dépendance trop tard, Redis est déjà trop profondément imbriqué dans l’application pour être retiré facilement, et il faut finalement le maintenir et le superviser comme un « animal de compagnie »
Pourquoi memcached remplit plus directement le rôle de cache
- memcached est un « système gratuit, open source, haute performance, de cache d’objets en mémoire distribué », un cache générique conçu pour accélérer les applications web dynamiques en réduisant la charge sur la base de données
- Dans des frameworks qui prennent en charge le cache enfichable comme Django, il est possible de changer de backend de cache
- Même s’il offre moins de fonctionnalités que Redis, memcached peut être préférable parce que ses caractéristiques d’exploitation sont plus simples
- La gestion des indisponibilités est facile : les bibliothèques clientes ignorent souvent les exceptions de connexion, et un simple
getpeut renvoyer une valeur par défaut ouNonemême si le serveur est hors service - memcached n’intègre pas de fonctionnalité de cluster, ce qui rend paradoxalement le clustering plus pratique
- Il suffit de configurer plusieurs URL dans la bibliothèque cliente pour qu’elle choisisse l’instance cible via le hachage des clés
- Si un appel client détecte la panne d’une instance, il retire le nœud du hacheur puis tente automatiquement une reconnexion après un certain délai
- La contrainte de persistance est structurellement réduite : memcached n’écrit pas sur disque, ce qui facilite son ordonnancement n’importe où comme charge de travail sans état
- La gestion des indisponibilités est facile : les bibliothèques clientes ignorent souvent les exceptions de connexion, et un simple
- On peut construire un mode d’exploitation similaire avec Redis, mais l’architecture de memcached est plus proche de cette logique, ce qui le rend plus intuitif à traiter comme un cache
- memcached reste une application relativement simple, et le fait qu’il y ait presque aucun surcoût même avec des dizaines d’instances de cache d’environ 64 Mo constitue un argument de choix
- Beaucoup de problèmes du type « la base de données est lente » proviennent en réalité de requêtes lentes ou d’index manquants ; il faut donc examiner aussi l’optimisation des requêtes en parallèle de l’ajout d’un cache
- Si les choix de conception de memcached vous intéressent, le blog memcached contient de nombreux billets passionnants, dont celui publié en mai : « Combien de temps cette réponse prend-elle vraiment ? (How Long Does That Response Take… For Real?) »
1 commentaires
Commentaires sur Hacker News
Redis est une excellente technologie, mais j’ai l’impression qu’il a du mal à bien remplir en même temps deux rôles différents : celui de structure de données persistante et celui de cache volatil
En pratique, même dans Redis, les deux se mélangent mal, puisque la persistance s’active ou se désactive globalement
Pour un cache pur, j’utiliserais memcached ou un équivalent, et je ne prendrais Redis avec la persistance activée que lorsqu’il faut des structures de données comme des tableaux de scores
Chez $WORK, nous n’avons adopté ni l’un ni l’autre, et pour la couche de cache des tâches lentes, nous stockons les données à la fois dans le système de fichiers et dans une table de base de données utilisée comme magasin clé-valeur
La DB aide à coordonner le thundering herd, les lectures depuis le même serveur ne touchent que le système de fichiers, et les lectures depuis d’autres serveurs consultent une fois la DB puis conservent les données dans le système de fichiers
On pourrait remplacer la couche système de fichiers par memcached, mais jusqu’ici cela fonctionne extrêmement bien
Redis avait clairement plus de fonctionnalités, et antirez était quelqu’un de charismatique tout en étant étonnamment humble, donc je comprends pourquoi Redis est devenu plus populaire
Malgré tout, pour moi, memcached a toujours été l’exemple ultime de choisir une technologie ennuyeuse
En tant qu’ingénieur plateforme, je peux prendre en charge les deux, mais quand des développeurs commencent à utiliser les fonctionnalités avancées de Redis comme la persistance, la réplication ou le clustering, j’essaie de vérifier qu’ils comprennent bien les inconvénients de ce choix
Chaque fois que je propose ce genre de solution, je me suis retrouvé d’innombrables fois à me battre sur le terrain avec des gens inexpérimentés qui estiment qu’un cache doit absolument vivre dans un stockage dédié
Dire « memcache » n’évite absolument pas ce genre de problèmes
Au milieu des années 2000, j’ai travaillé sur un système extensible qui utilisait memcache, et les développeurs sont tombés exactement dans les mêmes pièges que ceux cités dans l’article pour Redis
Ils ont essayé de contourner les lois des systèmes distribués avec memcache, et à cause de l’addiction au cache, ils dimensionnaient la flotte de serveurs en partant du principe que memcache serait toujours disponible, si bien qu’en cas de panne cela explosait soudainement comme une attaque DDoS
Il y avait aussi de l’amplification d’écriture : lorsqu’un hôte purgeait une clé à fort TPS, tous les autres hôtes frappaient le service dépendant pour la regénérer, les hot keys créaient des hot hosts, et memcached tournait sur les mêmes machines que les démons de service, ce qui menait à des pics CPU mystérieux
Des appels memcache partaient aussi dans un trou noir à cause de la persistance d’anciennes entrées DNS
Tout cela aurait pu être évité avec un meilleur usage de memcache, mais la tentation de l’abus était trop forte
J’ai l’impression d’avoir vu en production presque tous les problèmes Redis/Valkey mentionnés par l’auteur
Il y a eu un incident où Valkey, faute de politique mémoire, a consommé toute la mémoire et provoqué des erreurs d’écriture de l’append-only file, et un autre où les écritures AOF ont échoué simplement parce que le disque était plein
Il est aussi arrivé que Redis soit vivant, en cours d’exécution et entièrement rempli de données utilisateur, et comme rien ne permettait de revenir vers un chemin lent, cela générait des erreurs 500
J’ai aussi vu des cas où des gens utilisaient de manière créative des sorted sets et d’autres structures de données tout en dépendant du fait que cet ensemble ne serait jamais évincé
Malgré ce retour d’expérience, il me semble encore difficile de recommander memcache avant Redis
Concevoir l’application pour qu’elle ait une disposition de cache compatible avec memcache peut être délicat, et si une équipe suffisamment grande utilise memcache, il est très probable qu’elle finisse par trouver une voie qui nécessite Redis
On se retrouve alors à maintenir 2 technologies de cache
Une instance Redis configurée pour le cache ne peut pas servir à d’autres usages, une instance de cache doit avoir de l’éviction, et une instance non dédiée au cache ne doit pas en avoir
Au final, il faut une deuxième instance Redis avec une configuration différente
Honnêtement, concevoir une application avec une disposition de cache compatible memcache revient au même que la concevoir avec une disposition de cache compatible Redis
Les patterns de cache applicatif sont les mêmes : on récupère, et si la valeur n’existe pas, on la calcule puis on l’écrit
var value = cache.lookup( keyname, () => db.query(...), TimeSpan.FromMinutes(5) // or CacheOptions );De cette façon, on peut soit basculer immédiatement vers le chemin de repli, soit insérer la valeur au moment du cache miss
Une autre caractéristique peu mentionnée de memcache est que toutes les opérations sont en O(1) par conception
C’est un choix d’architecture délibéré des auteurs, qui impose des contraintes, mais garantit qu’il n’y aura pas d’arrêts aléatoires sur des opérations simples
Redis, à l’inverse, avec son cœur mono-thread, peut exécuter des opérations d’une complexité arbitraire ; du point de vue du développeur, cela peut donner l’impression d’être malin en les utilisant, mais tout le reste doit attendre que l’opération se termine
Dans les projets open source ou les programmes maintenus sur le long terme, ce genre de chose arrive souvent
Quand la base de code grossit, on finit par prendre en charge des choses qui n’étaient pas prévues au départ
Quand les fonctionnalités se multiplient, le nombre d’utilisateurs augmente aussi ; certains n’utilisent que les anciennes fonctions, d’autres adoptent les nouvelles, et au final une certaine valeur devient le défaut de fait au point de ne plus sembler optionnelle
En prenant Redis comme exemple, si l’on désactive AOF, il fonctionne comme un cache volatile en mémoire, mais la plupart des gens ne le voient même pas ainsi
D’où l’argument selon lequel moins de fonctionnalités et plus de simplicité valent mieux, et dans ce contexte Memcached est un exemple de cette approche des contraintes bénéfiques
Pour une grande équipe, cela a tout son sens, mais les projets open source ont besoin de mises à jour régulières pour continuer à obtenir du financement ou des contributions, donc il y a une tension inhérente
Cela mène parfois à des forks ou projets dérivés spécialisés dans une niche
Personnellement, je pense qu’il n’y a pas de bonne réponse universelle et que tout dépend du contexte
Parce que la communication non plus n’est pas gratuite
Les développeurs ont l’air de l’ignorer complètement
À mon avis, c’est parce qu’ils ont remplacé Memcached par Redis en s’attendant exactement à la même chose
Cela reste malgré tout un excellent cache
J’ai pas mal travaillé avec Flask ces dernières années, pas à plein temps, mais comme élément de la stack technique d’une petite activité e-commerce
Avec MongoEngine, SQLAlchemy, Celery et les stacks Python pour Google/eBay/Shopify, j’ai rencontré toutes sortes de pièges et d’étrangetés, mais jamais avec Redis
C’est peut-être parce qu’on ne donne pas les droits d’admin à quiconque considère Redis comme un stockage persistant, mais honnêtement je décrirais Redis comme une technologie extrêmement robuste et bien conçue
L’API est d’une simplicité extrême, et dès qu’il faut faire quelque chose d’un peu inhabituel, il existe une manière raisonnable et bien pensée de le faire
J’imagine qu’on peut utiliser des systèmes d’invalidation comme le tagging
Je me demande sincèrement quelles sont les choses « bizarres » qu’on peut faire avec un système de cache, et ce que les gens en font au-delà du simple cache de données
J’aime memcached, mais si vous configurez Redis comme cache volatile et que les gens le traitent comme un stockage de données persistant, ce n’est pas la faute de Redis
La comparaison est d’autant plus étrange que memcached non plus n’est pas persistant
Sans indication contraire, on ne peut pas vraiment reprocher à un nouveau développeur de partir de cette hypothèse
Memcached a été le sauveur du caching à son époque
Le fait qu’il ait été créé en 2003 par Brad Fitzpatrick pour LiveJournal est aussi appréciable
Chaque publication dans le fil d’un utilisateur pouvait avoir ses propres restrictions d’accès, ce qui permettait de mettre en cache les publications ou des pages entières
Je l’ai utilisé pendant des années avec Ruby on Rails, les pages étaient plus rapides et ça fonctionnait, tout simplement
Son inconvénient — et son avantage en termes de vitesse — était que le cache était stocké en mémoire et non sur disque
Si vous devez mettre en cache un grand volume de données sur un site à grande échelle, les coûts d’hébergement peuvent devenir élevés
Dans ce genre de cas, Solid Cache a été un sauveur pour moi
Sur le projet sur lequel je travaille actuellement, le cache dépasse les 100 GB, il est stocké sur disque dans PostgreSQL, consulté rapidement via des index, et Rails gère automatiquement l’expiration en supprimant les lignes concernées
Si la taille du cache était plus petite et que j’utilisais déjà Redis, je choisirais probablement simplement Redis
Mais si la vitesse était la priorité absolue, je ferais un benchmark entre Memcached et Redis
Le caractère temporaire de memcached et le fait que les gens l’utilisent ou non comme si c’était persistant sont deux sujets différents
Si le taux de hit du cache semble être de 99,9 % et qu’il est toujours là, tôt ou tard quelqu’un écrira du code qui dépend de ce comportement
Je me dis qu’en mode développement, une bibliothèque cliente pourrait peut-être aider en renvoyant
nulldans environ 10 % des casmemcached est absurdement plus rapide que Redis pour un simple cache clé-valeur
Il a des threads et il est hautement optimisé pour faire une seule chose extrêmement bien
Redis, à l’inverse, ressemble davantage à une sorte de tas Python partagé arbitraire avec toutes ses structures de données et son fonctionnement en thread unique
Chez Notion, Redis est utilisé pour plusieurs usages, mais le vrai caching est confié à memcached
On est autour de 300 microsecondes contre 350 microsecondes en moyenne par lecture
Le fait qu’il soit mono-thread n’a pas non plus tant d’importance, car le goulot d’étranglement n’est pas le CPU mais les E/S réactives
Ils permettent d’utiliser davantage de cœurs CPU, mais si la charge n’est pas si élevée, un memcached mono-thread consomme moins de CPU qu’une version multithread