Des caches Drupal! (et surtout des caches)

Share and options

Comme promis dans l'article précédent, je vais vous introduire la gestion du cache dans Drupal 7. Dans cet article, je ne vais pas m'étendre sur les politiques de cache d'un point de vue du développeur, mais plutôt du choix du backend approprié en fonction de l'environnement. Cet article a donc vocation de se placer plutôt côté architecte ou administrateur système.

Je suis moi même développeur, architecte n'est pas mon métier, cependant avoir un point de vue éclairé sur les problématiques que rencontrent ces derniers – sans pour autant être un expert – peut amener à se poser des questions sur le code que l'on produit. L'architecture et le design d'une application web (ou d'un sous-ensemble de cette dernière) poussent à se poser la question d'une politique de gestion de cache efficace. Pour mener à bien la conception d'une politique de cache, il faut comprendre les problèmatiques qui se posent derrière.

Cet article va être tourné plus particulièrement à la gestion du cache dans Drupal 7, qui à été grandement améliorée depuis Drupal 6. L'accent sera mis sur les différents backends.

Note de fin de soirée : Terminant cet article, je me suis rendu compte que ma simple introduction à la problématique elle même s'avère être suffisament consistente pour exister en tant qu'article complet. C'est pourquoi je m'arrête ici ce soir. Je vous souhaite une bonne lecture.

1. Un peu de culture  Drupal et PHP

1.1. Des sites à forte volumétrie

Toute application devant supporter une lourde charge amène les gens qui la gèrent à devoir trouver des mécanismes de cache toujours plus performants. Drupal aujourd'hui héberge du contenu sur des sites ayant une très forte volumétrie. http://drupal.org est un des plus gros : ce dernier éxiste depuis la naissance des premières versions communautaires de Drupal, et héberge toujours des contenus qui remontent à son origine. À titre d'exemple, prenons la page d'un projet que j'ai créé il y'a maintenant deux jours : http://drupal.org/project/cache_backport. L'identifiant du node (tous les contenus dans Drupal sont appellé des nodes, le mot français noeud laisse à désirer et je préfère garder la terminologie anglaise pour lever toute ambiguité), est 1023542 (pour vous faciliter la lecture : 1,023,542 notation anglaise). On peut donc estimer que le site Drupal héberge aujourd'hui plusieurs centaines de milliers de contenus (certains ont probablement été effacés depuis). De même, mon identifiant utilisateur est 240164, sachant que mon compte a été créé il y'a maintenant plus de 3 ans, je vous laisse faire le calcul sur le nombre d'utilisateurs potentiels actuel.

1.2. Un environnement particulier

PHP est un langage très utilisé aujourd'hui, cependant il a un défaut majeur (certains diront que c'est un avantage) : c'est un langage de scripting. Une des particularités qui l'accompagne est sa mauvaise gestion de la mémoire, il n'est par conséquent pas possible (du moins très difficile) d'écrire une application persistente en PHP. Comme toute application web écrite en PHP, Drupal est par conséquent une application volatile. Les scripts PHP sont exécutés par le serveur HTTP comme des des CGI (Common Gateway Interface), qui, comme l'acronyme l'indique, laisse le serveur web n'être qu'une simple passerelle entre l'application réelle et le client. Ceci veut dire qu'à chaque fois qu'un client atteind le serveur, un script PHP est lancé par le serveur. Lorsque ce dernier termine de générer la page demandée, il meurt. Il n'est par conséquent pas possible de conserver des données en mémoire, ni de les partager entre les différents hits provenant de différents client. Pire encore, un même client demandant plusieurs pages à la suite donnera lieu à chaque au cycle de vie complet d'un script PHP à chaque fois.

Par opposition aux environnement persistants (tels que les serveurs d'application Java/J2EE JBoss ou python Zope) une application PHP doit effecuter ce qu'on appelle un bootstrap lors de chaque requête HTTP. Le bootstrap est l'opération d'initialisation de l'application, elle charge ses fichiers, se connecte à la base de donnée, charge sa configuration, pour enfin donner lieu au traitement métier réel. C'est souvent juste après le bootstrap qu'intervient le contrôlleur lorsque l'application dipose d'un modèle MVC.

Le bootstrap est en règle générale une opération coûteuse, qui doit être optimisée à tout prix, certains frameworks ont excellé dans ce domaine, comme par exemple le Zend Framework. Drupal quant à lui de par sa modularité mais aussi parce que ses développeurs ont fait le parti-pris de laisser le moins possible de tâches aux administrateurs sytème (ce qui peut en fâcher plus d'un, croyez moi, plus particulièrement les architectes). Il possède une phase de bootstrapping qu'on peut dire lourde. Ceci est moins vrai à l'heure de Drupal 7 mais malgré ce dernier constat cette opération a toujours un impact visible sur les performances (contrairement au Zend Framework qui par défaut exécute un bootstrap incroyablement rapide).

Le bootstrap de Drupal charge un grand nombre de données (la liste des modules présents, l'ensemble des variables en base de données, ainsi qu'un certain nombre d'information diverses). Ce sont les premières lignes exécutées elles-même qui créent le besoin d'avoir des caches.

2. La mort d'une application : les I/O

2.1. Le goulot d'étranglement le plus rencontré

Toutes ces informations sont, pour la pluspart, lues dans la base de données, ce qui en soit est déjà une opération très lourde. Le temps de connection à une base de donnée, la transmission des requêtes SQL via le réseau (le plus souvent en TCP) sont des opérations d'entrée / sortie (que j'appellerais désormais des I/O). Un I/O est aujourd'hui ce qui coûte le plus cher en temps, que ce soit à destination de votre processeur et de votre mémoire, ou bien à destination d'un ordinateur de l'autre côté du monde. Chaque communication entre deux services impose plusieurs étapes incompressibles, entre autre le formattage des données selon un protocole, et l'émissions de ces dernières dans une couche de transport. Je ne vais pas vous faire mon cours de réseau, bien que le connaissant presque par coeur : je vous conseille donc vivement de lire de la documentation sur le modèle OSI ainsi que l'encapsulation réseau (ce dernier lien n'est pas le meilleur que j'ai pu lire, cependant il sera pour les curieux un bon point de départ).

2.2. La base de donnée et les I/O : prendre conscience du problème

Par défaut, Drupal utilise intensivement la base de donnée. À titre indicatif, en créant un site Drupal 6 nu de quelconque module, créez une dizaine de contenus, publiez les et accédez à la page d'accueil par défaut : une centaine de requêtes SQL seront effectuées. Une centaine de requêtes pour si peu c'est à la limite du sacrilège! Essayez avec Drupal 7, vous remarquerez que ce dernier ne fait pas mieux, puisqu'il peut monter jusqu'à 200 requêtes SQL dans les mêmes conditions. La moitié d'entre elles sont probablement effecutées lors du bootstrap lui même (je m'avance sur ce point).

Il faut prendre conscience que dans les environnement scalable (que nous pouvons traduire par extensibles) la base de donnée est très souvent déportée sur une machine physique différente que le serveur web (et donc l'application PHP) lui même. Vous rajoutez donc en plus du temps de connection incompressible (authentifcation et initialisation du protocole) un temps de latence ainsi qu'une bande passante réduite dûs à la couche réseau. Les fans du modèle OSI me diront que ça passe par un nombre incroyable de couches : PHP, appel système (système d'exploitation), réseau virtuel, driver réseau, carte réseau, protocole sous-jacent au réseau lui même, laison physique, et le chemin inverse (ceci est loin d'être complet et il y a en réalité plus de couches que cela) : chaque couche possède son propre protocole de communication, découpe l'information en trames ou en paquets, crypte ou décrypte et ainsi de suite.

2.3. Pour tout ceux qui croyaient en la magie

De plus, un SGBD (Système de Gestion de Base de Donnée, DBMS en anglais, acronyme pour DataBase Management System) stocke ses données sur un disque physique. Dans l'histoire de l'informatique et encore aujourd'hui le stockage physique est l'opération la plus couteuse. En effet, un disque dur n'a jamais été magique, il s'agit de plateaux magnétiques entrainés par des moteurs mécaniques sur lesquels tentent de lire des têtes de lecture, lorsque le plateau passe à la bonne vitesse et au bon moment en dessous de ces dernières. Cette description vous paraît peut-être un peu excessive ? C'est pourtant ce qu'il se passe.

Chaque accès aux données entraîne mécaniquement l'enchaînement de toutes ces opérations, passage par le réseau, puis accès sur un support physique. Il est important de bien comprendre ça pour comprendre que l'accès aux données est systématique le plus gros goulot d'étranglement d'une application, quelle qu'elle soit. Dans le cas réel, des caches existent dors et déja à tous les niveaux. Ainsi le système d'exploitation va maintenir en mémoire les blocks du disque dur les plus lus, mais il va aussi travailler à de nombreux autres niveaux. Le SGBD lui même va gérer un certain nombre de caches en mémoire. Ainsi MySQL à titre d'exemple va conserver en mémoire le résultat des requêtes les plus souvent effecutées, c'est ce qu'on appelle le query cache. Lorsque vous effectuez une requête SQL très souvent, il est fort probable que cette dernière ne déclenche jamais aucune lecture sur le disque physique. Votre requête SQL doit cependant atteindre le SGBD au travers du réseau.

D'ailleurs petite note personnelle : je hais MySQL pour un grand nombre de raisons. La première d'entre elle est probablement qu'il est faiblement ACID compliant (ce qui veut dire Atomicty Consistency Isolation Durability, cette phrase est une douce contradiction en soit, car ces propriétés ACID se définissent par leur stricte respect). Il faut donc aussi savoir que tout requête UPDATE sur une table d'une base de données MySQL entrainera l'effacement du query cache ce qui à pour effet de le rendre complètement inopérant sur des sites dont le contenu est fortement mutant.

Les opérations sur un SGBD sont toujours lourdes! Sauvez cette phrase dans un coin de votre cerveau, elle pourrait bien vous sauver un jour. Un SBGD et plus particulièrement un SGBDR (R comme Relationnel) est un outil de persistence des données basé sur un schema normalisé et si possile répondant à une forme normale la plus élevée possible, ceci afin d'assurer la cohérence des données. Récupérer des données provenant d'un SBGDR revient donc à une dénormalisation des données – donc une étape de transformation – et à l'inverse la sauvegarde de ces données revient à une normalisation des données.

3. Les cache et Drupal 7

Nous allons donc commencer à parler des caches avec un peu de culture. Drupal 7 abstrait la notion de cache. Les habitués du framework savent dors et déjà que Drupal stocke ses caches sous la forme de tables dans le SGBD. Drupal 7 lui stocke ses cache via un backend.

3.1. Les cache BIN? WTF dude!

Drupal n'appelle plus ses tables de cache des tables mais désormais des bins. Un bin est donc un espace de stockage. Cet notion est agnostique, entendre qu'elle est minimaliste et indépendante de son implémentation. C'est une couche d'abstraction nécessaire pour de multiple raisons, mais je ne vais pas vous faire un cours sur la programmation orientée objet ni sur les SOLID principles.

Bin est un diminutif de binary. Ceci veut dire que désormais Drupal ne stocke plus des données cachées anonymes mais bel et bien des données binaires. Vous pouvez penser que le nom importe peu, mais la terminologie démontre la clarté avec laquelle ils ont enfin réussi à isoler le problème : un cache binaire est un espace de stockage permettant de stoquer des données temporaires, dénormalisées mais surtout prêtes à la réutilisation.

En résumé, un cache est plus particulièrement un espace de partage de données temporaires nécessitant une opération de récupération et / ou de construction lourdes.

3.2. Ce qu'apporte Drupal 7

La fonctionnalité la plus importante qui constitue cette nouvelle abstraction est la possibilité d'utiliser plusieurs implémentations (plusieurs backends) différents sur un même site. Ceci permet de stocker nos données via des services externes différents, en fonction de leurs capacités. Dans notre cas, les facteurs de décision quant au choix des backends à utiliser sont peu nombreux : leur latence, le nombre d'I/O générés et leur capacité de stockage.

3.3. L'importance de l'utilisation de multiple backends

Comme j'ai pu l'introduire plus haut, oh, j'aime beaucoup cette expression, pas vous?

– Laissez moi vous introduire!

Donc, comme précisé plus haut, laisser une application web atteindre systématiquement la base de donnée c'est quelque part courrir à sa perte. Que ce soit les I/O trop fréquents, la normalisation des données ou bien tout simplement la mécanique interne du SGBD qui s'avère être le goulot d'étranglement de votre application, vous allez de toute manière ammoindrir les performances à cause d'elle.

De multiple implémentation de cache binaire existent aujourd'hui (j'en utilise régulièrement trois moi même rien qu'avec Drupal). Chacunes ont leur forces et leur faiblesses. Certaines seront locales, incroyablement rapide, mais limités à une faible quantité de données quand certaines autres seront déportées et engendront une latence non négligeable tout en étant capable de stoquer un volume de données plus élevée.

Grâce à l'API de gestion des caches binaires de Drupal 7, vous pourrez au sein de la même requête client utiliser l'un ou l'autre de ces backends en fonction de la nature des données que vous devez récupérer, mais aussi en fonction de leur volume et de leur fréquence d'accès.

Ce découpage n'est pas inné, il est de la tâche du développeur de préparer un socle applicatif suffisament souple, permettant à l'architecte de procéder à la découpe lui même lorsqu'il concoit une architecture complexe visant à héberger un site.

4. Encore une fois désolé

Il est tard, et je m'arrête ici. Cet article est une incongrue introduction à diverses problématiques de performance dans l'exécution d'application métier et plus particulièrement dans le contexte du web. Je me suis un peu éloigné du chemin qui m'étais tracé, c'est pourquoi je vais aller dormir. La nuit portant conseil, j'espère aboutir à un article un peu moins philosophique et beaucoup plus technique présentant les différents backends de cache que nous pouvons utiliser avec Drupal. Cet article, si l'envie m'en dit, sera probablement accompagné d'une série de micro benchmarks (entendez donc qu'ils ne seront en rien des preuves) présents en la qualité d'illustrations.

A bientôt pour la suite!