Image mise en avant pour l'article

Le cache Drupal : pour une meilleure gestion des performances !

20 mai 2022
Drupal - Technologies Web
Lorsque vous lancez un site, vous faites appel à plusieurs sources et bases de données : des fichiers, des images, des extensions ou des modules, etc. Pour que l’affichage soit fluide, votre gestionnaire de données Drupal doit faire cette opération en quelques millièmes de secondes pour ne pas nuire à l’expérience utilisateur, d’où la mise en cache.


La gestion du cache pour un site web a donc pour but d’améliorer le temps de réponse du site et d’éviter la surcharge du serveur. Ce temps de réponse est devenu un levier direct d’augmentation du trafic, notamment par le ranking réalisé par Google. Une gestion de cache performante permet également de réduire les requêtes et les calculs pour les serveurs. Il s’agit donc d’une démarche d’éco-conception des solutions web modernes.

Dans le but de rester compétitif face aux autres CMS, Drupal a mis en place une politique de cache performante. Véritable pierre angulaire des performances d'un site, le cache Drupal est basé sur des tags et est lié aux structures de contenu. Il est interfaçable avec des applicatifs de type Redis en back ou Varnish en front et offre plein d'outils pour l'utiliser et l'invalider.

Après ces quelques mots d’introduction, il est temps de découvrir les modules du cœur inclus dans ce système.

 

Les modules du cœur

Le fonctionnement de Drupal inclut des modules du cœur qui fournissent toutes les fonctionnalités de base et qui sont :

  • Internal Page Cache ou Cache de pages internes ;
  • Dynamic Page Cache ou Cache de pages dynamiques.

 

Internal Page Cache

Ce module de cache statique par page ne fonctionne que pour les utilisateurs anonymes. La première fois qu’un utilisateur anonyme demande une page, celle-ci est stockée puis réutilisée. Il est essentiellement recommandé pour les sites web de petite et taille moyenne.

Si votre site web propose un contenu personnalisé à des utilisateurs anonymes (dynamique, par session, comme un panier d'achat par exemple) vous devez désactiver ce module. En effet, le module Internal Page Cache part du principe que les pages sont identiques pour tous les utilisateurs anonymes.

Attention, ce module provoque des soucis avec les caches contexts et les caches max-age. Je vous recommande de le désactiver par défaut sauf pour les sites vitrines les plus simples et statiques.

 

Dynamic Page Cache

Ce module, fourni par Drupal, est recommandé pour les sites web de toutes les tailles. Il met en cache les pages sans les parties personnalisées. Il est utile pour tous les utilisateurs (anonymes et authentifiés).

Le module Dynamic Page Cache améliore les performances car il permet de mettre en cache les pages à contenu dynamique. Comment ? Les pages demandées par les utilisateurs (anonymes ou authentifiés) sont stockées lors de la première demande et peuvent ensuite être réutilisées. Les parties personnalisées sont exclues : elles sont transformées automatiquement en placeholders.

En fonction de la configuration de votre site et de la complexité de certaines pages, ce module peut augmenter considérablement la vitesse du site, même pour les utilisateurs authentifiés.

 

Utilisation du cache par Drupal

Il est plutôt rare d'interagir directement avec la Cache API de Drupal, en mettant, délibérément, un élément en cache ou en le récupérant du cache par exemple. En général, notre code a pour but de rendre des éléments (blocs, entités, formulaires, etc.) qui seront renvoyés sous forme de tableaux de rendu (render arrays). Tous ces tableaux de rendu sont traités et finissent par être envoyés dans un objet Response (HtmlResponse la plupart du temps).

Nous pouvons interagir ou voir le cache œuvre à travers ces 2 niveaux :

  • Les tableaux de rendu ou Render caching (aka fragment caching) ou render arrays,
  • Les réponses ou Response caching ou objet Response.

 

Render Caching

Dans les render arrays ou tableaux de rendu, des métadonnées de mise en cache (cacheability metadata) sont insérées et vont influencer la mise en cache du rendu de ces tableaux. Les cacheability metadata permettent de déterminer la validité du cache en fonction des dépendances, du contexte et du temps écoulé depuis la mise en cache.

Lorsqu'un moteur de rendu (Renderer) convertit le tableau de rendu en HTML, il part de l'élément situé à l'intérieur et travaille vers l'extérieur. Chaque étape est mise en cache et comprend l'élément et ses enfants. Les futures passes de rendu de ce même arbre utiliseront des versions en cache d'un élément dès que c’est possible afin d'accélérer les choses.

L'interaction la plus courante avec le cache dans Drupal se fait par le biais de ces cacheability metadata, en les modifiant, en les ajoutant ou en invalidant du cache par leur biais.

 

Response caching

Toutes les cacheability metadata définies dans les tableaux de rendu remontent jusqu'au Response object et permettent aux modules du cœur Internal Page Cache et Dynamic Page Cache de mettre en cache de manière valide une réponse à une requête.

De cette façon, les futures requêtes peuvent contourner toute la logique nécessaire à la construction de la page et renvoyer simplement une version en cache - en supposant qu'elle soit toujours valide.

Les caches Reverse Proxy comme Varnish ou CloudFlare sont des exemples de Response Caching.

 

 

Mise en cache manuel avec la cache API

Saviez-vous qu’il est possible d’effectuer une mise en cache manuelle avec la cache API ? Certes, le besoin n’est pas très courant mais il est toujours utile de connaître la procédure. Les éléments qui ne sont pas liés à un tableau de rendu peuvent être mis en cache à l'aide de la Cache API.

Par exemple, le code qui effectue des requêtes vers une API externe pourrait mettre en cache les résultats de cette requête localement et les réutiliser pendant une période déterminée. Cela augmente les performances de la page en éliminant une ou plusieurs requêtes qui prennent beaucoup plus de temps qu’une simple recherche d'un enregistrement dans le cache.

Comment utiliser la cache API ?

Maintenant, passons à la partie la plus importante : l’utilisation de la cache API. Rien de plus simple, il suffit des suivre ces quelques étapes :

  • Demandez un objet de cache via \Drupal::cache() ou en injectant un service de cache ;
  • Définissez une valeur Cache ID (cid) pour vos données. Un cid est une chaîne de caractères qui doit contenir suffisamment d'informations pour identifier les données de manière unique. Par exemple, si vos données contiennent des chaînes traduites, votre valeur cid doit inclure la langue du texte de l'interface sélectionnée pour la page ;
  • Appelez la méthode get() pour tenter une lecture du cache, afin de vérifier si le cache contient déjà vos données ;
  • Si vos données ne sont pas déjà dans le cache, calculez-les et ajoutez-les au cache à l'aide de la méthode set(). Le troisième argument de la méthode set() peut être utilisé pour contrôler la durée de vie de votre élément de cache.

Exemple d’utilisation de la cache API

$cid = 'mymodule_example:' . \Drupal::languageManager()
  ->getCurrentLanguage()
  ->getId();

$data = NULL;

$cache = \Drupal::cache()->get($cid);

if ($cache) {
  $data = $cache->data;
}

else {
  $data = my_module_complicated_calculation();
  \Drupal::cache()->set($cid, $data);
}

Vous souhaitez aller plus loin dans l'utilisation de la Cache API ? Alors, vous pouvez utiliser des Cache bins pour séparer les domaines de cache, supprimer des caches, ou encore, découvrir comment utiliser un autre backend que la base de données ?

Les cacheability metadata

Les Cacheability metadata se composent de 3 propriétés :

  • Les Cache tags, pour les dépendances aux données gérées par Drupal, comme les entités et la configuration ;
  • Les Cache contexts, pour les variations. C'est-à-dire les dépendances sur le contexte de la requête ;
  • Les Cache max-age, pour la mise en cache sensible au temps. C'est-à-dire les dépendances temporelles.

 

1. Les Cache Tags

 

> Pourquoi utiliser les caches tags ?

Les cache tags fournissent un moyen de suivre les éléments du cache qui dépendent de données gérées par Drupal.

C'est essentiel car le même contenu peut être réutilisé de nombreuses façons et donc il est impossible de savoir à l'avance où un contenu va être utilisé. Dans tous les endroits où le contenu est utilisé, il peut être mis en cache. Ce qui signifie que le même contenu peut être mis en cache à des dizaines d'endroits. Les cache tags permettent de répondre à la question de comment invalider tous les caches où un contenu particulier est présent.

Par exemple, en utilisant les cache tags, vous pouvez associer certaines données en cache à un node spécifique. Lorsque ce node est modifié et que son contenu change, les données en cache associées sont invalidées.

 

> C’est quoi les cache tags ?

Un cache tag est une chaîne de caractères. Par convention, ils sont de la forme thing:identifier et lorsqu'il n'y a pas de concept d'instances multiples d'une chose, il est de la forme simple thing. Il n'y a pas de syntaxe stricte, et la seule règle est : qu'il ne peut pas contenir d'espaces.

Drupal fournit automatiquement les cache tags pour les entités et la configuration - voir la classe de base Entity et la classe de base ConfigBase (tous les types d'entités spécifiques et les objets de configuration héritent de celles-ci).

Les données que Drupal gère se répartissent en 3 catégories de cache tags :

  • Les entités : elles disposent de cache tags de la forme <type d'entité ID>:<identité ID> ainsi que <type d'entité ID>_list et <type d'entité ID>_list:<bundle> pour invalider les listes d'entités.
    Par exemple :
    • node:5 - cache tag pour l'entité Node 5 (invalidé à chaque fois qu'il est modifié),
    • user:3 - cache tag pour l'entité User 3 (invalidé à chaque fois qu'il est modifié),
    • node_list - cache tag pour tous les Node (invalidée chaque fois qu'un node est mis à jour, supprimé ou créé. Applicable à tout type d'entité dans le format suivant : {type_d'entité}_list,
    • node_list:article - cache tag pour le bundle de node "articles" (type de contenu). Applicable à n'importe quel type d'entité + bundle dans le format suivant : {type_d'entité}_list:{bundle}.
  • Configuration : ces entités ont des cache tags de la forme config:<nom de la configuration>.
    Par exemple : config:system.performance - cache tag pour la configuration system.performance.
  • Custom : tout le reste, divers.
    Par exemple : library_info cache tag pour les asset libraries.

Bien que de nombreux types d'entités suivent un format de cache tags prévisible de <identification du type d'entité>:<identification de l'entité>, le code spécifique que nous développons ne devrait pas s'y fier. Il doit plutôt récupérer les cache tags à invalider pour une seule entité en utilisant sa méthode ::getCacheTags(), par exemple, $node->getCacheTags(), $user->getCacheTags(), etc.

 

> Comment définir les caches tags ?

Pour associer des cache tags à un élément de cache nous allons tout simplement fournir un tableau de chaines de caractères ['node:5', 'user:7'].

Selon l'endroit et le contexte d'utilisation cela peut être dans un tableau de rendu :

$userName = \Drupal::currentUser()->getAccountName();
$cacheTags =   User::load(\Drupal::currentUser()->id())->getCacheTags();

return [
  '#markup' => t('Bonjour @userName !', ['@userName' => $userName]),
  '#cache' => [
    'tags' => $cacheTags,
  ]
];

ou en argument d'une méthode set d'un CacheBackend :

$cache_backend->set(
  $cid, $data, Cache::PERMANENT, ['node:5', 'user:7']
);

 

> Comment les invalider ?

À l’aide du service cache_tags.invalidator à qui l’ont fourni un tableau de chaines de caractères via sa méthode invalidateTags :

\Drupal::service('cache_tags.invalidator')->invalidateTags(['node_list:article']);

 

2. Les Cache contexts

 

> Que permettent les cache contexts ?

Les cache contexts permettent de créer des variations de mises en cache dépendantes du contexte.

Par exemples :

  • Certaines données coûteuses à calculer dépendent du thème actif : différents résultats pour différents thèmes. Dans ce cas, il faut varier en fonction du cache context du thème ['theme'],
  • Lors de la création d'un tableau de rendu qui affiche un message personnalisé, le tableau de rendu varie par utilisateur. Il faut alors faire varier le tableau de rendu en fonction du cache context de l'utilisateur ['user'],
  • Si un élément varie selon que l'utilisateur actuel est authentifié ou non, l'utilisation d'un cache context du rôle anonyme ['user.role:anonymous'] indiquerait au cache de stocker 2 variantes de cet objet : une pour les utilisateurs ayant le rôle anonyme, et une autre pour tous les autres.

En général, les cache contexts sont issus du contexte de la requête, c'est-à-dire de l'objet Request. La plupart de l'environnement d'une application Web est dérivé du contexte de requête. Après tout, les réponses HTTP sont générées en grande partie en fonction des propriétés des requêtes HTTP qui les ont déclenchées.

Les cache contexts sont hiérarchisés par nature. L'exemple le plus simple à comprendre est le suivant : on fait varier quelque chose par utilisateur, il est inutile de le faire également par permissions. C'est-à-dire l'ensemble des permissions dont dispose un utilisateur, car la variation par utilisateur est déjà plus granulaire.

Un utilisateur a un ensemble de permissions, donc la mise en cache par utilisateur implique la mise en cache par permissions.

Maintenant, si une partie de la page varie en fonction de l'utilisateur et une autre en fonction des permissions, alors Drupal doit être suffisamment intelligent pour que la combinaison des deux : ne varie que par utilisateur. C'est là que Drupal peut exploiter les informations sur la hiérarchie pour ne pas créer de variations inutiles.

 

> C’est quoi les cache contexts ?

Comme les caches tags, les caches contexts sont des chaînes de caractères. Mais contrairement aux caches tags, la syntaxe a ici de la signification :

  • Les points séparent les parents des enfants,
  • Un cache context pluriel indique qu'un paramètre peut être spécifié ; pour l'utiliser : ajouter deux points, puis spécifier le paramètre souhaité.
    Si aucun paramètre n'est spécifié, tous les paramètres possibles sont pris en compte.

La liste des cache contexts du core :

cookies
  :name
headers
  :name
ip
languages
  :type
protocol_version
request_format
route
  .book_navigation
  .menu_active_trails
    :menu_name
  .name
session
  .exists
theme
timezone
url
  .path
    .is_front
    .parent
  .query_args
    :key
    .pagers
      :pager_id
  .site
user
  .is_super_user
  .node_grants
    :operation
  .permissions
  .roles
    :role

Des exemples concrets :

  • ['theme'] - vary by negotiated theme ;
  • ['user.roles'] - vary by the combination of roles ;
  • ['user.roles:anonymous'] - vary by whether the current user has the 'anonymous' role or not, i.e., "is anonymous user" ;
  • ['languages'] - vary by all language types: interface, content, etc. ;
  • ['languages:language_interface'] - vary by interface language — LanguageInterface::TYPE_INTERFACE ;
  • ['languages:language_content'] - vary by content language — LanguageInterface::TYPE_CONTENT ;
  • ['url'] - vary by the entire URL ;
  • ['url.query_args'] - vary by the entire given query string ;
  • ['url.query_args:foo'] - vary by the ?foo query argument ;
  • ['protocol_version'] - vary by HTTP 1 vs 2.

Il est possible de créer ses propres contextes en définissant des nouveaux services taggués. Les cache contexts sont des services taggués avec 'cache.context', dont les classes implémentent l'interface \Drupal\Core\Cache\Context\CacheContextInterface.

Enfin, les cache contexts ne fonctionnent pas pour les utilisateurs anonymes lorsque le module du cœur Internal Page Cache est activé. S'il est nécessaire d'adopter une stratégie de variation de cache en fonction de contexte, il faut donc le désactiver mais les performances globales en seront impactées.

 

3. Le Cache max-age

 

> À quoi sert le cache max-age ?

Le cache max-age fournit un moyen de créer des caches dépendants du temps.

Certaines données ne sont valables que pour une période limitée, et dans ce cas, vous voulez spécifier un âge maximum correspondant.

Cependant, dans le cas du cœur de Drupal, il n'y a pas de données valables que pour une période limitée et les mise en cache sont faites de façon permanente. L'invalidation s’y appuie entièrement sur les cache tags.

 

> C’est quoi un cache max-age ?

Un cache max-age est un nombre entier positif, exprimant un nombre de secondes.

Les durées maximales de cache sont transmises sous forme d'un seul entier, car un élément de cache donné ne peut logiquement avoir qu'une seule durée maximale.

Pour mieux comprendre, voici quelques exemples :

  • 60 signifie que l'élément peut être mis en cache pendant 60 secondes ;
  • 100 signifie que l'élément peut être mis en cache pendant 100 secondes ;
  • 0 signifie que l'élément peut être mis en cache pendant zéro seconde, c'est-à-dire qu'il ne peut pas être mis en cache ;
  • \Drupal\Core\Cache\Cache::PERMANENT signifie qu'il peut être mis en cache pour toujours, c'est-à-dire qu'il ne sera jamais invalidé que par des cache tags.

Donc, si vous voulez par exemple empêcher un tableau de rendu d'être mis en cache, vous devez spécifier max-age=0 dessus.

Exemple pour la plupart des tableaux de rendu :

$build['#cache']['max-age'] = 0 ;

Exemple dans une fonction :

\Drupal::cache()->set('my_cache_item', $school_list, \Drupal::time()->getRequestTime() + (86400)) ;

Si vous voulez changer le cache max-age d'un bloc à 0, vous devez implémenter la méthode getCacheMaxAge.

Malheureusement, cache max-age ne fonctionne pas pour les utilisateurs anonymes et le module du cœur de Drupal Internal Page Cache.

Pour terminer, Drupal et notamment la version Drupal 9 propose un système de cache très puissant qui vous permettra d'améliorer les performances de votre site. Pour une maîtrise plus approfondie, n’hésitez pas à consulter la documentation officielle de Drupal.

Pour poursuivre votre lecture, je vous propose cette petite selection :

 

Crédit photo : monsitj

Image mise en avant pour l'article
Adam Carton de Wiart Linkedin
Team Leader Drupal @Adimeo
WEBINAR
Migrer sur Drupal 9 : conseils, bonnes pratiques et intérêts de la migration !
Regardez le replay
Vous maitrisez la technologie Drupal et vous souhaitez participer à une aventure humaine ?
Adimeo recrute un Développeur Drupal Senior capable d’encadrer des projets et d’accompagner des développeurs juniors...
Voir notre offre d'emploi !