Etude de cas : un système de cache performant et stable


Etude de cas, Technique

Ensemble de solutions hardware et software, le cache permet d’améliorer les performances globales d’une application. Le fonctionnement de tout système de cache est de stocker le résultat d’une opération (quelle qu’elle soit) pour permettre à l’application d’y accéder directement sans avoir à refaire le calcul. Notre discussion portera sur le cache au niveau des applications web, mais le principe reste le même dans tous les systèmes de cache. De même qu’un ordinateur a plusieurs niveaux de mémoires : mémoire au niveau du processeur (cache L1, L2…), mémoire vive (DDR…) et mémoire persistante (disque dur, CD, clé USB…), les applications web ont aussi plusieurs niveaux de cache (cache navigateur, cache serveur, cache applicatif…). Par exemple, lorsqu’un utilisateur demande une page, son navigateur vérifie que la page n’est pas déjà dans son cache et le cas échéant va la demander au serveur. Le serveur à son tour regarde alors s’il a la page en cache sinon demande à l’applicatif de la générer (applicatif qui pourra lui même utiliser diverses caches !), puis la renvoie au navigateur.

Il en ressort deux points capitaux :
– plus les données demandées sont stockées dans une mémoire proche de l’utilisateur plus le résultat sera rapide
– pour une même application, une multitude de systèmes de cache aux objectifs différents peuvent coexister et intervenir à différents niveaux.

Dans la cadre d’un projet pour un client DISKO, nous devions nous servir d’un système de cache pour deux raisons. Tout d’abord d’une manière plus classique, pour améliorer les performances de l’application, où la structure de la base de données (basée partiellement sur le modèle EAV) a fait que nous ne pouvions décemment pas solliciter le serveur MySQL à chaque requête. D’autre part car l’application présente un système de publication : lorsqu’une modification est effectuée par un administrateur dans des données, celles-ci ne sont pas répercutées directement sur le site public, mais uniquement lorsque l’administrateur le décide. Une mise en cache solide est alors apparue comme une solution viable par rapport au versioning habituellement utilisé dans ces cas là (par exemple sur wordpress).

Notre objectif était donc de trouver une solution de cache à la fois performante et d’une grande stabilité.

 

Les solutions existantes

Il existe aujourd’hui pléthore de systèmes de cache disponibles sur le marché. Voici une présentation des plus communs afin d’éclaircir les raisons de notre choix. Le cache logiciel ne sera pas abordé en profondeur ici, puisque nous l’implémentons par défaut dans nos applications.

Le système de fichier

De loin le plus classique et le plus utilisé des systèmes de cache, le cache fichier (ou cache filesystem) consiste à sérialiser les données et les écrire dans un fichier physique sur le disque dur. L’accès aux données se fait en ouvrant et en déserialisant le contenu du fichier. Cette solution présente des avantages clairs : stabilité, facilité de mise en place, sauvegarde et duplication du cache très facile (il suffit de copier les fichiers!) mais présente aussi quelques inconvénients non négligeables. Les temps d’accès disque, en cas de charge, peuvent ralentir fortement l’application surtout si les données sont réparties sur plusieurs fichiers. Les accès concurrents à un même fichier peuvent aussi créer d’autres problèmes de rapidité et de stabilité. Ces issues peuvent être contournées pendant un certain temps en distribuant les fichiers de cache sur plusieurs machines équipées de stockages performants (SSD, RAID10….).

Memcache

Memcache est un système de cache distribué. Là où l’on avait deux serveurs qui devait avoir tous les deux les même données en mémoire, on a un seul cache mémoire distribué sur les deux serveur. Ainsi on augmente le nombre de données stockées en mémoire. Memcache est un système de stockage de données clés valeurs en mémoire, que l’on utilise pour stocker les résultats des requêtes à la base de données et les petits objets souvent appelés. L’applicatif a un accès beaucoup plus rapide aux données stockées par Memcache, ce qui permet une génération de page plus rapide et moins d’accès aux bases de données.

Redis

La solution Redis se place à mi-chemin entre le cache fichier et memcache. En plus d’utiliser la RAM pour la lecture/écriture du cache, Redis permet de répliquer les données sur le disque, afin de pouvoir les restaurer en cas de pépin. Redis pourrait sembler être la solution idéale au premier abord, mais ses performances ne semblent pas au niveau de Memcache (voir notre comparatif) et de plus si le serveur Redis n’est plus accessible, aucun des deux caches (mémoire et fichier) n’est accessible. Ce qui veux dire qu’en cas de reboot intempestif de Redis, le site sera coupé le temps du redémarrage, situation bien sûr non acceptable.

Varnish

Varnish Cache est un système de cache HTTP. On peut imaginer une architecture comme celle-ci: Un loadbalancer en entrée du système, qui dispatche les requêtes HTTP entre les différents clusters applicatifs.

Pour ajouter une couche de cache, l’idée est d’intercaler un Varnish Cache en frontal de chaque cluster applicatif, afin de répondre plus rapidement et de moins solliciter les clusters.

A la différences des deux solutions précédentes, Varnish Cache met en cache les pages déjà générées par l’applicatif. Varnish Cache gardant en cache les pages renvoyées par l’applicatif, si celui-ci venait à tomber, Varnish peut toujours répondre aux requêtes HTTP avec les données de son cache, permettant ainsi d’amoindrir les interruptions de services.

 

Notre solution

 Etude de cas : un système de cache performant et stable

Nous avons choisi d’utiliser un cache redondant basé sur des instances (non distribuées) de memcache et du système de fichier. Pour la mise à jour des données, nous écrivons les valeurs dans l’ensemble des memcaches et du filesystem, avec une vérification du hash des entrées pour garantir qu’aucune donnée enregistrée n’est corrompue. Les fichiers de cache physiques sont répartis dans plusieurs répertoires (défini en fonction de la clé de cache) pour éviter l’engorgement d’un seul répertoire avec possiblement plusieurs milliers de fichier.

Quand à la lecture, elle se fait dans un premier temps sur l’instance de memcache qui a le moins de charge à cet instant. Si la valeur n’y est pas trouvée, les données sont ensuite chargées à partir du filesystem, puis réécrites dans les memcaches, de façon à ce que la prochain accès soit traité via la mémoire vive.

 

En conclusion

Nous avons donc su allier les performances de memcache et la stabilité d’un cache filesystem. Les avantages principaux de notre solution par rapport à Redis sont d’une part que nous bénéficions des performances de memcache (qui sont supérieures à celles de Redis – voir notre article précédent), tout en nous permettant de toujours avoir accès à du cache dans le cas extrême où les serveurs memcache ne répondraient plus.
D’autre part, la repopulation automatique des memcaches permet d’ajouter aisément des instances en cas de forte charge.
Si cette solution a très bien fonctionné dans la pratique, nous aurions souhaité ne pas avoir à la développer en interne pour nous baser sur un outil open-source proposant les performances et la stabilité recherchée. Nous voyons actuellement avec l’augmentation du trafic global une nécessité croissance d’utiliser des sytèmes de cache (encore démontrée récemment avec Twemcache, la solution de cache de twitter). Peut-être que cet engouement permettra à une telle solution de voir le jour dans un futur proche ?

Laisser un commentaire