6 Minutes read

Implémentation de la gestion du cache dans Drupal : Une approche terre-à-terre (4/4)

Zoom sur le rendu du cache et conclusion.

Comme nous l’avons vu dans les parties précédentes, disponibles sur le blog Medium ekino-France, le cache Drupal est stocké lors du premier rendu et il sera ensuite accessible pour une restitution rapide. On peut donc penser que c’est lors du processus de sauvegarde du cache que le plus gros du travail est réalisé. Pour vérifier cela et aller au bout de ma curiosité, j’ai continué à fouiller les mécanismes de génération et de récupération du cache Drupal.

Mécanismes pour le module Page Cache

Nous avons déjà pas mal défriché cette partie avec l’explication du fonctionnement du module dans la première partie. Je vais essayer toutefois d’entrer un peu plus dans le détail.

Lorsque le site utilise le module de Internal Page Cache, le contenu final est stocké avec son identifiant. Cet identifiant, ou cache ID, est formé par la combinaison de l’URL et du format accepté par la requête (html, jsonxml).

Les requêtes suivantes pour une même URL généreront donc le même identifiant, ce qui permettra la récupération du cache et sa restitution.

Tout cela se passe, comme nous le savons maintenant (voir première partie), dans notre middleware PageCache. Celui-ci génère donc l’identifiant à partir de l’URL et du format de la requête et va essayer de récupérer le cache ayant le même identifiant. S’il n’y en a pas, il reprendra le fil de l’exécution de Drupal pour en récupérer la réponse. Dans ce dernier cas, une fois la réponse prête, il va d’abord la sauvegarder dans le cache avec l’identifiant généré avant de la renvoyer au client. La prochaine requête générant le même identifiant aura donc accès à ce cache.

Schema du fonctionnement du PageCache middleware lorsqu’aucun cache n’est disponible

Pour le Dynamic Page Cache, c’est plus compliqué…

Dynamic Page Cache : mécanismes de sauvegarde du cache

La conservation du cache se fait, comme nous avons aussi pu le voir en première partie, dans le DynamicPageCacheSubscriber, mais cette fois sur l’événement “kernel.response”. La configuration du cache va alors définir la “cacheability”, de notre réponse. Cette “cacheability” (que je vais hasardeusement francisé en “cachabilité”) est construite lors de la génération de la réponse. La cachabilité comprend principalement les métadonnées de caches et repose particulièrement sur les contextes de cache que nous avons pu aborder dans la partie précédente.

Cette notion est centrale pour la récupération ultérieure de ce cache. Elle formera littéralement la carte d’identité d’une entrée de cache puisque Drupal extraira l’identifiant du cache en se basant sur la cachabilité de la réponse.

Concrètement, voici le code qui génère l’identifiant de cache pour le Dynamic Cache Page :

// DrupalCoreCacheVariationCache (depuis la 10.3)
protected function createCacheId(
array $keys,
CacheableMetadata &$cacheable_metadata
){
if($contexts = $cacheable_metadata->getCacheContexts()) {
$context_cache_keys = $this->cacheContextsManager->convertTokensToKeys($contexts);
$keys = array_merge($keys, $context_cache_keys->getKeys());
$cacheable_metadata = $cacheable_metadata->merge($context_cache_keys);
}

return implode(':', $keys);
}

Ici, le code extrait les cache-contexts en les formatant pour qu’ils puissent devenir des clés de cache. Ces nouvelles clés sont ensuite fusionnées avec les clés de cache initiales pour créer une chaîne de caractères qui sera notre cache id. Dans notre cas, pour le Dynamic Cache Page, la clé initiale est « response ».

A noter que pendant l’opération, lors de la conversion en clés, les cache-contexts sont triés par ordre alphabétique et que les clés de caches initiales seront toujours en première position dans la chaîne de caractères renvoyée.

Lorsque nous faisons une requête, par exemple pour afficher une page, la cachabilité de la requête (initial cacheabilty) sera définie par le contexte de la route (et non l’URL comme dans le module Internal Page Cache) et le format de la requête. Elle n’est donc pas identique à la cachabilité de la réponse qui est, comme nous l’avons déjà dit, construite lors du rendu selon les métadonnées configurées. Drupal crée donc un objet intermédiaire (ou plusieurs dans certains cas) qui fera le lien entre eux. C’est une instance de la classe CacheRedirect qui pointera vers le cache de la réponse (mais qui peut donc aussi pointer vers un autre objet CacheRedirect).

Schema simplifié du stockage du cache sur l’événement “kernel.response”

Ce fonctionnement permettra de récupérer notre cache pour la prochaine requête ayant les mêmes contextes.

Dynamic Page Cache : mécanismes de rendu du cache

Nous l’avons vu, pour récupérer un item de cache existant, c’est le DynamicPageCacheSubscriber qui est sollicité sur l’événement “kernel.request”. Il va pour cela faire appel à l’implémentation de l’interface VariationCacheInterface. Drupal propose dans son cœur une implémentation avec la classe VariationCache.

Pour retourner la réponse en cache, Drupal récupéra alors l’élément de cache, un CacheRedirect, qui correspond à la cachabilité initiale. Il va ensuite suivre la cible pointée par ce CacheRedirect, en la récupérant dans le stockage du cache, et empilera le contenu de la cible dans une pile de caches. Si ce contenu cible est un nouvel objet CacheRedirect, il continuera à récupérer la cible de ce dernier, et cela jusqu’à ce qu’il récupère un objet de type Response à conserver dans la pile. C’est donc le dernier élément de cette pile, de type Response, qui sera ensuite retourné.

La réponse qui est alors récupérée est, comme nous le savons maintenant, une réponse partielle car elle doit encore être complétée en remplaçant les placeholders par des composants rendus.

La reconstitution de la page web est alors réalisée dans une étape ultérieure. C’est toujours sur l’événement “kernel.response” qu’un autre Event Subscriber, le HtmlResponseSubscriber, va déclencher la récupération des éléments qui serviront à combler les placeholders.

On commence à s’aventurer ici dans la génération du rendu des pages dans Drupal et on risque de s’éloigner de notre sujet pour en aborder un autre au moins aussi dense… Pour éviter cela, je vais essayer de simplifier en disant que Drupal va extraire de l’instance de notre réponse partielle la cachabilité des éléments qui sont à joindre au contenu principal. S’il y a un cache de ces éléments, il sera récupéré, sinon il pourra être généré et mis en cache, si leur contexte le permet.

La classe Renderer sera alors responsable de générer le nouveau contenu avec les éléments récupérés. Ce contenu servira finalement pour écraser le contenu partiel de la réponse.

Schéma simplifié du mécanisme de rendu des placeholders avec cache …

Le traitement de la réponse pourra ainsi suivre son chemin jusqu’à être renvoyé au client.

A noter qu’au niveau de la récupération du cache, la classe VariationCache et la classe CacheRedirect ne sont présentes dans Drupal que depuis la version 10.2. Dans les versions précédentes de Drupal 8/9/10, les mécanismes restent semblables mais ont une implémentation qui diffère.

Conclusion

Nous avons exploré une partie de l’implémentation du cache Drupal, c’est un système complet et très puissant. Il m’a marqué pour sa capacité de gérer aussi finement le cache mais aussi par la sophistication des modules disponibles de base. Les CMS proposant de telles fonctionnalités sans installer d’extensions (parfois payantes) ne sont pas nombreux à ma connaissance.

Le cache Drupal est donc très utile mais, même en utilisant les modules de cache, Drupal exécutera pas mal de code PHP pour renvoyer une réponse. C’est un autre sujet mais vous pouvez avoir un point de vue intéressant sur certaines lourdeurs du runtime Drupal en écoutant la conférence donnée à ce sujet au DrupalCamp 2024 (téléchargez le support pour bien suivre la présentation) présentée par mon collègue Benjamin Rambaud.

Soyons fou ! Et si Drupal faisait moins de chose au runtime ?

Dans un contexte de forte charge, cela peut devenir un point de friction, surtout pour les sites sous Drupal dont l’optimisation des performances sont critiques pour le métier. Si vous ressentez ce plafond de verre en terme de performance, je vous invite encore une fois à visionner la conférence de Nicolas Perussel (aka @mamoot), sur l’optimisation de Drupal, donnée lors de la DrupalCon 2022. Elle vous donnera surement des pistes pour améliorer les performances de votre site.

https://medium.com/media/0c426afbeee3909b92d6429931149734/href

Dans certains cas il peut aussi être intéressant de regarder ce que peut apporter un cache serveur comme Varnish, un service de cache encore plus véloce mais aussi relativement facile à configurer pour utiliser les cache-tags de Drupal (voir la documentation à ce sujet).

D’ailleurs, si vous souhaitez en savoir plus sur les cache tags dans varnish n’hésitez pas à lire l’article de mon collègue Quentin Somazzi sur ce sujet.

Varnish tags, my journey to fall in love with the cache

Autre possibilité à tenir en compte, il peut être intéressant d’utiliser un cache dans le cloud grâce aux services d’un CDN. Une option plus que valable pour ceux qui ont des sites multilingues avec des utilisateurs dans le monde entier.

En ce qui me concerne, je pense être arrivé au bout de mes interrogations initiales. J’ai appris beaucoup de choses mais malgré mes efforts pour appréhender globalement la gestion du cache, certains fonctionnements, sans être “magiques”, restent pour moi encore flous. Je me rends compte que je n’ai fait qu’effleurer le sujet. J’ai l’impression qu’il me reste encore pas mal de matière à creuser.

Je suis donc loin d’avoir fini de parcourir le code Drupal pour mieux comprendre les fonctionnements de ce CMS très complet.


Implémentation de la gestion du cache dans Drupal : Une approche terre-à-terre (4/4) was originally published in ekino-france on Medium, where people are continuing the conversation by highlighting and responding to this story.