• Caching: stale-if-error

    von am 9. Dezember 2009

    Ich bin wieder da! Ok, die meisten werden sich jetzt wohl fragen “der Nils war weg?”. Aber ja, die letzten zwei Wochen haben meine lieben Co-Autoren mir unter die Arme gegriffen und zehn sehr interessante Artikel verfasst. Deswegen gilt natürlich an alle, die den Blog in der Zeit unterstützt haben ein besonderer Dank. Ohne euch hätte ich das “jeder-Tag-ein-Artikel” Konzept nicht durchhalten können.

    Jetzt aber wieder zurück in die Realität und was zum Thema Caching geschrieben. Wie ihr wisst, habe ich ja zusammen mit Mike in der letzten Ausgabe des PHP Magazin ein wenig zu diesem Thema erzählt. Natürlich konnten wir nicht alle Aspekte dieses doch recht komplexen Themas aufführen. Aus diesem Grund will ich heute mal über eine Technik erzählen, die stale-if-error heißt.

    Prinzipiell ist es ganz einfach. Ich habe einen Cache, den ich nach einem Ergebis abfrage. Falls das Ergebnis nicht in meinem Zwischenspeicher vorhanden ist, so sagt mir mein Cache dies und ich muss mich halt neu drum kümmern. So weit so gut. Falls bei meiner neuen Berechnung nun ein Fehler auftritt, weil vielleicht nicht alle benötigten Daten zu diesem Zeitpunkt vorhanden sind, so müsste ich jetzt einen Fehler ausgeben. Die Idee hinter stale-if-error ist die, dass man in einem solchen Fall keinen Error anzeigt, sondern einfach die zuletzt gültigen Daten ausliefert, auch wenn sie per Definition nicht mehr gültig sind, also das expire Date erreicht wurde. Ein SQUID zum Beispiel kann so konfiguriert werden, dass es sich so verhält. Sollte man auch so verwenden, wenn man “normale” Webanwendungen baut.

    Bei Web-Caches gibt es dieses Verhalten also. So viel wir ich weiß übernehmen viele andere Caches dieses Prinzip leider nicht. Falls ich eine “lifetime” für meine gespeicherten Werte angebe, so habe ich oft nicht die Möglichkeit an meine Daten dran  zu kommen, falls die Lebenszeit abgelaufen ist. Eigentlich schade, denn in einigen Fällen möchte ich trotzdem dran kommen, denn es ist das beste, was ich zu diesem Zeitpunkt habe. Das ganze könnte so oder so ähnlich aussehen:

    $cache = new PHM_BestCacheEver( );
    if ( $cache->has( 'name' ) ) {
      $name = $cache->get( 'name' );
    }else{
      try {
        $name = calculateName( );
        $cache->set( 'name', '$name', '1 day' );
      }catch( CalculationException $e ){
        $name = $cache->getLastValid( 'name' );
      }
    }
    

    Ich weiß nicht, ob das jetzt für alle nachvollziehbar ist, aber ich denke schon. Wir müssen also nur eine Möglichkeit bauen, dass wir trotz der abgelaufenen Lebenszeit noch an die Daten kommen.

    Mein Einsatzgebiet für diese Funktionalität könnt ihr euch vielleicht schon denken. Ich verwendet den Zend Cache, um meine Twitter Follower anzuzeigen. Das Problem dabei ist, dass die API nur 100 Request pro Stunde abkann, danach liefer die Schnittstelle halt keine Daten zurück. Was darin resulultiert, dass keine Follower mehr angezeigt werden. Doofe Sache.

    Noch kurz als Nachtrag, da ich es gerade gefunden habe, einen passenden Ausschnitt auf einem RFC zu dem Thema:

       HTTP [RFC2616] requires that caches "respond to a request with the
       most up-to-date response held... that is appropriate to the request,"
       although "in carefully controlled circumstances" a stale response is
       allowed to be returned.
    Nils Langner

    Auch wenn Ihr es mir nicht glauben werdet, aber ich habe nichts gegen PHP. Ich rege mich einfach nur gerne auf. Ok so schlimm ist es auch nicht. Eigentlich wollte ich schon immer einen Blog haben und da ...

    Zum Profil von Nils Langner

    23 Kommentare »


    • Benni
      am 9. Dezember 2009 um 07:55 Uhr

      Sollte die Überschrift nicht “Caching: stale-if-error” heißen?


    • Nils Langner
      am 9. Dezember 2009 um 08:20 Uhr

      @Benni: Da muss ich dir Recht geben :) Danke für den Hinweis.


    • Dennis Becker
      am 9. Dezember 2009 um 09:19 Uhr

      Jetzt die Spannende Frage: klappt das mit Zend_Cache? Wenn ja, wie sieht das aus? :)


    • Nils Langner
      am 9. Dezember 2009 um 09:32 Uhr

      @Dennis: Ich bin kein Zend Experte, aber soviel ich weiß, kann der Zend Cache das nicht. Leider.


    • Hannes Dorn
      am 9. Dezember 2009 um 09:37 Uhr

      Ich würde noch einen Schritt weiter gehen, und immer den Inhalt des Cache anzeigen. Wenn ich danach drauf komme, das der Inhalt veraltet ist, hole ich mir den neuen Inhalt und speichere ihn im Cache. Damit muß der User nicht warten, bis die Daten neu erzeugt wurden. Nur der erste User muß warten, aber man kann die Daten ja auch vorab generieren und im Cache ablegen. Man könnte auch eine Deadline definieren, nach der nicht mehr der Inhalt des Cache ausgeliefert wird, weil er zu alt ist. Dann wartet der User bei diesem Zugriff mal kurz.


    • Nils Langner
      am 9. Dezember 2009 um 09:39 Uhr

      @Hannes: Ja das stimmt, nennt sich dann stale-on-revalidate (siehe url). Das Problem dabei ist, dass du deine Architektur umbauen musst, denn an dem Ansatz musst du die Aufgabe parallel abarbeiten.


    • Timo
      am 9. Dezember 2009 um 09:42 Uhr

      Äh, Nils, heisst das, dass du jedes Mal beim generieren einer Seite von phphatesme alle Twitter follower abholst???

      Sorry, selbst wenn Twitter mehr als 100 Requests/h zulassen würde – das geht ja gar nicht. Wenn die Twitter-Server mal nicht antworten, dann rennst du ja jedes Mal auf den HTTP-Timeout deines HTTP-Clients…

      Hannes hat vollkommen recht, du musst den Wert beim ersten Mal n den Cache legen und dich danach davon bedienen. V.a. wenn es um soetwas undynamisches (und -wichtiges ^^) wie die Anzahl der Follower geht…


    • Dennis Becker
      am 9. Dezember 2009 um 09:44 Uhr

      Meine sub-optimale Idee wäre, die Daten 2x im Cache abzulegen: einmal mit und einmal ohne Lifetime. So hätte man immernoch einen Fallback. Entweder man schreibt das explizit so in seinen Code oder man legt einen eigenen Zend_Cache_Backend-Adapter an und fackelt dieses Prinzip komplett darin ab und fügt die Methode “getLastValid” hinzu, die den stale ausliest.


    • Nils Langner
      am 9. Dezember 2009 um 09:45 Uhr

      @Timo: Genau deswegen habe ich ja den Artikel geschrieben ;) Da ich kein kritisches System habe, war mir das so lange recht, wie die Twitter API eine Zuverlässigkeit von 99% hat. Leider häufen sich die Fehler in letzer Zeit, so dass ich jetzt auf stale-if-error umschwenken werde. Stale-on-revalidate sehe ich zu umständlich in meinem Fall, dafür dass ich kaum einen Benefit von haben werde.


    • Nils Langner
      am 9. Dezember 2009 um 09:49 Uhr

      @Timo: Eine kurze Frage noch zu deinem Kommentar. Hast du es so verstanden, dass ich gar keinen Cache verwende?

      “Hannes hat vollkommen recht, du musst den Wert beim ersten Mal n den Cache legen und dich danach davon bedienen. V.a. wenn es um soetwas undynamisches (und -wichtiges ^^) wie die Anzahl der Follower geht…”

      Das wäre ja normales Caching, oder?


    • Timo
      am 9. Dezember 2009 um 09:59 Uhr

      Das Problem ist mir schon klar, aber:

      > Das Problem dabei ist, dass die API
      > nur 100 Request pro Stunde abkann

      Warum solltest du ansonsten 100 Requests pro Stunde an Twitter senden?


    • Nils Langner
      am 9. Dezember 2009 um 10:00 Uhr

      @Timo: 2 Rechner mit twirl installiert, die Follower und auf jeder Seite individuell die Retweets. Da kommen schnell 100 zusammen. Läuft momentan leider alles über einen Account.


    • Hannes Dorn
      am 9. Dezember 2009 um 10:11 Uhr

      stale-on-revalidate verwende ich auf Websites, wo die Generierung der Seiten sehr aufwendig ist und dementsprechend dauert.
      Da läuft aber nix parallel, sondern ich gucke nach dem ausliefern des Cacheinhalts, ob was zu tun ist. Wenn was zu tun ist, gucke ich, ob nicht schon jemand anders den Cache aktualisert. Nur dann wird die Seite neu erzeugt. Das Php Script läuft also einfach weiter, der User hat aber schon eine Seite auf dem Schirm.


    • Nils Langner
      am 9. Dezember 2009 um 10:12 Uhr

      @Hannes: Ok, lass ich durchgehen ;)


    • Ulf
      am 9. Dezember 2009 um 11:41 Uhr

      @Hannes
      Wie bewerkstelligst du denn dass, dass du prüfst ob der Cache aktuell ist und ob ihn nicht schon jemand anderes aufbaut? Während dieser Zeit ist doch dein PHP-Skript mit diesen Aufgaben beschäftigt und somit muss dieser Request (= User) länger auf sein Ergebnis warten. Sehr unschön, sauberer und besser (aber auch weitaus kompliziert) wäre gerade diese Überprüfungen in einem neuem Thread zu starten.

      Ich finde es schade, dass Caching-Systeme nicht selbst mitbekommen, dass Sie invalide Daten haben (oder geht das doch?) und diese wieder auffrischen. Dann hat man auch nie Wartezeiten für irgendeinen User.


    • Hannes Dorn
      am 9. Dezember 2009 um 11:51 Uhr

      Mein Script liefert immer eine Seite sofort aus (sofern im Cache vorhanden). Dann wird geguckt, ob die Seite im Cache neu erzeugt werden muß. Wenn ja, wird das in der Datenbank vermerkt, daß der Cache-Eintrag bereits von einem Script aktualisiert wird. Wird die Seite nun von einem anderen User aufgerufen, checkt das Script, daß die Seite zwar neu erzeugt werden muß, aber machts nicht mehr selbst, weil bereits ein anderes Script das macht.

      Klar kann man ein Cache System auch so bauen, daß es selbst Seiten aktualisiert. Aber ich will das nicht, ich will nur die Seiten aktualisieren, die auch abgerufen werden, und nicht auf verdacht 10.000 Seiten immer wieder aktualisieren.

      Wir aktualisieren den Cache beim Speichern von Änderungen im Backend. Damit muß auch keiner warten.


    • Cem Derin
      am 9. Dezember 2009 um 13:20 Uhr

      Müsste es nicht eigentlich “stale on error” heißen? ;)

      Jedenfalls: Ich habe mich des Problemes übrigens dahingehend entledigt, dass die Daten nicht bei einem Aufruf berechnet werden, sondern in dem Fall lediglich eine Neuberechnung über eine Queue anfordern. Das sorgt am Ende dann auch dafür, dass evtl. länger dauernde Berechnungen nicht zu Lasten des Benutzers gehen … und die von Ulf erwähnten Race-Conditions sind somit auch (so gut wie) ausgeschlossen.

      Willkommen zurück, übrigens ;) Wenn du die letzten zwei Wochen nicht da warst, hast du ja meine Fulminante Rückkehr aus dem Reich der Bloggerleichen verpasst ;)


    • Nils Langner
      am 9. Dezember 2009 um 14:39 Uhr

      @Cem: Die habe ich natürlich auch aus Thailand miterlebt. Finde ich super, dass ich endlich wieder was zum Lesen habe.
      Dein Ansatz ist natürlich ein angenehmer, leider ist er in der Entwicklung wohl ein wenig aufwendiger. stale-on-error klingt besser, aber soviel ich weiß, ist stale-if-error richtig.


    • Cem Derin
      am 9. Dezember 2009 um 14:56 Uhr

      Klar, kommt natürlich immer drauf an, ob es sich für das Projekt lohnt. Abstrahiert und problemlos übertragbar hab ich es auch noch nicht gebaut ;)


    • Ulf
      am 9. Dezember 2009 um 15:42 Uhr

      @Hannes
      Verwendest du ein Framework? Denn beim Zend Framework ist die Auslieferung des Responses die letzte Aktion, d.h. du kannst danach nicht irgendwelche Backend-Logiken anstoßen.

      Ansätze den Cache selbst zu inavlidieren bzw. die Neugenerierung anzustoßen, gibt es glaube ich unzählige. Die imo beste wäre es aber eben wenn der Cache das selbst könnte (und gerne auch mit Prios für wichtigere und unwichtige Bestandteile, so dass der Cache nicht permanent unwichtige Daten neu erstellt).


    • Hannes Dorn
      am 9. Dezember 2009 um 16:07 Uhr

      @Ulf
      Ja, verwende ein Framework (mein eigenes): http://www.opage.at
      Bei mir wird der Inhalt in einer Variable gesammelt, komprimiert und dann ausgegeben (vereinfacht dargestellt). Danach folgt ein flush(). Das Script läuft danach auf dem Server noch einfach weiter.


    • Test first = besserer Code? | PHP hates me - Der PHP Blog
      am 10. Dezember 2009 um 07:01 Uhr

      [...] weiteres Caching Buzzword philosophieren. Stale-on-revalidate wäre es gewesen. Da wir aber in den gestrigen Kommentaren schon genug darüber gesprochen haben, habe ich mich entschieden den Tag doch einem anderem Thema [...]


    • PHPGangsta
      am 10. Dezember 2009 um 11:12 Uhr

      > Ich finde es schade, dass Caching-Systeme nicht
      > selbst mitbekommen, dass Sie invalide Daten haben
      > (oder geht das doch?) und diese wieder auffrischen.
      > Dann hat man auch nie Wartezeiten für irgendeinen User.

      Das wäre für den ein oder anderen Einsatzzweck vielleicht echt eine Option. Google hat genau diese Funktionalität in seinen DNS-Server eingebaut, der dadurch deutlich schneller antwortet, da es deutlich weniger Cache-misses gibt (nur bei völlig unbekannten Domains. Jede Domain, die bereits einmal aufgerufen wurde, wird vor dem Ablauf erneuert).

      http://www.heise.de/ix/meldung/Oeffentlicher-DNS-Server-von-Google-876709.html

    RSS Feed für Kommentare zu diesem Artikel. TrackBack URL

    Hinterlasse einen Kommentar

    Werbung
    PHP Magazin
    Ausgabe 02/2010

    Dieses Mal mit Artikeln zu den Themen OpenSocial und Apache Shindig, Graphentheorie, Smarty3

    t3n
    Ausgabe 19

    Social Media (R)evolution. Weitere Themen sind noSQL, Crowdsourcing ...

    PHP Journal
    Ausgabe 2/2010

    PHP & Windows optimal nutzen, die besten PHP-CMS im Überblick, Google-API mit Zend Framework nutzen.

    Wir wurden schon öfters gefragt, ob man uns nicht irgendwie unterstützen kann. Die Antwort war immer einfach: Klar! Am einfachsten ist es eure nächsten Einkäufe bei Amazon über unsere Link abzuwickeln. Damit würdet ihr uns schon sehr helfen. Über Co-Autoren freuen wir uns aber noch mehr.