Facebook
Twitter
Google+
Kommentare
24

Programmiert immer so, als ob es keinen Cache gibt

Wenn man schon lange PHP programmiert, hat man schon an einigen Projekte mitgearbeitet. Ich glaube, ich mache das jetzt fast seit 10 Jahren mehr oder weniger professionell, doch manchmal erkennt man Dinge doch erst sehr spät. In diesem Fall geht es um das Cachen. Gar nicht mal um eine spezielle Technik, wie memcache oder localcache. Ich war oft der Meinung, dass man mit Caching fast jede Webseite performant bekommen kann und dazu stehe ich heute auch noch.

Was ich aber bemerkt habe, ist ein Phänomen, das mir, wenn ich so darüber nachdenke, schon öfters über den Weg gelaufen ist, bei dem Entwickler sich so stark vom Caching abhängig machen, dass die Webseite nicht ohne diesen funktionieren kann. Dies kann aber meiner Meinung nach zu einem wirklichen Problem werden.

Ich kenne Webseiten, die ohne Caching bis zu einer Minute brauchen, um ihre Startseite zu „berechnen“. Natürlich kann man jetzt einen HTTP Cache davorschalten, so kann man sicher sein, dass z.B. ein squid die bereits gerenderte Seite im Speicher hält und sie nicht mehr berechnet werden müssen. Das geht natürlich nur so lange, wie sich die Seite nicht verändert hat. Prinzipiell ist ein squid eine feine Sache und jedem zu empfehlen, sich aber schon bei der Programmierung darauf zu verlassen, dass dieser die Last wegnimmt ist meiner Meinung nach falsch.

Wenn ich lokal arbeite, dann unterliegen meine Sourcen permaneten Änderungen, ein Cache wäre also mehr als lästig, denn ich müsste ihn für die Stellen immer ausschalten, die ich gerade am bearbeiten bin. Entwickle ich an einem Projekt, bei dem eine Seite bis zu einer Minute zum rendern braucht, bekomme ich ganz schnell einen Nervenzusammenbruch, auch wenn die Seite dann im Produktivumfeld schnell wie die Hölle ist.

Oft sind es auch Probleme in der Architektur, die Caches erst notwendig machen. Oder glaub ihr, dass ein Xing Personen, die ich kennen könnte live berechnet? Ich tippe mal ganz stark drauf, dass dies in einem separaten Thread geschieht und den eigentlichen Server damit nicht ausbremst. Zumindest die Vorberechnungen. So etwas muss danach ganz klar gecached werden, aber als Entwickler betrachte ich dieses Teil danach als Blackbox und es beeinträchtigt nicht die eigentliche Seitenberechnung.

Zusätzlich erschwert die Verwendung von Caching immer das Debugging. Ich weiß nicht, welche Version meines Sktiptes denn jetzt wirklich gecached wurde. Ist es die neue Seite und mein Bug wurde nicht behoben oder ist es einfach die alte und es dauert nicht ein wenig, bis die Änderungen live sind.  Natürlich kann ich meinen Cache invalidieren und somit zwingen die Seite neu zu berechnen. Dies ist aber immer ein zusätzlicher Schritt.

Ein wichtiger Punkt bei der Sache ist noch das „aufwärmen“ des Caches. Mit aufwärmen meine ich, dass der Cache anfänglich keine Anfragen gespeichert hat und diese erst nach und nach füllt. Die ersten 100 Anfragen auf 100 Unterseiten würden also alle 1 Minute dauern. Dies würde bedeuten, dass mein Server in den ersten Minuten unter einer unglaublichen Last steht, die er vielleicht nicht ab kann. Aufwendige Aufwärm-Skripte müsste ich mir in diesem Fall einfallen lassen. Das kann ja auch nicht wirklich effizient sein.

Ziel muss es also sein, Teile der Software, die beim rendern einer Seite verwendet werden so performant hinzubekommen, dass diese auch ohne Cache in passabler Zeit beim Browser ankommt. Sollte eure Seite also ohne Cache kaum funktionieren, solltet ihr euch mal Gedanken darüber machen, ob ich nicht bestimmte Anfragen ausserhalb und asynchron berechnet werden sollten. Ich will damit nicht sagen, dass ihr keine Caches verwenden sollt, denn dieser Ansatz ist ein ganz wichtiger. Nur sollte man sich überlegen, ob man mit dem Caching nicht architektonische Fehler vertuschen will und man es dem Entwickler nicht unnötig kompliziert dadurch macht.

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

24 Comments

  1. Natürlich sollte man clever genug sein, das Caching mit einer Konstante ein- und abschaltbar zu machen. Und natürlich dann als nächstes auch clever genug zu sein, das Caching beim Debuggen abzuschalten (was ich ab und an vergesse) 🙂 Dies alles setzt wiederum voraus, das der Fehler definitiv nicht im Cache liegt, deshalb nutze ich seit Jahr und Tag den gleichen von dem ich absolut sicher bin das er funktioniert: Cache_Lite aus der Pear …

    Ansonsten stimme ich dir voll und ganz zu, ich benutze Caching in der Regel nur bei SOAP, wenn Curl zum Einsatz kommt.

    Reply
  2. Wenn eine Seite mehr als 2 Sekunden ohne Caching zum Antworten (d.h. bis das Skript ausgeführt wurde) braucht hat man aber schon ne Menge falsch gemacht. Nur innrhalb von PHP bekommt man das eigentlich fast nicht hin, es sei denn man geht seine Algorithmen völlig planlos an. Meistens hakts da bei SQL-Queries oder anderen externen Verbindungen.

    Ich cache häufig getter-Methoden um den Datenbankserver zu entlasten. Habe ich z.B. eine Methode getContract($contract_id, $cache = true), rufe ich die Methode eigentlich immer im Skript mit getContract($contract_id) auf, nach Updates auf die Tabelle bzw. den Datensatz rufe ich jedoch direkt mit $cache = false auf sodass der Cache sich automatisch aktualisiert.

    Das Caching kann man dann in einer Superglobalen oder in einer Cache-Registry machen, super einfach zu implementieren und bringt in vielen Anwendungen echte Vorteile. Ein ähnliches Verfahren wurde aber auch letztens glaube ich hier vorgestellt.

    Was zudem unbedingt vermieden werden sollte: SQL-Queries in einer Schleife, ganz böse, die Laufzeit zieht sich brutal in die Länge! in den aller aller meisten Fällen lässt sich sowas durch geschickte/richtige Verwendung von SQL vermeiden.

    Reply
  3. Gerade auf Startseiten sind 2 Sekunden gar nichts, da auf großen Sites oft 50+ Queries ausgeführt werden, da (logischerweise) auf Klassen zurückgegriffen wird, und nicht auf optimierte Queries. Da Startseiten dummerweise die Eigenschaft haben von allem ein bisschen anzuzeigen stellt sich wohl eher die Frage wie man Startseiten optimiert, eigentlich müsste man für die eigene Queries schreiben und nicht auf Klassen mit spezialisierten Aufgaben zurückgreifen. Macht nur keiner 🙂

    Reply
  4. Ich glaube man muss beim Thema Seitenaufbau 2 Dinge betrachten: die Ausführungszeit von PHP und die Anzahl an Requests, die ein Browser machen muss für die einzelne Seite. Es kann ja sein, dass PHP vielleicht 200ms braucht, aber die Seite trotzdem erst nach 4 Sekunden vollständig geladen wurde… Das habe ich auch schon einige Male gesehen. Gerade bei Ladezeiten kann man im Bereich HTML/JS/CSS und Bildern auch ziemlich viel falsch machen!

    Reply
  5. @Guido: mit 2 Sekunden könnte ich ja noch leben. Ab 30 wird’s dann echt schwer, weil es den Workflow zerstört. 50 Queries sollten doch aber eigentlich, wenn es einfache sind, auch nicht lange dauern oder?

    Reply
  6. Hallo!
    Interessant was du da schreibst, Guido. Gerade heute morgen habe ich eine Startseite optimiert. 😉 Auf die unzähligen Klassen, die ihre IDs alle miteinander vermischen, um eine einzigartige Auflistung von Datensätzen hinzubekommen, habe ich verzichtet und stattdessen eine prägnante SQL-Abfrage geschrieben. Die Geschwindigkeitsverbesserung war sehr zu spüren, da Skript läuft mindestens 300% schneller, die Abfragen sind auf ein Minimum reduziert.

    Nun für den Wartungsfall ist das sicherlich nicht optimal. Aber ich gehe mal davon aus, dass sich die _getIds()-Methoden der Klassen nicht mehr verändern werden.

    Reply
  7. Hallo Nils,
    vorweg auch großes Lob von mir für deinen informativen blog, den ich seit längerem lese.
    Doch ich will mich kurz halten. Du bist heute auf das caching eingegangen, was ich sehr interessant finde, doch leider erwähnst du hier Techniken wie squid, die wahrscheinlich nicht jedem bekannt sind (einschließlich mir).
    Das führt mich auch gleich zu einer kleinen Kritik, oder auch viel mehr einem „Wunsch“. Geht doch etwas mehr auf die Technikthemen ein. Bin mir sehr sicher, dass diese auf großes Interesse stoßen.

    Gruß
    Philip

    Reply
  8. @ Dennis Becker: Front End Optimierung ist natürlich ein anderes Thema, was aber immer wichtiger wird (könnte man eigentlich auch mal nen blog-eintrag zu machen). Dazu gibts ein gutes Buch was ich aber nur teils im Buchladen gelesen habe 😉
    Steve Souders (Chief Performance Yahoo!), „High Performance Websites“ (oder gleich die englische Fassung)

    Reply
  9. @Guido:
    Wenn das nicht-optimieren zu so einer langen Ladezeit führt würde ich meine Wartbarkeit aber zumindest teilweise opfern und speziellere Funktionen bauen die dann schneller arbeiten.

    Reply
  10. 50 Queries sollten niemals mehr als 5 Sekunden brauchen (insbesondere nicht bei der Entwicklung, wo Datenbankserver und Applikationsserver noch auf dem gleichen Rechner laufen), da würde ich mir sofort die Datenbank-Struktur und deren Indizes anschauen. Ausnahme: Ihr arbeitet bei Facebook oder Google oder so.

    Ansonsten kann man dem Beitrag nur beipflichten. Jede Applikation sollte ohne Caching genauso funktionieren (eben nur langsamer) und man sollte niemals mit Cache entwickeln. Auch ohne Browser-Cache insbesondere bei CSS und JavaScript-Problemen!

    Viele Grüße
    Ulf

    Reply
  11. @ Ulf: Naja, also ohne Browser-Cache zu entwickeln find ich übertrieben. Dafür gibt es doch [STRG] + [F5]. Z.B. bei Seiten bei denen ein und dasselbe Icon 50 bis 100 mal vorkommt ist das Laden ohne Browser-Cache eine Qual. Das bremst mich bei der Entwicklung aus 😉
    Und um Ladezeiten zu überprüfen kann ich ja auch [STRG]+[F5] machen (und mir die Analyse in Firebug angucken).

    Reply
  12. Beim Lesen der Überschrift habe ich erst gedacht, du wolltest auf etwas anderes hinaus, das meiner Meinung nach wichtiger als die angesprochenen Punkte sind: Man muss unbedingt vermeiden, übergreifende Anforderungen wie das Caching über alle Schichten seiner Anwendung zu verstreuen. Der Kommentar Nr. 2 von Peter Hansen schildert so ein potentieller Worst-Case: Schlimmstenfalls werden an 1001 Stelle im Code Annahmen über das Caching-Verfahren getroffen, die ein globales Ändern oder Ausschalten verunmöglichen, „Geschäftslogik“ und Caching-Aspekt sind komplett verquirlt.

    Was deine Thesen betrifft, stimme ich mit Einschränkungen zu. Eine Anwendung sollte durchaus (ohne Last) performant laufen, ohne dass z. B. Datenbank-Abfragen und Views zwischengespeichert werden. Aaaber: Es gibt imho nicht-inhaltsbezogene Tätigkeiten, bei denen ein obligatorisches Caching mit entsprechender Anlaufverzögerung keine Sünde sein muss. Das betrifft etwa das „Kompilieren“ von anderen Templatesprachen nach PHP oder den Aufbau eines Klassenindex für den Autoloader. (Ich bin von beidem kein Fan, aber das ist eine Designentscheidung, die jeder selbst treffen muss.) Solche Vorbereitungen sind u. U. nicht nur notwendig für ein vernünftiges Laufzeitverhalten, sondern auch vorhersehbar und laufen nicht etwa bei plötzlichen, unerwarteten Requests auf viele verschiedene Inhalte oder mit neuen Parametern aus dem Ruder.

    Reply
  13. @rbq: Caching überall zu verstreuen halte ich auch für ein Problem. Wobei es natürlich Ausnahmen gibt. Ein Cache in der DB Schicht und einer in der HTPP Schicht kann natürlich sehr sinnvoll sein.

    Kommentare editieren wird es erst geben, wenn WordPress das nativ unterstützt. Sorry!

    Reply
  14. @rbq das Geschäftslogik und Caching müssen dabei nicht unbedingt vermischt werden. Man könnte die Klasse die die Geschäftslogik enthält auch einfach extenden, die Methoden überschreiben und dann mit parent:: (oder halt einer Instanz vom ursprünglichen Objekt) die ursprünglichen Methoden aufrufen. So kann eine Instanz der Klasse mit und eine ohne Caching existieren. Möchte ich kein Caching nehme ich halt keine Instanz von einer Klasse mit Caching.

    Reply
  15. @Christopher
    Immer habe ich den Browser-Cache auch nicht ausgeschaltet, weil ich ja nicht immer CSS und JavaScript entwickele. Es gibt aber imo nicht Schlimmeres als 10 Minuten in CSS bzw. JavaScript herumzudebuggen um dann festzustellen, dass der Browser immer die Cache-Version nutzt. [STRG] + [F5] hilft da leider auch nicht immer.

    @Peter
    Mit deinem Beispiel vermischst du gerade Caching mit Geschäftslogik! Der Client, der die Methode aufruft, muss explizit entscheiden ob er den Cache nutzt. Dies sollte aber vom Client komplett unabhängig bzw. über eine Konfiguration. In deinem Beispiel müsste ich für den Einsatz das Caches jeden Methodenaufrud mit den optionalen Parameter erweitern. Wenn das System etwas komplexer ist, bin ich damit schnell einen Tag nur mit Anschalten des Caches beschäftigt.

    Reply
  16. @Ulf
    Mit Sicherheit lässt sich das besser machen, aber auch mit ein paar kleinen Verbesserungen ist das mit meinem Ansatz kein größeres Problem…:
    Man kann das Objekt immer über Factory-Singleton-Kombination bekommen, die Factory könnte entscheiden ob gerade die Klassse mit Caching oder die ohne benutzt werden soll (bspw. anhand einer Konstanten).
    Die Klasse mit Caching extendet die ohne und überschreibt die Methoden die Caching bekommen sollen, dort wird dann entsprechend Code eingebaut der das Ergebnis der Anfrage in den Speicher legt. Wenn schon was im Speicher ist kommt das Ergebnis daher. Wenn nicht wird die parent-Methode aufgerufen (oder wenn das Caching über den Parameter deaktiviert wurde).
    Bei den Methoden die Updates vornehmen wird dann entsprechend in der Klasse mit Caching die Methode überschreiben. In der überschriebenen Version wird dann einmal die alte Version der Update-Methode aufgerufen und dann die passende getter-Methode mit $cache = false.

    Das es bessere Lösungen dafür geben mag (z.B. Zend Cache) bezweifel ich nicht, nur ist „meine Lösung“ jetzt auch nicht so unflexibel und muss nicht zwingend Geschäftslogik und Caching vermischen – oder habe ich da jetzt noch was übersehen?

    Reply
  17. So wie du es beschrieben hast, ist es schon besser, wirklich gefallen tut es mir aber noch nicht.

    Du hast bei deiner Lösung das Problem, dass du Geschäftslogik doppelst. Wenn du beispielweise deine ResultSets von Queries erweitern möchtest, musst du dies jedes Mal an zwei Stellen tun. Die Stelle die den Cache abfragt und die Stelle die die Persistenzschicht direkt anfragt. Das kann bei größeren Systemen schnell chaotisch werden, ganz davon abgesehen dass man damit gegen das DRY-Prinzip verstößt.

    Und deine Factory-Singleton-Kombination ist nichts anderes als eine globale Variable. Pfui! 😉

    Viele Grüße
    Ulf

    Reply
  18. > Und deine Factory-Singleton-Kombination ist nichts anderes als eine globale Variable. Pfui!

    Ne, das heißt jetzt Dependency Injection Container und ist cool. =)

    Was mir gerade mal jemand erklären muss ist allerdings, wann und warum man überhaupt auf die abgedrehte Idee kommen sollte, ausgerechnet SQL-Queries mit PHP zu cachen.

    Reply
  19. Da ich annehme, dass ca. 90% aller von den Lesern geschriebenen Anwendungen CRUD sind, gibt es auch einen anderen Ansatz für die Programmierung solcher Sachen:
    lasst das R weg!

    R steht für Read, was analog auch für Reporting stehen kann.
    CUD müssen möglichst „realtime“ abgehandelt werden, aber gilt das für R auch?

    Kleine Erläuterung, um es besser zu verstehen:
    Im dem Augenblick, in dem Daten gespeichert werden, sind sie auch schon veraltet. Jede Routine, die Daten verändert, muss sowieso zu genau diesem Zetpunkt seine Prüfungen fahren, denn in der Zwischenzeit können die Daten geändert worden sein.
    Wie lang dieser Zeitpunkt ist, das ist die Vertragssache mit dem Kunden über SLAs und bestimmt den Preis 😉

    Aber das R(eporting) kann eine völlig eigenständige Anwendung sein bzw. eine separate Domain. Und da die Daten ja sowieso veraltet sind, spielt es hierbei nur eine Rolle, wie alt Daten sein sollen, die angezeugt werden.
    UNd damit kann dieser „R-Teil“ bis zum Gehtnichtmehr und St.-Nimmerleins-Tag optimiert werden, mit Caching, OLAP, etc. ect.

    Um die Fanatiker der concurrent- oder constistency-Paranoia zu beruhigen: auch im realen Leben kann man nicht erwarten, dass eine Anweisung sofort und reibungslos ausgeführt wird. Auch das ist Teile einer Abmachung.

    Cheers!

    Reply
  20. Prinzipiell gebe ich dir und den meisten Kommentatoren recht, allerdings gibt es Ausnahmen.

    „50 Queries sollten niemals mehr als 5 Sekunden brauchen“ 80% der Projekte, welche Cachen, das Caching nicht bräuchten, wenn man mal ordentlich an der Performance-Schraube drehen würde.

    Reply
  21. Hi,

    ein wirklich ausgezeichneter Artikel, bestätigt mich in meiner Programmierung.
    Ich habe mir bisher immer Gedanken ums Caching gemacht, aber trenne beide „Systeme“ voneinander bzw. verschachtel es wenn dann so, dass ein Flag das gesamte integrierte Caching aktiviert.

    Ich hab aktuell ein Projekt am laufen auf http://moviestreamsonline.freeiz.com was noch mit keinem Caching arbeitet und ich noch keines vorimplementiert habe.
    Würde mich über Vorschläge freuen!

    Besten Dank
    Grüße

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen