Invalidation du cache en cascade. Partie 1

Depuis plusieurs années maintenant, comme presque tous les articles sur les approches avancées de la mise en cache recommandent d'utiliser les techniques suivantes en production:

  • Ajout aux noms de fichiers d'informations sur la version des données qu'ils contiennent (généralement sous la forme d'un hachage des données dans les fichiers).
  • Définition des en Cache-Control: max-age têtes HTTP Cache-Control: max-age et Expires , qui contrôlent le temps de mise en cache des documents (ce qui élimine la revalidation des documents pertinents pour les visiteurs revenant à la ressource).



Tous les outils de construction de projets que je connais prennent en charge l'ajout au nom des fichiers de hachage de leur contenu. Cela se fait à l'aide d'une règle de configuration simple (comme celle illustrée ci-dessous):

 filename: '[name]-[contenthash].js' 

Un tel soutien généralisé à cette technologie a conduit au fait que cette pratique est devenue extrêmement courante.

Les experts en performances de projets Web recommandent également d'utiliser des techniques de séparation de code . Ces techniques permettent de diviser le code JavaScript en lots distincts. Ces bundles peuvent être téléchargés par le navigateur en parallèle, ou même uniquement lorsqu'ils deviennent nécessaires, à la demande du navigateur.

L'un des nombreux avantages de la séparation de code, en particulier, lié aux meilleures techniques de mise en cache, est que les modifications apportées à un fichier séparé avec le code source n'entraînent pas l'invalidation du cache de l'ensemble complet. En d'autres termes, si une mise à jour de sécurité a été publiée pour le package npm créé par le développeur «X» et que le contenu de node_modules fragmenté par les développeurs, seul le fragment contenant les packages créés par «X» devra être modifié.

Le problème ici est que si tout cela est combiné, cela conduit rarement à une augmentation de l'efficacité de la mise en cache des données à long terme.

En pratique, les modifications apportées à l'un des fichiers de code source entraînent presque toujours l'invalidation de plusieurs fichiers de sortie du système d'assemblage de packages. Et cela est précisément dû au fait que des hachages ont été ajoutés aux noms de fichiers qui reflètent les versions du contenu de ces fichiers.

Problème de version de nom de fichier


Imaginez que vous avez créé et déployé un site Web. Vous avez utilisé le fractionnement de code. Par conséquent, la plupart du code JavaScript de votre site est chargé sur demande.

Dans le diagramme de dépendance suivant, vous pouvez voir le point d'entrée de la base de code - le fragment racine de main , ainsi que trois fragments de dépendance chargés de manière asynchrone - dep1 , dep2 et dep3 . Il existe également un fragment de vendor contenant toutes les dépendances de site de node_modules . Tous les noms de fichiers, conformément aux directives de mise en cache, incluent des hachages du contenu de ces fichiers.


Arbre de dépendance typique du module JavaScript

Étant dep2 que les dep3 dep2 et dep3 importent des modules à partir du fragment de vendor , alors en haut de leur code généré par le dep3 projet, nous trouverons très probablement des commandes d'importation qui ressemblent à ceci:

 import {...} from '/vendor-5e6f.mjs'; 

Réfléchissons maintenant à ce qui se passera si le contenu du fragment de vendor change.

Si cela se produit, le hachage dans le nom du fichier correspondant changera également. Et puisque le lien vers le nom de ce fichier se trouve dans les commandes d'importation pour les dep3 dep2 et dep3 , alors il faudra que ces commandes d'importation changent:

 -import {...} from '/vendor-5e6f.mjs'; +import {...} from '/vendor-d4a1.mjs'; 

Cependant, étant donné que ces commandes d'importation font partie du contenu des dep3 dep2 et dep3 , leur modification signifie que le hachage du contenu des fichiers dep2 et dep3 dep2 également. Et cela signifie que les noms de ces fichiers changeront également.

Mais cela ne s'arrête pas là. Étant donné que le fragment main importe les fragments dep2 et dep3 et que leurs noms de fichier ont changé, les commandes d'importation dans main changeront également:

 -import {...} from '/dep2-3c4d.mjs'; +import {...} from '/dep2-2be5.mjs'; -import {...} from '/dep3-d4e5.mjs'; +import {...} from '/dep3-3c6f.mjs'; 

Et enfin, puisque le contenu du fichier main a changé, le nom de ce fichier devra également changer.

Voici à quoi ressemblera le diagramme de dépendance.


Modules de l'arbre de dépendances affectés par une seule modification du code de l'un des nœuds feuilles de l'arbre

Cet exemple montre comment une petite modification de code effectuée dans un seul fichier a entraîné l'invalidation du cache de 80% des fragments du bundle.

Bien qu'il soit vrai que tous les changements n'entraînent pas de si tristes conséquences (par exemple, invalider le cache de nœud feuille conduit à invalider le cache de tous les nœuds jusqu'à la racine, mais invalider le cache racine ne provoque pas l'invalidation en cascade atteignant la capture de feuille), dans un monde idéal nous n'aurions pas à traiter les invalidations inutiles du cache.

Cela nous amène à la question suivante: "Est-il possible de bénéficier des ressources immuables et de la mise en cache à long terme, sans souffrir d'invalidations de cache en cascade?"

Approches de résolution de problèmes


Le problème avec les hachages du contenu des fichiers dans les noms de fichiers, d'un point de vue technique, n'est pas que les hachages sont dans les noms. Cela réside dans le fait que ces hachages apparaissent dans d'autres fichiers. Par conséquent, le cache de ces fichiers est désactivé lors de la modification des hachages dans les noms des fichiers dont ils dépendent.

La solution à ce problème consiste à utiliser le langage de l'exemple ci-dessus pour permettre d'importer le fragment vendor par les fragments dep2 et dep3 sans spécifier les informations de version du fichier de fragment vendor . Ce faisant, vous devez vous assurer que la version du vendor téléchargée est correcte, en tenant compte des versions actuelles de dep2 et dep3 .

Il s'est avéré qu'il existe plusieurs façons d'atteindre cet objectif:

  • Importez des cartes.
  • Travailleurs des services.
  • Scripts natifs pour le chargement des ressources.

Considérez ces mécanismes.

Approche n ° 1: Importer des cartes


Les cartes d'importation sont la solution la plus simple à l'invalidation du cache en cascade. De plus, ce mécanisme est plus facile à mettre en œuvre. Mais, malheureusement, il n'est pris en charge que dans Chrome (cette fonctionnalité doit en outre être explicitement activée ).

Malgré cela, je veux commencer par l'histoire des cartes d'importation, car je suis sûr que cette décision deviendra la plus courante à l'avenir. De plus, la description du travail avec les cartes d'importation aidera à expliquer les caractéristiques d'autres approches pour résoudre notre problème.

L'utilisation des cartes d'importation pour empêcher l'invalidation du cache en cascade se compose de trois étapes.

▍Étape 1


Vous devez configurer le bundle de sorte que lors de la construction du projet, il n'inclue pas de hachage du contenu des fichiers dans leurs noms.

Si vous assemblez un projet dont les modules sont illustrés dans le diagramme de l'exemple précédent, sans inclure de hachage de leur contenu dans les noms de fichiers, les fichiers dans le répertoire de sortie du projet ressembleront à ceci:

 dep1.mjs dep2.mjs dep3.mjs main.mjs vendor.mjs 

Les commandes d'importation dans les modules correspondants n'incluront pas non plus les hachages:

 import {...} from '/vendor.mjs'; 

▍Étape 2


Vous devez utiliser un outil comme rev-hash et l'utiliser pour générer une copie de chaque fichier avec un hachage ajouté à son nom indiquant la version de son contenu.
Une fois cette partie du travail terminée, le contenu du répertoire de sortie devrait ressembler à celui illustré ci-dessous (notez qu'il existe maintenant deux options pour chaque fichier):

 dep1-b2c3.mjs", dep1.mjs dep2-3c4d.mjs", dep2.mjs dep3-d4e5.mjs", dep3.mjs main-1a2b.mjs", main.mjs vendor-5e6f.mjs", vendor.mjs 

▍Étape 3


Vous devez créer un objet JSON qui stocke des informations sur la correspondance de chaque fichier au nom duquel il n'y a pas de hachage avec chaque fichier au nom duquel il y a un hachage. Cet objet doit être ajouté aux modèles HTML.

Cet objet JSON est une carte d'importation. Voici à quoi cela pourrait ressembler:

 <script type="importmap"> {  "imports": {    "/main.mjs": "/main-1a2b.mjs",    "/dep1.mjs": "/dep1-b2c3.mjs",    "/dep2.mjs": "/dep2-3c4d.mjs",    "/dep3.mjs": "/dep3-d4e5.mjs",    "/vendor.mjs": "/vendor-5e6f.mjs",  } } </script> 

Après cela, chaque fois que le navigateur voit la commande d'importation du fichier situé à l'adresse correspondant à l'une des clés de la carte d'importation, le navigateur importe le fichier qui correspond à la valeur de clé.

Si vous utilisez cette carte d'importation comme exemple, vous pouvez découvrir que la commande d'importation qui fait référence au fichier /vendor.mjs va en fait interroger et charger le fichier /vendor-5e6f.mjs :

 //    `/vendor.mjs`,  `/vendor-5e6f.mjs`. import {...} from '/vendor.mjs'; 

Cela signifie que le code source des modules peut se référer assez facilement aux noms de fichiers des modules qui ne contiennent pas de hachage, et le navigateur télécharge toujours les fichiers dont les noms contiennent des informations sur les versions de leur contenu. Et, comme il n'y a pas de hachage dans le code source des modules (ils ne sont présents que dans la carte d'importation), les modifications apportées à ces hachages n'entraîneront pas l'invalidation de modules autres que ceux dont le contenu a vraiment changé.

Vous vous demandez peut-être maintenant pourquoi j'ai créé une copie de chaque fichier au lieu de simplement renommer les fichiers. Cela est nécessaire pour prendre en charge les navigateurs qui ne peuvent pas fonctionner avec les cartes d'importation. Dans l'exemple précédent, ces navigateurs ne verront que le fichier /vendor.mjs et téléchargeront simplement ce fichier, en faisant comme d'habitude, en rencontrant des constructions similaires. En conséquence, il s'avère que les deux fichiers doivent exister sur le serveur.

Si vous souhaitez voir les cartes d'importation en action, voici un ensemble d'exemples qui illustrent toutes les façons de résoudre le problème d'invalidation du cache en cascade présenté dans cet article. Jetez également un œil à la configuration de l'assembly du projet , au cas où vous souhaiteriez savoir comment j'ai généré la carte d'importation et les hachages de version pour chaque fichier.

À suivre ...

Chers lecteurs! Êtes-vous au courant de l'invalidation du cache en cascade?


Source: https://habr.com/ru/post/fr472860/


All Articles