Sélectionner une page

Octopuce est un hébergeur infogérant pour ses clients. Souvent, nos clients arrivent avec des solutions techniques à eux, et parfois ils ont aussi besoin d’aide : typiquement, qu’on leur recommande tel ou tel logiciel (ou qu’on leur déconseille tel autre) pour mieux servir leurs usagers.

C’est quoi Varnish ?

Assez fréquemment, un client disposant d’un site web public nous demande de l’aider en vue d’améliorer ses performances. Dans la boite à outil, on peut souvent recommander Varnish, logiciel de mise en cache de pages ou morceaux de pages, qu’on peut placer devant l’application du client (typiquement, un CMS, programme permettant de gérer le contenu publié en ligne, comme WordPress, Spip, ou parfois des applications métier du client).

Chez Octopuce, on a une longue et belle histoire avec Varnish : écrit initialement par Poul-Henning Kamp, développeur danois de logiciel libre, nous l’avons utilisé avec succès dès 2008 pour Yakaz, puis Mediapart, puis pour d’autres clients.

Varnish fonctionne ainsi :

  • il écoute sur un port TCP où il fournit un serveur web capable de recevoir des requêtes
  • à chaque requête, il exécute sa configuration, écrite dans un langage simple et compilé, nommé VCL, pour savoir que faire de cette requête
  • et il répond de 3 manières possible au client :
    • soit directement parce que la requête est une erreur
    • soit directement parce que la page est déjà dans son cache et qu’elle est non-expirée
    • soit il transmet la requête à un (potentiellement plusieurs) serveur web derrière lui, chargé de lui répondre. Dans ce cas il met potentiellement la page dans son cache avant de répondre au client.

Varnish peut donc servir de

  • cache de requêtes web pour alléger la charge d’un serveur
  • répartiteur de charge pour répartir entre plusieurs serveurs web les requêtes reçues, et potentiellement en testant que serveur web est hors service, pour éviter de l’utiliser dans ce cas
  • Serveur web intermédiaire entre un internaute et une application, pour réécrire des URL, rediriger automatiquement, ajouter / enlever des cookies, protéger une application contre certaines attaques etc. La versatilité de Varnish dans cette situation est importante : grâce à des modules d’extension écrits généralement en C, on peut ajouter de nouvelles fonctions à Varnish!

Comment marcher le cache de Varnish ?

Le langage VCL de Varnish est divisé en plusieurs fonctions (nommées vcl_qqchose) qui sont lancées à différents moments du cycle de vie d’une requête web : lorsqu’on reçoit la requête de l’internaute, lorsqu’on veut vérifier si elle est dans le cache ou pas, lorsqu’on reçoit la réponse du serveur derrière, en cas d’erreur etc.

À chaque étape de ce processus, on peut choisir de tester et si besoin modifier certaines valeurs : l’url de la requête, le nom de domaine demandé, des en-têtes HTTP, les cookies etc. On peut aussi donner des directives à Varnish pour changer son déroulement, modifier les durées de mise en cache, ignorer certains paramètres de l’internaute etc.

Ce déroulement est donc complexe, mais si on ne lui dit rien (donc par défaut), Varnish a un comportement relativement normal : il reçoit une requête, la transmet au serveur web tel quel, et garde la page en cache pour une durée par défaut fournie par le serveur web, ou pas si la requête est réputée non cachable comme une méthode POST, des Cookies etc. Pour en savoir plus, je recommande vivement ce que j’appelle la big picture de Varnish : une page décrivant le déroulement précis et complet d’une requête web !

Afin d’aider un client ayant des doutes sur la façon dont Varnish met ou ne met pas en cache ses pages, nous avons décidé de faire une recherche exhaustive sur ce sujet. Vous trouverez le résultat de ces recherches ci-dessous

Quelles règles pour mettre une page en cache ?

Les leviers sur lesquels on a moyen d’agir pour mettre en cache, ou pas, ou à moitié (on va voir plus bas) une page dans varnish sont les suivants :

  • Que le serveur web derrière retourne un en-tête HTTP Cache-Control qui précise si la page peut être mise en cache ou pas
  • Que le serveur web derrière retourne un en-tête HTTP Expires, qui précise à quelle date et heure la page devra être supprimée du cache.
  • Fixer la valeur de beresp.ttl (avec l’unité s,m,h,d) dans la fonction vcl_backend_response de Varnish. Par exemple :
    set beresp.ttl = 30m;
  • Fixer la valeur du « grace time » (temps de grâce) dans la fonction vcl_backend_response de Varnish. Par exemple :
    set beresp.grace = 120s;
  • Retourner « purge » depuis la fonction vcl_recv de Varnish : Par exemple :
    if (req.method == "PURGE") { return (purge); }
    pour purger du cache une url (et une seule, c’est le principe!) si on l’appelle avec la méthode PURGE (à filtrer par IP préalablement bien sûr!)
  • Appeler la méthode std.ban() dans le VCL de Varnish (typiquement dans vcl_recv à nouveau) : par exemple
    std.ban("req.http.host == mondomaine.fr");
    pour purger du cache Varnish toutes les url d’un domaine (ban permet donc de purger des URL en bloc, il a un process en arrière plan, le « ban lurker » chargé de cette purge.)
  • Utiliser le module supplémentaire de Varnish vmod_purge pour mettre en œuvre un « soft-purge » qui a le même comportement que le principe du « grace time » qu’on verra plus bas :
    import purge;
    sub vcl_hit {
    if (req.method == "PURGE") { purge.soft(); }
    }
  • Via la ligne de commande varnishadm : appeler la méthode ban. Par exemple :
    varnishadm
    varnish> ban req.url ~ "/articles/2024/.*"
    200

Sachant que dans tous les cas, l’appel à return (purge), à std.ban() ou à ban dans varnishadm produit toujours un hard purge : la page est supprimée du cache (de manière synchrone avec PURGE et asynchrone avec BAN) et ne sera donc plus jamais servie (sauf à être remise dans la cache)

Hard purge? soft-purge ? grace time ? kezako?

C’est à partir de là que ça se complique un peu… Varnish est un outil puissant, notamment sur sa gestion du cache, donc forcément c’est plus compliqué que « j’ai ça dans mon cache, ou pas ?« 

Dans la page expliquant cela en anglais chez Varnish on a ce magnifique dessin :

On y voit qu’un objet (en général une page web) peut être en cache bien après sa TTL (sa durée habituelle d’expiration)
Les étapes de cachabilité sont donc :

  • Par défaut, une page est en cache entre son heure d’arrivée (t_origin) et pour la durée souhaitée (la TTL), via Expires ou Cache-Control ou set beresp.ttl. Tant qu’on est entre les 2, Varnish servira la page du cache sans demander au serveur web. La réponse à l’internaute est donc immédiate, et aucune demande n’est envoyée au serveur web
  • Si on a fixé, pour la page concernée, un « grace time » via set beresp.grace, la page pourra quand même être servie immédiatement à l’internaute pendant une durée supplémentaire (entre TTL et grace sur le schéma) déterminée par le grace time. Cependant, à la différence de la période origin-ttl, si une page est servie pendant le grace-time, Varnish la demande quand même au serveur web en arrière-plan, pour ensuite la remettre dans son cache. L’internaute a donc reçu immédiatement une page relativement ancienne, mais la nouvelle est stockée dans le cache peu après, pour pouvoir servir l’internaute suivant avec une page fraîche !
  • Enfin, encore plus compliqué (et je ne l’ai jamais vu utilisé jusque là) Varnish supporte aussi un temps « keep » qu’on peut poser de la même façon (set beresp.keep=10d; par exemple) et qui permet de servir, après la TTL et le grace-time, une page du cache, mais en ayant demandé au serveur web de confirmer que la page n’a pas été modifiée depuis le temps t_origin. (d’où le « If-Modified-Since » du schéma, qui est un en-tête HTTP envoyé au serveur web pour lui dire de répondre 304 Not Modified à la demande dans ce cas). Si votre application web supporte cet en-tête, cela permet de servir un article en ne le rafraichissant du cache Varnish que si ce dernier a été modifié.

La différence entre un hard purge et un soft purge se fait donc sentir ici : lorsqu’on utilise ban ou std.ban ou return(purge), on enlève la page du cache. Elle ne peut donc être plus soumise à aucune de ces expirations (ttl/grace/keep). Le prochain appel à la page concernée sera envoyé de manière synchrone au serveur web et on répondra à l’internaute avec une page neuve (et probablement plus lentement). On notera que l’on ne peut pas obtenir un soft-purge par l’outil en ligne de commande varnishadm. Autant, dans ce cas, passer par un appel HTTP à Varnish, filtré et validé dans vcl_recv.

SI on utilise le module vmod_purge avec un appel à purge.soft() (en précisant éventuellement une durée grace et/ou keep en paramètre) on ne purge pas la page du cache, on marque juste sa TTL à ZÉRO. À partir de là, les temps grace et keep peuvent tout de même entrer en jeu. Si grace a été posé, au prochain appel de la page, l’internaute aura alors une page servie immédiatement par Varnish, mais elle sera appelée en arrière plan sur le serveur web pour être remise dans le cache pour le prochain visiteur.

Pour aller plus loin, on peut aussi utiliser le module xKey de Varnish, qui permet de choisir en batch quelles pages purger ou ne pas purger, de manière hard ou soft, à l’aide de clés de sélection décidées par les développeurs. Là on tiens du superbe \o/

Pour terminer, voici les pages de références, en anglais, qui m’ont aidées à faire mes nombreux tests et à écrire cette article :