La gestion du « cache » dans un site ou une application web

janvier 25, 2016
clevertech

Dans tous les projets web on est confronté à un moment à la gestion du « cache ». Au fur et à mesure des projets sur lesquels j’ai travaillé, j’ai pu tenter différentes techniques plus ou moins performantes, pour vous délivrer aujourd’hui une manière efficace de s’affranchir des problématiques du cache et l’utiliser à votre avantage.

Pré-requis pour bien comprendre cet article : connaître les bases de la programmation HTML et des échanges clients – serveurs HTTP.

Mais qu’est ce qu’on appelle le « cache » ?

Vulgairement, le cache est une copie en mémoire d’un fichier ou d’une donnée et qui permet ainsi d’optimiser les temps de réponses de plusieurs façons :

  1. Le cache étant généralement directement « en mémoire » – c.a.d en mémoire « vive » (RAM, mémoire « flash ») avec des temps d’accès plus rapide que les accès « physiques » (comme un accès à un disque dur), la récupération d’une donnée s’en trouve grandement accélérée.
  2. La version en « cache » de l’information est généralement le résultat attendu d’un traitement, ce qui permet d’économiser le temps du traitement pour les prochains appels. ;
C’est super ça, et on peut tout mettre en cache ?

En théorie oui, mais en pratique un cache a une durée limitée car la mémoire vive, elle, n’est pas illimitée, et il est nécessaire de libérer de l’espace régulièrement, voir de passer par une copie « physique » du cache pour recharger plus rapidement une ressource lors d’un prochain appel. Certains algorithmes permettent également de gérer de manière intelligente le cache afin de ne conserver uniquement en mémoire que les ressources les plus utilisées.

Autre problème : dans le cadre des pages générées « dynamiquement » – dont le contenu peut changer d’un appel à un autre en fonction par exemple des informations en session de l’internaute -, il n’est pas souhaitable que ces pages soient conservées en cache, au risque de fournir une version obsolète de la page.

Compris, mais au fait à quel niveau est géré ce « cache » concrètement ?

C’est là que ça se complique car le « cache » peut se gérer à différents endroits, de manière combinée :

  1. Le cache du navigateur : le navigateur gère lui même son propre « cache », ce qui permet de charger une ressource instantanément sans la charger depuis le réseau. Par contre la mise en cache peut varier selon le navigateur et il y a donc des différences de gestion et de mise en cache des ressources javascript, images, css, etc… entre par exemple Firefox, Chrome ou Internet Explorer (pour ne cite qu’eux).
    Il est toutefois possible de supprimer le cache du navigateur dans les paramètres de chaque navigateur, voir d’en demander l’actualisation en rafraîchissant une page en utilisant la combinaison CTRL + F5 plutôt que simplement la touche F5, mais il est difficilement concevable de demander à un internaute d’effectuer cette action à chaque mise à jour du site ;
  2. Le cache des proxy : la bête noire pour les applications web car les proxies (principalement les proxies d’entreprise) ne respectent pas tous les consignes de gestion en cache des entêtes HTTP et ont généralement tendance à tout mettre en cache ;
  3. Le cache des serveurs HTTP (Apache, IIS, etc…) : ils peuvent également gérer la mise en cache de ressources (javascript, images, et css), mais cette mise en cache respecte en principe les consignes délivrées par les entêtes HTTP et peuvent donc être modifiées au niveau applicatif si besoin (avec plus ou moins de succès) ;
  4. Le cache au sein de votre propre application : vous pouvez décider de mettre en cache certains résultats de traitements au sein de votre application web, côté serveur (java, php, etc…) :  dans ce cas vous êtes seul maître de la gestion de votre cache, ce qui ne pose normalement pas de soucis à ce niveau car d’une manière ou d’une autre vous pouvez facilement jouer dessus en cas de problème ou de mise en cache « abusive » ;
Attend, c’est quoi ces « consignes » dans les entêtes HTTP ?

Pour faire simple, les pages internet et les navigateurs utilisent le protocole HTTP pour communiquer avec votre serveur. Une requête HTTP possède des entêtes qui permettent notamment de modifier la mise en cache ou non d’une ressource ou d’une requête AJAX.

Ces consignes sont normalisées et on peut logiquement gérer la mise en cache de telle ou telle ressource juste en spécifiant les bonnes entêtes HTTP. Cependant je ne vais pas m’épancher sur le sujet car ces entêtes ne sont pas infaillibles, loin de là, et ce n’est pas la façon la plus efficace au final. Mais pour en savoir un peu plus sur ces entêtes, vous pouvez toujours faire un tour ici : https://fr.wikipedia.org/wiki/Cache-Control

A noter que ces problèmes de cache concernent principalement les requêtes HTTP avec la méthode GET. Dans le cadre des méthodes POST, PUT ou DELETE, la nature même de ces méthodes font qu’aucune mise en cache n’est effectuée pour ces requêtes (du moins, en principe).

Ok, mais c’est quoi finalement le problème avec le cache ?

On y arrive ! Si le cache peut s’avérer très utile pour optimiser le temps de chargement d’une page internet (notamment sur mobile), il peut s’avérer très gênant lorsque vous désirez que votre ressource soit actualisée quotidiennement, voir systématiquement dans le cas d’une requête AJAX récupérant des informations du serveur : imaginez que votre requête renvoie les informations d’il y a plusieurs jours, celà peut poser de gros problème !

Or, on a vu qu’il y a au minimum 4 niveaux de mise en cache entre votre site et votre serveur : le navigateur, le(s) proxy(s), le serveur HTTP et le cache applicatif. Il faut donc trouver un moyen qui soit compatible et puisse fonctionner avec ces différentes couches, sans pour autant supprimer la mise en cache car il n’est pas concevable d’aller actualiser systématiquement toutes les ressources de votre page web à chaque affichage, au risque de voir les performances de votre site dégringoler !

Effectivement mais si les directives des entêtes HTTP ne sont pas infaillibles, comment s’assurer d’une « bonne » mise en cache ?

Durant mes projets, j’ai été confronté à plusieurs reprises à la problématique de gestion du cache dans les applications / sites internet et j’ai tenté plusieurs approches :

  1. Première tentative : j’en ai parlé, l’utilisation des entêtes HTTP qui s’est avéré assez aléatoire : tout d’abord les navigateurs peuvent outre-passer ces directives et décider de mettre en cache arbitrairement une ressource. Et si ce n’est pas le cas, une requête peut se retrouver mise en cache tout aussi arbitrairement au niveau d’un proxy. Résultat : insuffisant ! ;
  2. Seconde tentative : versionner les noms des ressources (ex. : main-1.0.js) : pas une mauvaise idée en soi, mais difficile à maintenir puisque les fichiers doivent être renommés à chaque modification, bien qu’il soit possible d’automatiser celà via des routines de déploiements (ce qui fera certainement l’objet d’un autre article à part entière). Autre problème, si la page HTML n’est pas actualisée au niveau du cache du navigateur, elle peut continuer à faire référence aux anciennes versions des ressources : on peut donc se retrouver avec une page web partiellement actualisée et il a de fortes chances qu’elle ne puisse pas fonctionner correctement… Résultat : bien mais pas top ;
Je ne savais pas que c’était aussi compliqué le « cache », il n’y pas de solution miracle ?

Oui et non. La solution que je vous livre passe en réalité par une combinaison bonnes pratiques à prendre en compte dès le début du développement du site ou de l’application web :

  • Utiliser les entêtes HTTP ainsi que les metadata dans la partie <head> de la page pour les pages internet de manière à ce que la page HTML ne soit jamais mise en cache : si ces entêtes ne fonctionnent pas de manière infaillible pour les ressources javascript, css ou les requêtes AJAX, il semble y avoir un consensus en ce qui concerne les pages HTML ;
    <meta http-equiv="Cache-Control" content="no-cache, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
  • Ajouter systématiquement un paramètre de version à chaque ressource javascript, css : en ajoutant simplement un paramètre type « ?_v=1.0.0″ à une ressource, vous n’avez pas besoin de modifier son nom et vous pouvez continuer d’utiliser le cache car la ressource sera mise en cache avec le paramètre, et il suffira d’incrémenter le numéro de version pour forcer la mise en cache de la ressource avec la nouvelle version.
    <script type="text/javascript" src="https://monsite.com/assets/js/scripts.js?_v=1.0.0"></script>
    <link rel="stylesheet" href="https://monsite.com/assets/css/astyles.css?_v=1.0.0">
  • Ajouter un paramètre « timestamp » à chaque requête nécessitant d’être actualisé à chaque appel (généralement le cas des requêtes AJAX) : la requête sera bien mise en cache, mais en utilisant à chaque fois un timestamp différent vous n’utiliserez en réalité jamais une version depuis le « cache » : ex. : http://server.com/?_t=1453756481304.
    $.GET('https://monsite.com/rest/data?_t=' + (new Date().getTime()), function() {});

En pratique, c’est pour moi la seule solution efficace qui permette de s’affranchir des problématiques de mise en cache inopportuns, tout en continuant à l’utiliser par l’intermédiaire d’un paramètre de « version », ce qui a pour avantage non négligeable de permettre de mettre en ligne une nouvelle version d’une application web de manière transparente sans craindre que l’internaute utilise une version ancienne situé dans le cache !

 

Et vous ?

Avez-vous été confronté un jour à ce genre de problématique ? Quelle solution avez-vous mis en place pour la/les contourner ?

No comments

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>