Caching in Kette
Übersicht
Datenfluss
Bei der Bereitstellung einer Seite von einem Server auf einen Client-Browser ist eine Vielzahl von Systemen und Subsystemen involviert. Wer genau hinsieht, erkennt, dass die Daten von der Quelle bis zum Ziel verschiedene Stationen durchlaufen, von denen jede ein potenzieller Caching-Kandidat ist.
Datenfluss einer typischen CMS-Anwendung
Starten wir unsere Reise mit einem Datenelement, das auf einer Festplatte gespeichert ist und in einem Browser angezeigt werden muss.
Hardware und Betriebssystem
Zunächst verfügt die Festplatte (Hard Disk Drive, HDD) selbst über einen in der Hardware eingebauten Cache. Zweitens verwendet das Betriebssystem, das die Festplatte bereitstellt, freien Speicher, um häufig aufgerufene Blöcke zwischenzuspeichern und so den Zugriff zu beschleunigen.
Content-Repository
Auf der nächsten Ebene folgt CRX oder Oak – die von AEM verwendete Dokumentdatenbank. CRX und Oak teilen die Daten in Segmente, die im Speicher zwischengespeichert werden können, sodass ein langsamerer Zugriff auf die Festplatte vermieden wird.
Drittanbieterdaten
Die meisten größeren Web-Installationen verfügen auch über Drittanbieterdaten: Daten aus einem Produktinformationssystem, einem Kundenbeziehungs-Management-System, einer älteren Datenbank oder einem beliebigen anderen Web-Dienst. Diese Daten müssen, wenn sie benötigt werden, nicht jedes Mal aus der Quelle abgerufen werden – vor allem dann nicht, wenn klar ist, dass sie sich nicht allzu häufig ändern. Sie können also zwischengespeichert werden, wenn sie nicht mit der CRX-Datenbank synchronisiert werden.
Geschäftsebene – App/Modell
Normalerweise rendern Ihre Vorlagenskripte den aus CRX stammenden Rohinhalt nicht über die JCR-API. Wahrscheinlich gibt es eine Geschäftsebene dazwischen, die Daten in einem Business-Domain-Objekt zusammenführt, berechnet und/oder transformiert. RIchtig – wenn diese Vorgänge kostspielig sind, sollten Sie sie zwischenspeichern.
Markup-Fragmente
Das Modell ist jetzt die Grundlage für das Rendering des Markups für eine Komponente. Warum nicht auch das gerenderte Modell zwischenspeichern?
Dispatcher, CDN und andere Proxys
Die gerenderte HTML-Seite wird an den Dispatcher weitergeleitet. Wir haben bereits besprochen, dass der Hauptzweck des Dispatchers (trotz seines Namens) darin besteht, HTML-Seiten und andere Web-Ressourcen zwischenzuspeichern. Bevor die Ressourcen den Browser erreichen, können sie einen Reverse-Proxy, der zwischenspeichern kann, und ein CDN, das auch zum Caching verwendet wird, durchlaufen. Der Client kann sich in einem Büro befinden, das nur über einen Proxy Web-Zugriff gewährt. Und der Proxy entscheidet sich möglicherweise zum Caching und Speichern von Traffic.
Browsercache
Nicht zuletzt speichert auch der Browser zwischen. Dies wird häufig übersehen, obwohl es sich um den nächstgelegenen und schnellsten Cache in der Caching-Kette handelt. Leider wird er zwar nicht zwischen Benutzenden, aber immerhin zwischen verschiedenen Anfragen einer Benutzerin oder eines Benutzers geteilt.
Wo und wann zwischenspeichern
Hierbei geht es um eine lange Kette potenzieller Caches. Und wir alle haben es schon erlebt, Probleme durch veraltete Inhalte. Aber angesichts der großen Anzahl an Phasen ist es ein Wunder, dass das Ganze meistens überhaupt funktioniert.
Aber wo in dieser Kette ist es überhaupt sinnvoll, zwischenzuspeichern? Am Anfang? Am Ende? Überall? Es kommt darauf an … und hängt von einer Vielzahl von Faktoren ab. Schon bei zwei Ressourcen auf derselben Website könnte es jeweils eine andere gewünschte Antwort auf diese Frage geben.
Um Ihnen eine grobe Vorstellung davon zu geben, welche Faktoren Sie in Betracht ziehen könnten …
Gültigkeitsdauer (Time to Live, TTL): Wenn Objekte eine kurze inhärente Gültigkeitsdauer haben (bei Traffic-Daten ggf. kürzer als bei Wetterdaten), ist Caching möglicherweise nicht sinnvoll.
Produktionskosten: Wie teuer (in Bezug auf CPU-Zyklen und I/O) sind die Reproduktion und Bereitstellung eines Objekts? Sind diese Vorgänge kostengünstig, kann auf das Caching ggf. verzichtet werden.
Größe: Große Objekte erfordern, dass mehr Ressourcen zwischengespeichert werden. Dies könnte ein begrenzender Faktor sein und muss gegen den Nutzen abgewogen werden.
Zugriffshäufigkeit: Wenn selten auf Objekte zugegriffen wird, ist das Caching möglicherweise nicht effektiv. Sie würden bloß alt oder invalidiert werden, bevor sie zum zweiten Mal aus dem Cache aufgerufen werden. Solche Elemente würden einfach Speicherressourcen blockieren.
Gemeinsamer Zugriff: Daten, die von mehr als einer Entität verwendet werden, sollten weiter oben in der Kette zwischengespeichert werden. Tatsächlich ist die Caching-Kette keine Kette, sondern eine Baumstruktur. Ein Datenelement im Repository kann von mehr als einem Modell verwendet werden. Diese Modelle können wiederum von mehr als einem Render-Skript verwendet werden, um HTML-Fragmente zu generieren. Diese Fragmente sind auf mehreren Seiten enthalten, die an mehrere Benutzende mit ihren privaten Caches im Browser verteilt werden. „Gemeinsam“ bezieht sich hier also nicht nur auf die gemeinsame Verwendung zwischen Personen, sondern auch zwischen Software-Teilen. Wenn Sie einen potenziellen „freigegebenen“ Cache finden möchten, verfolgen Sie einfach die Baumstruktur zum Stammverzeichnis zurück und suchen Sie nach einem gemeinsamen Vorgänger. Dort sollten Sie zwischenspeichern.
Räumliche Verteilung: Wenn Sie Benutzende auf der ganzen Welt haben, kann die Verwendung eines verteilten Cache-Netzwerks dazu beitragen, die Latenz zu verringern.
Netzwerkbandbreite und -latenz: Apropos Latenz – wer sind Ihre Kundinnen und Kunden und welche Netzwerktypen nutzen diese? Vielleicht sind Ihre Kundinnen und Kunden aus einem weniger entwickelten Land und nutzen Mobilgeräte und eine 3G-Verbindung mit Smartphones der älteren Generation. Denken Sie dann darüber nach, kleinere Objekte zu erstellen und sie in den Browser-Caches zwischenzuspeichern.
Diese Aufstellung ist bei weitem nicht vollständig, aber Sie sollten inzwischen eine gute Vorstellung davon haben, worum es geht.
Grundlegende Regeln für das Caching in Kette
Wie schon erwähnt, Caching ist schwierig. Im Folgenden finden Sie einige Grundregeln, die wir aus früheren Projekten abgeleitet haben und mit denen Sie Probleme in Ihrem Projekt vermeiden können.
Vermeiden von doppeltem Caching
Jede im letzten Kapitel eingeführte Ebene liefert einen gewissen Wert in der Caching-Kette. Entweder durch Einsparung von Rechenzyklen oder durch Annäherung der Daten an die Verbrauchenden. Es ist nicht falsch, ein Datenelement in mehreren Phasen der Kette zwischenzuspeichern. Aber Sie sollten immer Vorteile und Belastungen der nächsten Phase berücksichtigen. Das Caching einer vollständigen Seite im Veröffentlichungssystem bietet in der Regel keinen Vorteil, weil dies bereits im Dispatcher erfolgt ist.
Mischen von Invalidierungsstrategien
Es gibt drei grundlegende Invalidierungsstrategien:
- TTL: Ein Objekt läuft nach einer festen Zeitdauer ab (z. B. „2 Stunden ab jetzt“).
- Ablaufdatum: Ein Objekt läuft zu einem bestimmten Zeitpunkt in der Zukunft ab (z. B. „10:00 Uhr am 10. Juni 2019“).
- Ereignisbasiert: Ein Objekt wird explizit durch ein Ereignis invalidiert, das auf der Plattform aufgetreten ist (z. B. bei Änderung oder Aktivierung einer Seite).
Nun können Sie verschiedene Strategien auf verschiedenen Cache-Ebenen verwenden. Einige davon sind allerdings „toxisch“.
Ereignisbasierte Invalidierung
Rein ereignisbasierte Invalidierung: Invalidierung vom inneren Cache zur äußeren Ebene
Eine rein ereignisbasierte Invalidierung ist am einfachsten zu verstehen und in der Theorie umzusetzen und bietet die höchste Genauigkeit.
Einfach gesagt, werden die Caches nacheinander invalidiert, nachdem sich ein Objekt geändert hat.
Sie müssen nur an eine Regel denken:
Immer von inneren zum äußeren Cache invalidieren. Wenn Sie zuerst einen äußeren Cache invalidiert haben, wird möglicherweise veralteter Inhalt von einem inneren Cache erneut zwischengespeichert. Gehen Sie nicht davon aus, wann ein Cache wieder sauber ist – Sie müssen es wissen. Am besten durch Auslösen der Invalidierung des äußeren Caches nach der Invalidierung des inneren Caches.
Soweit in der Theorie. Aber in der Praxis gibt es eine Reihe von Fallstricken. Die Ereignisse müssen verteilt werden – potenziell über ein Netzwerk. In der Praxis ist dies das schwierigste Vorhaben bei der Invalidierung.
Automatische Reparatur
Im Falle einer ereignisbasierten Invalidierung sollten Sie über einen Notfallplan verfügen. Was geschieht, wenn ein Invalidierungsereignis verpasst wird? Eine einfache Strategie könnte darin bestehen, eine Invalidierung oder Bereinigung nach einer bestimmten Zeit durchzuführen. Also angenommen, Sie haben das Ereignis verpasst und es wird nun veralteter Inhalt bereitgestellt. Ihre Objekte haben jedoch auch eine implizite TTL von lediglich mehreren Stunden (Tagen). Schließlich führt das System also eine automatische Selbstreparatur durch.
Rein TTL-basierte Invalidierung
Nicht synchronisierte TTL-basierte Invalidierung
Dies ist ebenfalls ein ziemlich gängiges System. Sie stapeln mehrere Cache-Ebenen übereinander. Jede dieser Ebene dient einem Objekt dann für eine bestimmte Zeit.
Dies lässt sich leicht implementieren. Leider ist es aber schwierig, die effektive Lebensdauer eines Datenelements vorherzusagen.
Äußerer Cache zur Verlängerung der Lebensdauer eines inneren Objekts
Sehen Sie sich die obige Abbildung an. Jede Caching-Ebene führt eine TTL von 2 Minuten ein. Die TTL müsste dann insgesamt auch 2 Minuten betragen, oder? Nicht ganz. Wenn die äußere Ebene das Objekt unmittelbar vor dem Ablauf abruft, verlängert die äußere Ebene die effektive Gültigkeitsdauer des Objekts. Die effektive Gültigkeitsdauer kann in diesem Fall zwischen 2 und 4 Minuten betragen. Angenommen, Sie haben mit Ihrer Geschäftsabteilung vereinbart, dass ein Tag tolerierbar ist, und Sie haben vier Cache-Ebenen. Die tatsächliche TTL jeder Ebene darf in diesem Fall nicht länger als sechs Stunden sein, wodurch sich die Cache-Fehlerrate erhöht.
Das heißt nicht, dass es ein schlechtes System ist. Sie sollten nur seine Grenzen kennen. Es ist eine sehr einfache Strategie, mit der man beginnen kann. Aber wenn der Traffic Ihrer Site zunimmt, sollten Sie eine präzisere Strategie in Betracht ziehen.
Synchronisieren der Invalidierungszeit durch Festlegen eines bestimmten Datums
Auf dem Ablaufdatum basierende Invalidierung
Die effektive Lebensdauer lässt sich besser vorhersehen, wenn Sie ein bestimmtes Datum für das innere Objekt festlegen und es an die äußeren Caches weitergeben.
Synchronisieren von Ablaufdaten
Allerdings können nicht alle Caches die Datumsangaben weitergeben. Und das kann schwierig werden, wenn der äußere Cache zwei innere Objekte mit unterschiedlichen Ablaufdaten aggregiert.
Mischen von ereignis- und TTL-basierter Invalidierung
Mischen von ereignis- und TTL-basierten Strategien
Ebenfalls geläufig im AEM-Kontext ist die Verwendung der ereignisbasierten Invalidierung bei inneren Caches (z. B. In-Memory-Caches, in denen Ereignisse nahezu in Echtzeit verarbeitet werden können) und TTL-basierte Caches auf äußerer Ebene – möglicherweise ohne Zugang zu einer expliziten Invalidierung.
In AEM hätten Sie dann einen In-Memory-Cache für Geschäftsobjekte und HTML-Fragmente in den Veröffentlichungssystemen, der invalidiert wird, wenn sich die zugrunde liegenden Ressourcen ändern, und Sie geben dieses Änderungsereignis an den Dispatcher weiter, der ebenfalls ereignisbasiert funktioniert. Vorgeschaltet könnte hier beispielsweise ein TTL-basiertes CDN sein.
Eine Ebene mit Caching auf Basis einer kurzen TTL vor einem Dispatcher könnte eine Spitze, die normalerweise nach einer automatischen Invalidierung auftreten würde, effektiv mildern.
Mischen von TTL- und ereignisbasierter Invalidierung
Toxisch: Vermischen von TTL- und ereignisbasierter Invalidierung
Diese Kombination ist toxisch. Platzieren Sie einen ereignisbasierten Cache nie nach einem TTL- oder ablaufbasierten Cache. Erinnern Sie sich noch an den Übertragungseffekt der reinen TTL-Strategie? Dieselbe Wirkung kann auch hier beobachtet werden. Nur kann es möglicherweise nicht erneut passieren, dass das Invalidierungsereignis des äußeren Caches bereits eingetreten ist, – vielleicht niemals. Dadurch kann die Lebensdauer des zwischengespeicherten Objekts unendlich werden.
TTL- und ereignisbasierte Kombination: Übertragung bis zur Unendlichkeit
Partielles Caching und In-Memory-Caching
Sie können in die Phase des Render-Vorgangs eingreifen, um Caching-Ebenen hinzuzufügen. Angefangen beim Abrufen von Remote-Datenübertragungsobjekten über das Erstellen lokaler Geschäftsobjekte bis hin zum Caching des gerenderten Markups einer einzelnen Komponente. Auf konkrete Implementierungsmöglichkeiten kommen wir in einem späteren Tutorial zurück. Aber vielleicht haben Sie die Implementierung einige dieser Caching-Ebenen bereits fest eingeplant. Das Mindeste, was wir daher an dieser Stelle tun können, ist auf die Grundprinzipien und Fallstricke hinzuweisen.
Einige Worte zur Warnung
Zugriffssteuerung respektieren
Die hier beschriebenen Techniken sind überaus leistungsstark und gehören unbedingt in die Toolbox jeder AEM-Entwicklungsperson. Aber lassen Sie sich von Ihrer Begeisterung nicht zu sehr mitreißen, sondern setzen Sie sie mit Bedacht ein. Wenn ein Objekt in einem Cache gespeichert und in nachfolgenden Anfragen für andere Benutzende freigegeben wird, bedeutet dies tatsächlich, die Zugriffssteuerung zu umgehen. Bei öffentlich zugänglichen Websites ist dies in der Regel kein Problem, kann aber zu einem werden, wenn sich eine Benutzerin oder ein Benutzer anmelden muss, um Zugriff zu erhalten.
Angenommen, Sie speichern das HTML-Markup eines Sites-Hauptmenüs in einem In-Memory-Cache, um es auf verschiedenen Seiten zu verwenden. Dies ist ein ideales Beispiel für die Speicherung von teilweise gerendertem HTML, da die Navigationserstellung aufgrund der Vielzahl der zu durchlaufenden Seiten normalerweise kostspielig ist.
Eine identische Menüstruktur wird nicht nur auf allen Seiten eingesetzt, sondern auch für alle Benutzenden, was für noch mehr Effizienz sorgt. Vielleicht gibt es aber auch einige Elemente im Menü, die ausschließlich für eine bestimmte Gruppe von Benutzenden reserviert sind. In diesem Fall kann das Caching etwas komplexer werden.