Caching für PHP Systeme

Es gibt ja grundsätzlich zwei Wege wie man in einem PHP-basierten System caching betreiben kann. Ok, es gibt viel mehr, aber zwei Hauptwege sind deutlich erkennbar. Ich hab mal zusammengeschrieben was in dem Zusammenhang interessant ist - gerade weil im Moment ein paar Kollegen unter hoher Serverlast zu leiden haben. Das ganze ist allgemein gehalten, betrachtet aber aus begreiflichen Gründen auch die speziellen Folgen für WordPress.

  • Caching von vorkompilierten PHP Seiten
  • Caching von Seitenoutput

Zu beiden Hauptwegen gibt es eine ganze Reihe von Varianten. Die PHP Seiten selber liegen bei Webservern ja als Source vor - unverarbeitet und nicht irgendwie optimiert für den Ladeprozess. Hat man komplexe PHP Systeme laufen, fällt also bei jedem PHP-File das parsen und kompilieren in den internen Code an. Das kann bei Systemen mit vielen Includs und vielen Klassenbibliotheken durchaus beachtlich sein. An diesem Punkt setzt die erste Hauptrichtung des Cachings an: der erzeugte Zwischencode wird einfach weggespeichert. Entweder in shared Memory (Speicherblöcke die vielen Prozessen eines Systems gemeinsam zur Verfügung stehen) oder auf die Festplatte. Hier gibt es eine Reihe von Lösungen - ich selber setze turck-mmcache ein. Der Grund ist hauptsächlich das es nicht im shared Memory cached, sondern auf der Platte (was aber meines Wissens die anderen ähnlichen Lösungen auch tun) und das es für turck-mmcache ein Debian-Paket gibt. Und das ich bisher relativ wenig negative Erfahrungen damit gemacht habe (jedenfalls bei Debian stable - bei Debian testing sieht das anders aus, da fliegt einem die PHP Anwendung um die Ohren). Da WordPress auf einer grösseren Menge von Bibliotheksmodulen mit recht grossem Sourceinhalt basiert, bringt ein solcher Cache eine ganze Menge um die Grundlast von WordPress zu mindern. Da diese Caches in der Regel vollkommen transparent - ohne sichtbare Auswirkungen ausser der Beschleunigung - sind, kann man einen solchen Cache auch generell aktivieren.

Die Zweite Hauptrichtung für das Caching ist das Zwischenspeichern von Seiteninhalten. Hier kommt als Spezialität hinzu, das Seiten ja oft dynamisch abhängig von Parametern erstellt werden - und daher eine Seite nicht immer den gleichen Output erzeugt. Man denke nur an so banale Sachen wie die Anzeige des Benutzernamens wenn ein Benutzer angemeldet ist (und einen Cookie dafür gespeichert ist). Auch aufgrund von HTTP Basic Authentication (die Anmeldetechnik bei der das Popup-Fensterchen für Benutzer und Passwort kommt) können Seiteninhalte unterschiedlich sein. Und POST-Requests (Formulare die ihre Inhalte nicht über die URL mitschicken) produzieren auch wieder Output der von diesen Daten abhängig ist.

Grundsätzlich muss also ein Output-Cache diese ganzen Eingangsparameter berücksichtigen. Eine gute Strategie ist oft die POST-Ergebnisse garnicht zu cachen - denn dort würden auch Fehlermeldungen etc. auftauchen, die abhängig von externen Quellen (Datenbanken) sind und daher sogar bei identischen Eingabewerten unterschiedliche Ausgaben produzieren könnten. Man kann also eigentlich nur GET-Anfragen (URLS mit Parametern direkt in der URL) sinnvoll cachen. Hierbei muss man aber sowohl die gesendeten Cookies als auch die gesendeten Parameter in der URL berücksichtigen. Sofern das eigene System mit basic-Authentication arbeitet, muss auch das mit einfliessen in den Cache-Begriff.

Ein zweites Problem ist, das Seiten selten rein statisch sind - auch statische Seiten enthalten durchaus Elemente die man eigentlich lieber dynamisch haben möchte. Hier muss man also eine wesentliche Entscheidung treffen: reicht rein statischer Output, oder muss ein Mix kommen? Desweiteren muss man noch die Entscheidung treffen, wie sich Seitenaktualisierungen auswirken sollen - wie merkt der Cache, das etwas geändert wurde?

Ein Ansatz den man verfolgen kann ist ein sogenannter reverse-Proxy. Man packt einfach einen normalen Web-Proxy so vor den Webserver, das jeglicher Zugriff auf den Webserver selber technisch durch diesen Webproxy gezogen wird. Der Proxy steht direkt vor dem Webserver und ist somit für alle Benutzer verbindlich. Da Webproxies das Problem der Benutzerauthentifizierung, der Parameter und der POST/GET-Unterscheidung schon gut beherrschen sollten (in der normalen Anwendungssituation für Proxies sind die Probleme ja die gleichen), ist das eine sehr pragmatische Lösung. Auch die Aktualisierung wird von solchen Proxies meist recht gut gelöst - und im Notfall kann der Benutzer durch einen erzwungenen Reload den Proxy ja überreden die Inhalte neu zu ziehen. Leider geht diese Lösung nur dann, wenn man den Server selber unter Kontrolle hat - und der Proxy zieht noch weitere Resourcen, weshalb unter Umständen dafür kein Platz auf dem Server ist. Auch ist es stark von der Anwendung abhängig wie gut sie mit Proxies klar kommt - wobei Probleme zwischen Proxy und Anwendung auch bei normalen Benutzern aufstossen würden und deshalb sowieso gelöst werden müssen.

Der zweite Ansatz ist die Software selber - letzten Endes kann die Software ja genau wissen wann Inhalte neu erstellt werden und was für das Caching berücksichtig werden muss. Hier gibt es wieder zwei Richtungen der Implementierung. MoveableType, PyDS, Radio Userland, Frontier - diese erzeugen alle statische HTML Seiten und haben deshalb das Problem mit der Serverlast bei Seitenzugriffen nicht. Der Nachteil liegt auf der Hand: Datenänderungen erzwingen ein Neuerstellen der Seiten, was bei grossen Sites nervig werden kann (und dazu geführt hat das ich von PyDS auf WordPress gewechselt habe).

Die zweite Richtung ist das Caching aus der dynamischen Anwendung selber: beim ersten Zugriff wird der Output unter einem Cache-Key gespeichert. Beim nächsten Zugriff auf den Cache-Key wird einfach geguckt ob der Output schon vorliegt, wenn ja wird er ausgeliefert. Der Cache-Key setzt sich aus den GET-Parametern und den Cookies zusammen. Wenn Datenbankinhalte geändert werden, werden die entsprechenden Einträge im Cache gelöscht und damit die Seiten beim nächsten Zugriff neu erstellt.

WordPress selber hat mit Staticize ein sehr praktisches Plugin für diesen Einsatzzweck. In der aktuellen Beta ist es schon im Standardumfang mit drin. Dieses Plugin erzeugt wie oben beschrieben für Seiten einen Cache-Eintrag. Und berücksichtigt dabei die Parameter und Cookies - basic Authentication wird bei WordPress ja nicht benutzt. Der Clou ist aber, das Staticize die Seiten als PHP speichert. Die Cache-Seiten sind also selber wieder dynamisch. Diese Dynamik kann jetzt dazu benutzt werden um Teile der Seite mit speziellen Kommentaren zu markieren - wodurch für diese Teile der Seite dann wieder dynamische Funktionsaufrufe eingesetzt werden. Der Vorteil liegt auf der Hand: wärend die grossen Aufwände für die Seitenerstellung wie das Zuladen der diversen Bibliotheksmodule und das Lesen der Datenbank komplett erledigt sind können einzelne Teilbereiche der Site weiterhin dynamisch bleiben. Natürlich müssen die Funktionen dafür dann so aufgebaut sein das sie nicht das ganze Bibliothekswerk von WordPress brauchen - aber zum Beispiel dynamische Counter oder Anzeigen der gerade aktiven Benutzer oder ähnliche Spielereien lassen sich auch in den Cacheseiten somit weiter dynamisch halten. Matt Mullenweg benutzt es zum Beispiel um auch auf Seiten aus dem Cache ein Zufallsbild aus seiner Bibliothek anzuzeigen. Staticize löscht bei Erstellung oder Änderung eines Beitrags einfach den ganzen Cache - sehr primitiv und bei vielen Files im Cache kann das dann schon mal etwas länger dauern, aber dafür sehr wirksam und pragmatisch.

Welche Caches setzt man sinvollerweise jetzt wie ein? Ich würde bei komplexeren Systemen immer gucken ob ich nicht einen PHP Codecache einsetzen kann - also turck mMCache oder Zend Optimizer oder phpAccelerator oder was es sonst noch so gibt.

Den Anwendungscache selber würde ich persönlich nur dann aktivieren wenn er auch wirklich nötig wird durch die Last - bei WordPress kann man ja ein Plugin vorhalten und nur bei Bedarf aktivieren. Denn Caches mit statischer Seitenerstellung haben nunmal so ihre Probleme - Layoutänderungen sind erst nach Löschung des Cache aktiv etc.

Wenn man einen Reverse-Proxy einsetzen kann und die Resourcen auf der Maschine dafür auch ausreichen ist er sicherlich immer zu empfehlen. Alleine schon weil man so selber die Probleme mitbekommt, die in der eigenen Anwendung eventuell bezüglich Proxies drin sind - und die jedem Benutzer hinter einem Webproxy auch Ärger machen würden. Gerade wenn man zum Beispiel Zope benutzt gibt es sehr gute Möglichkeiten in Zope die Kommunikation mit dem Reverse Proxy zu verbessern - dazu ist ein Cache Manager in Zope verfügbar. Auch andere Systeme bieten dafür gute Grundlagen - aber im Endeffekt sollte jedes System das saubere ETag und Last-modified Header produziert und korrekt conditional GET (bedingte Zugriffe die mitschicken welche Version lokal schon vorliegt und dann nur aktualisierte Inhalte sehen wollen) behandeln geeignet sein.

tags: CMS, Sysadmin, Texte, Wordpress

Elvis June 11, 2007, 6:09 p.m.

Schon 2 Jahre alt, der Artikel und immernoch nützlich, besten Dank.

halbesbit Nov. 13, 2008, 7:44 p.m.

Wie schon alt so wenige kommentare naja jetzt ist wenigszebnz ein 2ter da. 13. Jan. 2005 bis heute schon über 3Jahre :) und ich denk das meiste ist immernoch zu gebrauchen?

aioon Oct. 10, 2009, 8:24 p.m.

Alt ist das Verfassungsdatum, aktuell für mich die Lösung eines meiner Probleme!

Vielen Dank, dafür das es diesen Beitrag gibt ;)

Gruß

Alex

eFrane March 25, 2010, 11:19 a.m.

So alt der Beitrag auch ist, er hat tatsächlich nichts an seiner Aktualität verloren. Schöne Übersicht!

Benny Aug. 17, 2010, 5:46 a.m.

Vielen Dank - auch wenn der Beitrag schon 5 Jahre alt ist, hat mir grad ein kleines Stückchen weiter geholfen.