Commentaires
Jouons à cache-cache avec HTTP
Le cache HTTP est devenu le cache à la mode pour les développeurs PHP depuis l'arrivée de Symfony2.
La mise en oeuvre du cache HTTP à travers Symfony2 est assez simple, grâce au respect de la norme, et de reverse proxy comme celui de Symfony2 ou encore mieux Varnish.
Son implémentation est évoquée dans de très nombreux articles.
Cependant, la mise en oeuvre est la partie émergée de l'iceberg. Il ne suffit pas de mettre quelques lignes de code en plus pour avoir un cache performant. La partie la plus importante étant l'analyse préalable du site, permettant de connaître les pages nécessitant un cache et quel type de cache il faut employer plutôt qu'un autre.
Faire un bon cache-cache c'est d'abord savoir compter
Avant de partir dans du code, il est nécessaire d'avoir une bonne vision de l'utilisation du site par les clients et par les contributeurs en effectuant différentes mesures.
L'implémentation d'un cache HTTP, comme tout développement, doit se faire de manière itérative afin de détecter les risques, ajuster les choix et avoir une meilleur visibilité sur son impact. Les trois premières mesures : temps, charge et popularité, servent à répartir ce développement itératif.
Les deux autres mesures : versatilité et criticité, permettent de déterminer le type de cache et ses paramètres à implémenter.
Temps
Tout d'abord il faut savoir le temps d'exécution de chacune des pages. Il n'est pas nécessaire d'établir une mesure précise, un simple ordre de grandeur des différentes pages suffit.
Pour la simplification de cet article, deux ordres de grandeurs sont retenus :
Charge
La plupart du temps, plus le temps d'exécution d'une page est longue plus sa charge associée est grande, mais ce n'est pas tout le temps vrai : l'utilisation de services web, par exemple, peut allonger le temps d'exécution d'une page sans pour autant en augmenter sa charge (le processus reste en attente). Il est aussi possible d'avoir une page ayant une charge importante mais un temps d'exécution très court : remplir 1Go de mémoire est assez rapide en PHP...
Il faut donc mesurer cette charge, ainsi, pour simplifier, deux ordres de grandeurs sont retenus :
- Charge importante ;
- Charge peu importante
Popularité
Le nombre de visites pour chaque page est un autre critère à prendre en compte. Il permet de mesurer la popularité et d'émettre une classification avec les deux critères précédents, ainsi deux critères sont encore retenus :
- Page très visitée ;
- Page peu visitée
Versatilité
Chaque donnée, proposée par un site, possède une fréquence de mise à jour différente. Par exemple, un contenu éditorial délivre des articles qui ne sont que peu mis à jour mais aussi des valeurs boursières, qui sont elles fréquemment mises à jour.
Il faut être capable de donner trois valeurs à cette versatilité :
- Une valeur haute : Le temps maximum entre deux modifications ;
- Une moyenne : La moyenne de temps entre deux mises à jours ;
- Une valeur basse : Le temps minimum entre deux éditions
Criticité
Cette mesure n'en est pas vraiment une. Elle est surtout un ordre de grandeur qui détermine si votre donnée doit être absolument à jour ou non pour votre client. Prenons encore l'exemple de la valeur boursière par : sur un site de contenu éditorial, cette donnée peut se permettre de ne pas être exact pendant quelques minutes, en revanche, sur une API REST destinée à faire des échanges boursiers il est absolument nécessaire d'avoir la donnée la plus fraîche possible.
Le tri des données s'effectue sur ces deux critères :
- Criticité importante ;
- Criticité peu importante
Mise en place
Pour mesurer toutes ces valeurs, il est nécessaire d'avoir un ensemble d'outils de monitoring :
- Des outils de benchmarks pour le temps de réponse comme siège (ou ab mais moins fiable), ou même des outils en mode SaaS comme NewRelic;
- Connaître la charge d'une page, c'est utiliser un bon outil de profiling comme XHProf, et de relever les valeurs de consommation de CPU et de mémoire de la page;
- La popularité d'une page peut se mesurer facilement avec Google Analytics par exemple ou d'autres solutions du même genre. Il peut aussi être possible de la mesurer en analysant les fichiers d'accès du serveur web (mais ce sera bien plus compliqué);
La versatilité se mesure de différentes manières selon la source :
- Si la modification des données provient d'une administration, et donc de modifications humaines, il suffit de mesurer la popularité de cette interface avec les mêmes outils que la mesure précédente;
- En revanche si cette modification provient de sources automatiques, il faut mettre en place son propre monitoring. Par exemple, on peut très bien utiliser Statsd avec la bibliothèque statsd-php et mettre ce bout de code :
<?php
if ($dataHasChanged) {
$statsd->timing("mydata.".$dataKey.".versatility", $timeBetweenUpdate);
}
- Il est en revanche impossible de mesurer la criticité d'une donnée. La tâche en revient au directeur du produit ou aux utilisateurs qui seront capables de spécifier cette ordre de grandeur.
Analyse
Classification
La classification permet de déterminer l'ordre de changement lors d'un développement itératif. Ce principe n'est pas que lié au cache HTTP et peut être utilisé pour toute phase d'optimisation.
Le tri de nos pages, de la plus importante à modifier à la moins importante, se fait d'abord sur sa popularité décroissante, puis son temps décroissant et enfin sa charge décroissante, ce qui donne avec les ordres définis plus haut cette classification :
- Page très visitée + Temps long + Charge importante ;
- Page très visitée + Temps long + Charge peu importante ;
- Page très visitée + Temps court + Charge importante ;
- Page très visitée + Temps court + Charge peu importante ;
- Page peu visitée + Temps long + Charge importante ;
- Page peu visitée + Temps long + Charge peu importante ;
- Page peu visitée + Temps court + Charge importante ;
- Page peu visitée + Temps court + Charge peu importante
Elle permet aussi de connaître l'utilité de l'implémentation d'un cache, en effet pour les 2 à 4 derniers cas le cache HTTP peut ne pas avoir d'utilité et être contre performant en ajoutant une couche de calcul peu utilisée.
Max-Age ? Expires ?
Le type de cache et ses paramètres sont déterminés par la combinaison de la versatilité et de la criticité de l'information. Plus la donnée est critique moins le cache doit avoir un impact sur la fraîcheur des données.
Ainsi pour une donnée très critique, il est préférable de mettre en place un max-age
avec la valeur basse de la versatilité. Si elle est moins critique, on peut utiliser des valeurs plus hautes.
Etag ? Last-Modified ?
Si la différence entre la versatilité haute et la versatilité basse est très importante, une chose est certaine : on ne saura jamais quand notre donnée peut être modifiée. Il est alors intéressant de se baser sur une clé de modification comme une date de mise à jour pour un header last-modified
ou encore un hash de la donnée via un header etag
.
Cependant, il faut être sur que la création de cette clé sera toujours plus rapide que la récupération de la donnée + son post-processing (la plupart du temps l'affichage de la donnée avec Twig sur Symfony2).
L’accumulation d'un de ces headers avec un max-age
ou expires
peut être intéressant si notre donnée n'est pas très critique, on évite alors le temps de pre-processing.
Invalidation
L'invalidation du cache par une méthode PURGE
est considérée comme une mauvaise pratique dans le cache HTTP.
Premièrement cette méthode n'est pas incluse dans la norme. Ainsi, chaque Reverse-Proxy implémente son propre mécanisme de purge et il faudra de nouveau implémenter toute cette partie si l'on passe du reverse proxy de Symfony2 à Varnish par exemple.
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
Deuxièment la gestion d'une purge coté applicatif peut très vite être douloureuse et engendrer de nombreux bugs. Il revient en effet au développeur de penser au cache à chaque fois que la donnée est manipulée et, croyez-moi, il va souvent l'oublier.
De plus l'invalidation est déjà comprise dans la norme HTTP :
- quand le
max-age
arrive à 0,
- quand
expires
est antérieur à la date actuelle
- ou encore à la mise à jour de la clé de modification.
Il faut donc à tout prix éviter de créer son propre mécanisme d'invalidation.
Règle d'or
Cette méthodologie vous permettra de poser les bases pour votre cache HTTP mais ne pourra jamais répondre à tous les cas particulier, ni apporter la finesse nécessaire du premier coup.
La règle d'or pour la mise en place d'un cache HTTP est de tester et répéter ce cycle de Mesure - Analyse - Implémentation, jusqu'à obtenir un cache performant et pertinent.