Mittwoch, Oktober 04, 2006

Idee zum WebCaching

Immer wieder treibt mich das Thema "HTTP-Caching" um -- ich nenne es auch gerne WebCaching, auch wenn der Begriff es nicht ganz trifft. Caching ist ein wichtiges Thema in vielen Bereichen, in denen es um Performanz geht. Prozessorarchitekturen, Datenbanken und webbasierte Systeme etc. nutzen Caching sozusagen als Turbolader. Vermeintlich langsame Systeme können durch geschickte Caching-Strategien rasend schnell werden, indem sie die von ihnen erwarteten Reaktionen vorhalten.

Kurz etwas Hintergrund zu HTTP und HTTP-basiertem Caching, bevor ich zu meiner Caching-Idee komme.

HTTP ist ein Anfrage/Antwort-Protokoll. Der Client, z.B. Sie über Ihren Webbrowser, schickt eine HTTP-Anfrage (request) an einen Webserver, und der Server beantwortet die Anfrage mit einer HTTP-Rückmeldung, einem HTTP-Reply. Es ist immer dasselbe. Ausschließlich der Client schickt Requests an den Server; und der Server reagiert nur auf diese Anfragen mit einem Reply. Das Prinzip ist sehr einfach.

Wenn Sie eine normale Webseite aufrufen, wird in aller Regel eine Vielzahl von HTTP-Requests an den Webserver geschickt. All das regelt der Browser automatisch für Sie und hängt ab von den Inhalten einer Webseite (Bilder, CSS etc.), die noch nachzuladen sind. Wenn Sie dieses Spielchen einmal verfolgen wollen, es gibt mit LiveHTTPHeaders eine nette Erweiterung für den Firefox, die Sie installieren können.

Caching funktioniert nur, wenn sich zwischen Client und Server eine oder mehrere Cache-Einheiten befinden, die den ganzen HTTP-Traffic mit abbekommen und gegebenenfalls HTTP-Requests "abfangen" und statt des Servers ein Reply an den Client zurückgeben. Der Server ist damit entlastet.

Bevor der Cache einen HTTP-Request abfängt, muss er wissen, für welche Requests er die Reply übernehmen darf. Dazu markiert der Server beim Versand einer Reply den HTTP-Header mit einem Hinweis, dass ein Cache die Nachricht für eine gewisse Zeit vorhalten darf. Der mitlesende Cache merkt sich das und antwortet zukünftig auf gleiche Anfragen in Stellvertretung für den Server.

Um es ein wenig konkreter zu machen: Sie dürfen sich einen Request wie eine Anfrage vorstellen die lautet "Gib mir den Inhalt der Webseite www.xyz.de/test.html", woraufhin der Webserver www.xyz.de bzw. ein zwischengeschalteter Cache den Inhalt test.html zurück liefert.

Bei diesem Ansatz gibt es ein Problem: Ändert sich test.html beim Webserver und ist die Caching-Zeit beim Cache noch nicht abgelaufen, liefert der Cache die "veraltete" Seite zurück -- und der Server kann den "neuen" Inhalt nicht ausliefern, da der Cache den HTTP-Request nicht durchläßt.

Irgendwie muss also der Cache vom Server über die Ungültigkeit von test.html im Cache informiert werden. Dazu gibt es jedoch kein standardisiertes Protokoll. Wenn Inhalte vor Ihrer Zeit im Cache ungültig werden, Pech gehabt. Wenn man auf Lösungen Marke Eigenbau verzichten möchte, dann muss man sich etwas einfallen lassen, was vollkommen verträglich ist mit der Standard-Webtechnologie.

Meine Idee dazu:

Der Client fragt test.html beim Server an. Der Server liefert zwar test.html mit einem Reply aus, jedoch enthält test.html nicht den eigentlichen Seiteninhalt, sondern ein JavaScript-Programm. Dieses Programm weist den Client an (sofern JavaScript ausgeführt werden darf), eine Seite test-001.html per Request vom Server anzufragen und den Inhalt von test-001.html zum Inhalt von test.html zu machen (Stichwort DOM, Document Object Model). Dieser Inhaltstausch ist wichtig, damit die Seite auch unter test.html per Bookmark gemerkt werden kann. Der Server gibt also test.html nicht zum Cachen frei, jedoch test-001.html (z.B. für eine Stunde).

Jede neue Anfrage nach test.html belastet zwar den Server damit, eine kurze Antwort auszuliefern, die das JavaScript-Programm enthält, aber die Folgeanfrage nach test-001.html wird vom Cache beantwortet. Dem Server wird damit der wesentliche Traffic vom Leib gehalten.

Ändert sich test.html beim Server, wird eine neue Version test-002.html generiert. Erreicht nun den Server ein neuer HTTP-Request für test.html, liefert er test.html mit einem leicht geänderten JavaScript-Programm aus, das nun nicht mehr zum Nachladen von test-001.html auffordert, sondern nach test-002.html verlangt. Damit wird der Cache "umgangen", der Server kann die aktuelle test-002.html-Seite ausliefern und fürs Caching (z.B. wieder für eine Stunde) freigeben.

Haben Sie die Idee im Kern verstanden?

Ich kann mir gut vorstellen nicht der Erste zu sein, der diese Idee hat. Wenn Sie eine entsprechende Seite finden, ich würde mich freuen davon zu erfahren. Vielleicht haben Sie noch andere interessante Ideen? Sicher gibt es noch andere Varianten zum Caching.

Kommentare:

Andreas hat gesagt…

ASP.NET hat einen fertigen Page-Cache implementiert (gecachet wird in Zusammenarbeit mit dem IIS) und bietet hier die Möglichkeit deklarativ (oder aber auch über eine API) Daten zu cachen bzw. nur dann neu vom Server neu zu holen, wenn sich beispielsweise eine Datenquelle geändert hat.
Über die Direktive [OutputCache Duration="60" VaryByParam="id"] wird z.B. nur die Seite vom Server geholt wenn sich der id-Parameter ändert. Oder ich setze beispielsweise eine CacheDendency auf ein File auf dem Server. In diesem Fall wird die Seite nur neu geholt, wenn sich das File geändert hat. Dies ist nicht eine genaue Umsetzung Ihres Lösungsansatzes aber meiner Meinung nach sehr sinnvoll. Schließlich ändert sich die Seite aufgrund von externen Bedingungen (wie z.B. neue Parameter, Änderung von DB-Inhalten, Files, o.ä.). (Info: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpatterns/html/DesPageCache.asp )

Gruß, Andreas Maier. (SEB1_WS_06/07 Blog: http://andreas-maier.spaces.live.com/ )

Christoph Hautzinger hat gesagt…

Nunja, es muss schon ein sehr großer Traffic auf der Seite herrschen (mehrere hundert Anfragen pro Sekunde) um sich über solch eine Caching-Strategie Gedanken zu machen.

Was bei reinen HTML-Seiten die Performance deutlich erhöht ist evtl. die Verwendung eines puritanischeren Webservers: Ein mit ettlichen Modulen konfigurierter Apache2 verarbeitet einen HTTP-Request natürlich langsamer als ein (sauber) selbstgeschriebener Webserver. Minimalistische Webserver sollte man auch jede Menge im Netz finden.

Eine andere Alternative hierzu bietet ein Reverse Proxy (http://de.wikipedia.org/wiki/Proxy#Reverse-Proxy), den man vor die Webseite schaltet (ich will hier mal mod_proxy und mod_accel für den Apache nennen)

Caching Strategien für Webseiten gibt es wirklich viele, v.a. für Webseiten mit dynamischem Inhalt, was aber hier nicht Kern des Postings war...

Aaron_Mueller hat gesagt…

Ein kleiner Bericht vom Chief Architect von seveload.de (die mit enorm großen Datenmengen zu kämpfen haben), findet man hier: http://blog.thinkphp.de/archives/145-Interview-mit-Thomas-Bachem,-Chief-Architect-sevenload.de.html

Sehr interessant ist auch, das sie keinen Apache verwenden, sondern LigHTTPd (http://www.lighttpd.net/).

dh hat gesagt…

Vielen Dank für die Hinweise. Eine Anmerkung noch: Diese Lösung (und andere, die ich möglicherweise noch posten werde) soll ausschließlich standardisierte Technologien verwenden. Proprietäre Lösungen sind zum Teil sehr elegant, aber auf bestimmte Produkte beschränkt. Ich bin vorrangig an Ideen interessiert, die in diesem Sinne neutral sind.

Cybaer hat gesagt…

Also der Eintrag ist ja schon älter (aber Kompliment: dein Blog schreit beim ersten Besuch danach, immer weiter zurückzugehen :)), aber ich sehe die Notwendigkeit nicht, da es Conditonal Requests gibt.

D.h., der Server sendet eine Kennung mit (Dateidatum und/oder ETag - aber: Vorsicht bei Load Balancing!), und beim CR wird diese Kennung wieder zum Server gesendet. Ist die Resource (ausweislich der Kennung) noch aktuell, sendet der Server nur einen 304er Status. Ist sie es nicht, dann, und nur dann, sendet er die aktuellen Daten.

Bei statischen Resourcen ist das usus. Bei dynamischen Resourcen leider nicht, da sich offenbar nur wenige Entwickler die Mühe machen. Problematisch ist es allerdings auch bei dynamischen Resourcen nicht - dafür die Lasteinsparung umso größer! :-))

Ein Beispiel in PHP habe ich auf meine Coding-Site gelegt: Conditional Requests mit PHP

dh hat gesagt…

@Cybaer: Vollkommen korrekt, das geht auch. Ich hatte seinerzeit nach einer Lösung gesucht, bei der eine Web-Seite dynamisch aus Komponenten zusammengesetzt wird, die ihrerseits Komponenten enthalten können. Dafür ist der skizzierte Ansatz recht praktisch. Conditional Requests sind selbstverständlich eine andere Alternative.

Cybaer hat gesagt…

@dh: Verstehe.
Ich habe das so gelöst, daß ich aus den Komponenten ein eigenes ETag bilde, bzw. wenn sinnvoll, von den Komponenten das jeweilige Dateidatum hole.

Das ETag ist dann eine Prüfumme über die Komponenten, bzw. möglichst sogar nur eine Prüfsumme über die Parameter, die für die Zusammenstellung der Komponenten verantwortlich sind.

Und bei mehreren Dateidaten zählt einfach das aktuellste. ;)

dh hat gesagt…

@Cybaer: Das ist eine originelle Idee! Vielen Dank für den Tipp!