Kaskadierende Cache-Ungültigmachung. Teil 1

Seit einigen Jahren empfiehlt fast jeder Artikel über fortgeschrittene Caching-Ansätze die Verwendung der folgenden Techniken in der Produktion:

  • Hinzufügen von Informationen zu der Version der darin enthaltenen Daten zu den Dateinamen (normalerweise in Form eines Hash der Daten in den Dateien).
  • Festlegen von HTTP-Headern Cache-Control: max-age und Expires , die die Caching-Zeit von Materialien steuern (wodurch die erneute Validierung der relevanten Materialien für Besucher, die zur Ressource zurückkehren, entfällt).



Alle mir bekannten Tools zum Erstellen von Projekten unterstützen das Hinzufügen von Inhalten zur Hash-Datei. Dies erfolgt mithilfe einer einfachen Konfigurationsregel (wie unten gezeigt):

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

Diese weit verbreitete Unterstützung für diese Technologie hat dazu geführt, dass diese Praxis äußerst verbreitet geworden ist.

Experten für die Leistung von Webprojekten empfehlen außerdem die Verwendung von Codetrennungstechniken . Diese Techniken ermöglichen das Aufteilen von JavaScript-Code in separate Bundles. Solche Bundles können vom Browser auf Anfrage des Browsers parallel oder sogar nur dann heruntergeladen werden, wenn sie erforderlich werden.

Einer der vielen Vorteile der Codetrennung, insbesondere im Zusammenhang mit erweiterten Caching-Techniken, besteht darin, dass Änderungen an einer separaten Quelldatei nicht den gesamten Cache des Bundles ungültig machen. Mit anderen Worten, wenn ein Sicherheitsupdate für das vom Entwickler "X" erstellte npm-Paket veröffentlicht wurde und der Inhalt von node_modules von Entwicklern fragmentiert wird, muss nur das Fragment node_modules , das die von "X" erstellten Pakete enthält.

Das Problem hierbei ist, dass wenn all dies kombiniert wird, dies selten zu einer Steigerung der Effizienz des langfristigen Daten-Caching führt.

In der Praxis führen Änderungen an einer der Quellcodedateien fast immer zur Ungültigmachung von mehr als einer Ausgabedatei des Paketassemblierungssystems. Dies liegt genau an der Tatsache, dass den Dateinamen Hashes hinzugefügt wurden, die die Versionen des Inhalts dieser Dateien widerspiegeln.

Problem bei der Versionierung des Dateinamens


Stellen Sie sich vor, Sie haben eine Website erstellt und bereitgestellt. Sie haben die Codeaufteilung verwendet. Daher wird der größte Teil des JavaScript-Codes Ihrer Website auf Anfrage geladen.

Im nächsten Abhängigkeitsdiagramm sehen Sie den Codebasis-Einstiegspunkt - das dep1 von main sowie drei asynchron geladene Abhängigkeitsfragmente - dep1 , dep2 und dep3 . Es gibt auch ein vendor , das alle Site-Abhängigkeiten von node_modules . Alle Dateinamen enthalten gemäß den Caching-Richtlinien Hashes des Inhalts dieser Dateien.


Typischer Abhängigkeitsbaum für JavaScript-Module

Da dep2 und dep3 Module aus dem vendor Snippet importieren, werden wir im oberen Teil ihres vom Project dep3 generierten Codes höchstwahrscheinlich Importbefehle finden, die ungefähr so ​​aussehen:

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

Lassen Sie uns nun darüber nachdenken, was passieren wird, wenn sich der Inhalt des vendor ändert.

In diesem Fall ändert sich auch der Hash im Namen der entsprechenden Datei. Und da sich der Link zum Namen dieser Datei in den Importbefehlen für dep2 und dep3 , müssen sich diese Importbefehle ändern:

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

Da diese Importbefehle jedoch Teil des Inhalts der dep2 und dep3 , bedeutet eine Änderung, dass sich auch der Hash des Inhalts der dep2 und dep3 Dateien dep3 . Und das bedeutet, dass sich auch die Namen dieser Dateien ändern.

Aber das endet nicht dort. Da das main die dep2 und dep3 Fragmente importiert und sich ihre Dateinamen geändert haben, ändern sich auch die Importbefehle in main :

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

Und schließlich muss sich auch der Name dieser Datei ändern, da sich der Inhalt der main geändert hat.

So sieht nun das Abhängigkeitsdiagramm aus.


Module im Abhängigkeitsbaum, die von einer einzelnen Änderung des Codes eines der Blattknoten des Baums betroffen sind

Dieses Beispiel zeigt, wie eine kleine Codeänderung in nur einer Datei zur Ungültigmachung des Caches von 80% der Bundle-Fragmente führte.

Zwar führen nicht alle Änderungen zu solch traurigen Konsequenzen (zum Beispiel führt die Ungültigmachung des Blattknotencaches zur Ungültigmachung des Cache aller Knoten bis zur Wurzel, aber die Ungültigmachung des Wurzelcaches führt in einer idealen Welt nicht zu einer kaskadierenden Ungültigkeit, die den Blattfang erreicht) Wir müssten uns nicht mit unnötigen Cache-Ungültigmachungen befassen.

Dies führt uns zu der folgenden Frage: "Ist es möglich, die Vorteile unveränderlicher Ressourcen und langfristiger Zwischenspeicherung zu nutzen, ohne unter kaskadierenden Cache-Invalidierungen zu leiden?"

Lösungsansätze


Das Problem mit den Hashes des Inhalts von Dateien in Dateinamen besteht aus technischer Sicht nicht darin, dass die Hashes in Namen sind. Es liegt in der Tatsache, dass diese Hashes in anderen Dateien erscheinen. Infolgedessen wird der Cache dieser Dateien deaktiviert, wenn die Hashes in den Namen der Dateien geändert werden, von denen sie abhängen.

Die Lösung für dieses Problem besteht darin, die Sprache des obigen Beispiels zu verwenden, um das Importieren des vendor durch die dep2 und dep3 ohne die Versionsinformationen der vendor anzugeben. Dabei müssen Sie sicherstellen, dass die heruntergeladene vendor korrekt ist, wobei die aktuellen Versionen von dep2 und dep3 .

Wie sich herausstellte, gibt es mehrere Möglichkeiten, um dieses Ziel zu erreichen:

  • Karten importieren.
  • Servicemitarbeiter.
  • Native Skripte zum Laden von Ressourcen.

Betrachten Sie diese Mechanismen.

Ansatz 1: Karten importieren


Importzuordnungen sind die einfachste Lösung für die Kaskadierung der Cache-Ungültigmachung. Darüber hinaus ist dieser Mechanismus am einfachsten zu implementieren. Leider wird es nur in Chrome unterstützt (diese Funktion muss außerdem explizit aktiviert sein ).

Trotzdem möchte ich mit der Geschichte über Importkarten beginnen, da ich sicher bin, dass diese Entscheidung in Zukunft die häufigste sein wird. Darüber hinaus wird die Beschreibung der Arbeit mit Importkarten dazu beitragen, die Merkmale anderer Ansätze zur Lösung unseres Problems zu erläutern.

Die Verwendung von Importzuordnungen zur Verhinderung einer kaskadierenden Cache-Ungültigmachung besteht aus drei Schritten.

▍Schritt 1


Sie müssen den Bundler so konfigurieren, dass beim Erstellen des Projekts keine Hashes des Inhalts von Dateien in ihren Namen enthalten sind.

Wenn Sie ein Projekt zusammenstellen, dessen Module im Diagramm aus dem vorherigen Beispiel dargestellt sind, ohne Hashes ihres Inhalts in die Dateinamen aufzunehmen, sehen die Dateien im Projektausgabeverzeichnis folgendermaßen aus:

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

Importbefehle in den entsprechenden Modulen enthalten auch keine Hashes:

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

▍Schritt 2


Sie müssen ein Tool wie rev-hash verwenden und damit eine Kopie jeder Datei erstellen, deren Name mit einem Hash versehen ist, der die Version des Inhalts angibt.
Nachdem dieser Teil der Arbeit erledigt ist, sollte der Inhalt des Ausgabeverzeichnisses ungefähr so ​​aussehen wie der unten gezeigte (beachten Sie, dass es jetzt zwei Optionen für jede Datei gibt):

 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 

▍Schritt 3


Sie müssen ein JSON-Objekt erstellen, das Informationen über die Entsprechung jeder Datei speichert, in deren Namen kein Hash vorhanden ist, zu jeder Datei, in deren Namen ein Hash vorhanden ist. Dieses Objekt muss zu HTML-Vorlagen hinzugefügt werden.

Dieses JSON-Objekt ist eine Importzuordnung. So könnte es aussehen:

 <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> 

Wenn der Browser danach den Importbefehl der Datei an der Adresse sieht, die einem der Schlüssel der Importzuordnung entspricht, importiert der Browser die Datei, die dem Schlüsselwert entspricht.

Wenn Sie diese /vendor.mjs als Beispiel verwenden, können Sie feststellen, dass der Importbefehl, der auf die Datei /vendor.mjs verweist, die Datei /vendor.mjs tatsächlich /vendor-5e6f.mjs und /vendor-5e6f.mjs :

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

Dies bedeutet, dass der Quellcode der Module leicht auf die Dateinamen von Modulen verweisen kann, die keine Hashes enthalten, und der Browser immer Dateien herunterlädt, deren Namen Informationen über Versionen ihres Inhalts enthalten. Und da der Quellcode der Module keine Hashes enthält (sie sind nur in der Importzuordnung vorhanden), führen Änderungen an diesen Hashes nicht zur Ungültigmachung anderer Module als derjenigen, deren Inhalt sich tatsächlich geändert hat.

Vielleicht fragen Sie sich jetzt, warum ich eine Kopie jeder Datei erstellt habe, anstatt nur die Dateien umzubenennen. Dies ist erforderlich, um Browser zu unterstützen, die nicht mit Importkarten arbeiten können. Im vorherigen Beispiel sehen solche Browser nur die Datei /vendor.mjs und laden diese Datei einfach herunter, wobei sie wie /vendor.mjs und auf ähnliche Konstrukte stoßen. Infolgedessen stellt sich heraus, dass beide Dateien auf dem Server vorhanden sein müssen.

Wenn Sie Importzuordnungen in Aktion sehen möchten, finden Sie hier eine Reihe von Beispielen , die alle Möglichkeiten zur Lösung des in diesem Artikel gezeigten Problems der Kaskadierung der Cache-Ungültigkeit veranschaulichen. Sehen Sie sich auch die Konfiguration der Projektassembly an , falls Sie erfahren möchten, wie ich die Importzuordnung und die Versions-Hashes für jede Datei generiert habe.

Fortsetzung folgt…

Liebe Leser! Ist Ihnen eine kaskadierende Cache-Ungültigmachung bekannt?


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


All Articles