Sie sind hier : sebastian1012.bplaced.net/ homepage-neu / kreuz-und-quer / tutorials-info-neuigkeiten-php / ausgaben-in-cache-speichern.php

Ausgaben in Cache speichern

Viele auf PHP basierende Projekte generieren mit einer PHP-Datei viele Ausgabe-Seiten. Sei es in einem Onlineshop, in denen auf der Detailseite die Daten aus einer Datenbank geholt und vom Script an die richtige Stelle platziert werden oder ein Blog, bei dem im Prinzip genau das gleiche passiert: Eine Datei wird mit einem eindeutigen Parameter aufgerufen und über diesen Parameter können aus der Datenbank die nötigen Inhalte geladen und präsentiert werden. Die fertig zusammengebaute Seite wird dann zurück an den Client geschickt.
Wenn man nun Seiten hat, die sich recht selten ändern (beispielsweise die Detailseite eines bestimmten Artikels in einem Shop), wäre es ja nun möglich die einmal erzeugte Ausgabe als HTML-Datei zu speichern, damit beim nächsten Aufruf die Seite nicht wieder umständlich zusammengebaut werden muss, sondern einfach nur die HTML-Datei geladen werden muss. Und genau das nennt ist Caching.

Anwendungsgebiete sind vor allem Detailseiten, die relativ selten geändert werden und über einen Permalink erreichbar sind. Denkbar schlecht geeignet ist Caching für Seiten, die sich recht häufig ändern, wie beispielsweise die neuesten Artikel einer Website, denn dort soll ja – sobald ein neuer Artikel eingestellt wird – dieser auch auftauchen.

Erstmal haben wir unsere PHP-Seite, die irgendwelche Ausgaben ausspuckt:

for($i=0;$i<1000;$i++) {   echo $i." Hallo Welt, ich bin ein PHP-String, der auf dem Bildschirm ausgegeben wird.<br />"; }

Dieses Script ist nicht sehr sinnvoll, das braucht sie aber auch gar nicht, um das Konzept zu erklären. Es wird 1000 mal der Satz ausgegeben. Dieser Satz steht stellvertretend für die vielen Ausgaben auf einer „richtigen“ Website.

Um die gesamten Ausgaben einer PHP-Anwendung zu erhalten, müssen wir die Ausgabe puffern. Das geschieht durch folgende Erweiterung:

ob_start(); for($i=0;$i<1000;$i++) {   echo $i." Hallo Welt, ich bin ein PHP-String, der auf dem Bildschirm ausgegeben wird.<br />"; }   $content = ob_get_clean(); echo $content;

ob_start weißt PHP an, alle Ausgaben in den internen Puffer zu schreiben und ob_get_clean liefert all diese Ausgaben in einem String zurück und leert den Puffer. Diesen String können wir dann ausgeben. Das ist allerdings erstmal das Puffern und noch nicht das Cachen (sorry für diese Denglisch-Wörter 🙂 ).
Für das Caching muss die Ausgabe nun in eine statische Datei gespeichert werden. Dazu erweitern wir das Script:

ob_start(); for($i=0;$i<1000;$i++) {   echo $i." Hallo Welt, ich bin ein PHP-String, der auf dem Bildschirm ausgegeben wird.<br />"; }   $content = ob_get_clean(); $fh = fopen("cache".$_SERVER['PHP_SELF'].".html","w"); fputs($fh, $content); fclose($fh); echo $content;

Wir öffnen ein File-Handle auf die gewünschte Datei. Diese wird erzeugt, falls sie nicht existiert und ansonsten überschrieben (Flag „w“ in der fopen-Funktion). Sie bekommt den Dateinamen der aktuellen PHP-Datei – hier kann man aber hineinschreiben, was man will und liegt im (vorher per Hand erzeugten) Verzeichnis cache. Das Wichtigste bei der Vergabe des Dateinamens ist, dass es keine Überschneidungen mit anderen Dateien gibt. Geeignet wäre beispielsweise auch – wenn die PHP-Datei mit einer ID aufgerufen wird – den Dateinamen „cache_“.$_GET[‚id‘].“.html“ zu nehmen, da jede ID der einzelnen Artikel ja eindeutig sein sollte – Stichwort Primärschlüssel in der Datenbank.

Das bloße Speichern hilft natürlich noch nix, denn das würde eher mehr Laufzeit verbrauchen als vorher (und dazu noch zusätzlichen Speicherplatz belegen). Beim nächsten Aufruf sollte nicht das PHP-Script abgearbeitet werden sondern die gespeicherte Datei einfach geladen und ausgegeben werden. Damit aber Aktualisierungen, die ab und zu ja doch vorkommen, trotzdem einen Effekt haben, könnte man entweder den Cache in regelmäßigen Abständen löschen (bei uns alle Dateien im Verzeichnis cache). Oder aber man prüft, wann die HTML-Datei das letzte mal bearbeitet wurde und falls das länger her ist als ein bestimmter Zeitabstand, lässt man doch PHP arbeiten, damit der Stand in der HTML-Datei aktualisiert wird.

if(file_exists("cache".$_SERVER['PHP_SELF'].".html") && time()-filemtime("cache".$_SERVER['PHP_SELF'].".html")<24*3600) {   echo file_get_contents("cache".$_SERVER['PHP_SELF'].".html");   exit(); } ob_start(); for($i=0;$i<1000;$i++) {   echo $i." Hallo Welt, ich bin ein PHP-String, der auf dem Bildschirm ausgegeben wird.<br />"; }   $content = ob_get_clean(); $fh = fopen("cache".$_SERVER['PHP_SELF'].".html","w"); fputs($fh, $content); fclose($fh); echo $content;

Nun prüft PHP am Anfang, ob eine Datei im Cache existiert, die auf die Anforderung passt. Falls es eine findet, prüft es zusätzlich, ob die Änderungszeit länger als 1 Tag zurückliegt. Sind beide Bedingungen erfüllt, wird die Datei möglichst schnell eingelesen und ausgegeben. Anschließend wird die weitere Verarbeitung des Scripts mit exit() verhindert, denn wir haben ja alles, was wir wollten.

Im Vergleich mit der normalen Ausgabe verhalten sich die Laufzeiten folgendermaßen:

Datei Gesamtlaufzeit durchschnittliche Laufzeit pro Durchlauf Verhältnis zur schnellsten Variante
result_ausgabe_
cache.php
31.94712 s 3.109 ms 100 %
result_ausgabe_
normal.php
38.875900 s 3.888 ms 125 % (+ 25%)

Die gecachte Version ist also in diesem Fall um 25 % schneller. Das Einlesen der HTML-Datei kostet zwar eine gewisse Zeit, allerdings kostet das Durcharbeiten der Schleife wesentlich mehr Zeit. Das ist auch in der Praxis so (teilweise noch größerer Abstand), denn dort müsste erst eine (wahrscheinlicher sind mehrere) Datenbankabfrage(n) gemacht und ausgewertet werden, die recht viel Zeit verschlingt. Und außerdem können umfangreiche Berechnungen oder rekursive Abfragen damit entschärft werden, da sie nun nur noch 1 mal pro Tag ausgeführt werden muss (1 Besucher muss sich also opfern, und die etwas längere Laufzeit in Kauf nehmen).
Ein weiterer Vorteil ist auch, dass der Server wesentlich entlastet wird, da er weniger Berechnungen durchführen muss und dadurch auch weniger Arbeitsspeicher für dieses Script verwenden muss.

Gibt es auch einen Nachteil von Caching?
Ja den gibt es leider auch: Während eine PHP-Datei, die nur die Daten heranholt, relativ wenig Webspace (= Festplattenspeicher) benötigt, muss mit Cashing zu jedem Ausgabeartikel eine HTML-Datei gespeichert werden. Das kann recht schnell sehr viel Platz einnehmen. Empfehlenswert ist ein eigener Server mit einer großen Festplatte. Auf Shared Hosting-Paketen ist Caching nicht zu empfehlen.

Trotzdem ist Caching eine tolle Möglichkeit die Performance von Anwendungen zu erhöhen.
Die Quellcodes für das Beispiel und die Benchmarks gibts wie immer zum Download.

UPDATE: Mir ist gerade noch eine Lösung für das Problem des hohen SPeicherplatzbedarfs eingefallen. Leider kostet das wieder recht viel der durch das Caching gewonnenen Performance: Man könnte die HTML-Ausgabedateien per gzip packen und somit per PHP recht leicht verarbeiten. Allerdings beträgt der Geschwindigkeitsvorteil vor der „normalen“ Ausgabe dann nur noch 2 % – dafür lohnt sich der Aufwand glaube nicht. Also entweder große Platte und richtiges Caching oder kleine Platte (bzw wenig Webspace) und kein Caching.