Facebook
Twitter
Google+
Kommentare
23

Caching: stale-if-error

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.
Ü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

23 Comments

  1. 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.

    Reply
  2. @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.

    Reply
  3. Ä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…

    Reply
  4. 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.

    Reply
  5. @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.

    Reply
  6. @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?

    Reply
  7. 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?

    Reply
  8. @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.

    Reply
  9. 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.

    Reply
  10. @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.

    Reply
  11. 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.

    Reply
  12. 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 😉

    Reply
  13. @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.

    Reply
  14. @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).

    Reply
  15. > 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

    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