Wie funktioniert das schlüsselbasierte Caching?

10

Ich habe kürzlich einen Artikel im 37Signals-Blog gelesen und frage mich, wie sie den Cache-Schlüssel erhalten.

Es ist schön und gut, einen Cache-Schlüssel zu haben, der den Zeitstempel des Objekts enthält (dies bedeutet, dass der Cache ungültig wird, wenn Sie das Objekt aktualisieren). Aber wie verwenden Sie dann den Cache-Schlüssel in einer Vorlage, ohne einen DB-Treffer für genau das Objekt zu verursachen, das Sie aus dem Cache abrufen möchten?

Wie wirkt sich dies insbesondere auf die Eins-zu-Viele-Beziehungen aus, in denen Sie beispielsweise die Kommentare eines Posts rendern?

Beispiel in Django:

{% for comment in post.comments.all %}
   {% cache comment.pk comment.modified %}
     <p>{{ post.body }}</p>
   {% endcache %}
{% endfor %}

Unterscheidet sich das Caching in Rails beispielsweise nur von Anfragen an Memcached (ich weiß, dass sie Ihren Cache-Schlüssel in etwas anderes konvertieren). Zwischenspeichern sie auch den Cache-Schlüssel?

Dominic Santos
quelle
Ein Beispiel für Django finden Sie unter rossp.org/blog/2012/feb/29/fragment-caching !
Vdboor
Ich habe mir das schon angeschaut und das scheint unter genau dem gleichen Problem zu leiden. Die Daten, die er zwischenspeichern möchte, sind erforderlich, um auf den Cache zugreifen zu können. Das einzige, was er zu sparen scheint, ist der innere teure Vorgang, der sich von den meisten Anwendungsfällen für diese Art des Caching unterscheidet.
Dominic Santos
Das stimmt, und das passiert auch mit dem 37signals-Code, der sich auf den Rendering-Code konzentriert. Der Trick besteht darin, die gesamte Liste auch in einem anderen Container zwischenzuspeichern oder den Abruf des Objekts an einer anderen Stelle zwischenzuspeichern.
Vdboor
Eigentlich scheint ihre Caching-Strategie etwas besser ausgebildet zu sein. Ich empfehle auch diesen Artikel: 37signals.com/svn/posts/…
JensG
Es sieht so aus, als hätte Ihr Code-Snippet einen Tippfehler - post.bodysollte es sein comment.body?
Izkata

Antworten:

3

Ja, wenn Sie einen geraden Speicherauszug eines einzelnen bereits geladenen Objekts zwischenspeichern, erhalten Sie nichts oder so gut wie nichts. Das beschreiben diese Beispiele nicht - sie beschreiben eine Hierarchie, bei der jede Änderung an etwas Niedrigerem auch eine Aktualisierung aller höheren Elemente in der Hierarchie auslösen sollte.

Das erste Beispiel aus dem 37signals-Blog wird Project -> Todolist -> Todoals Hierarchie verwendet. Ein ausgefülltes Beispiel könnte folgendermaßen aussehen:

Project: Foo (last_modified: 2014-05-10)
   Todolist:  Bar1 (last_modified: 2014-05-10)
       Todo:  Bang1 (last_modified: 2014-05-09)
       Todo:  Bang2 (last_modified: 2014-05-09)

   Todolist:  Bar2 (last_modified: 2014-04-01)
       Todo:  Bang3 (last_modified: 2014-04-01)
       Todo:  Bang4 (last_modified: 2014-04-01)

Nehmen wir also an, es Bang3wurde aktualisiert. Alle Eltern werden ebenfalls aktualisiert:

Project: Foo (last_modified: 2014-05-16)
   Todolist:  Bar2 (last_modified: 2014-05-16)
       Todo:  Bang3 (last_modified: 2014-05-16)

Wenn es dann Zeit zum Rendern ist, ist das Laden Projectaus der Datenbank grundsätzlich unvermeidlich. Sie brauchen einen Punkt, um anzufangen. Da last_modifiedes sich jedoch um einen Indikator für alle untergeordneten Elemente handelt, verwenden Sie diesen als Cache-Schlüssel, bevor Sie versuchen, die untergeordneten Elemente zu laden.


Während die Blog-Beiträge separate Vorlagen verwenden, werde ich sie zu einer zusammenfassen. Hoffentlich wird die vollständige Interaktion an einem Ort etwas klarer.

Die Django-Vorlage könnte also ungefähr so ​​aussehen:

{% cache 9999 project project.cache_key %}
<h2>{{ project.name }}<h2>
<div>
   {% for list in project.todolist.all %}
   {% cache 9999 todolist list.cache_key %}
      <ul>
         {% for todo in list.todos.all %}
            <li>{{ todo.body }}</li>
         {% endfor %}
      </ul>
   {% endcache %}
   {% endfor %}
</div>
{% endcache %}

Angenommen, wir übergeben ein Projekt, dessen cache_keyCache noch vorhanden ist. Da wir Änderungen an allen zugehörigen Objekten an das übergeordnete Objekt weitergeben, bedeutet die Tatsache, dass dieser bestimmte Schlüssel noch vorhanden ist, dass der gesamte gerenderte Inhalt aus dem Cache abgerufen werden kann.

Wenn dieses bestimmte Projekt gerade aktualisiert wurde - beispielsweise wie Foooben -, muss es seine untergeordneten Elemente rendern und erst dann die Abfrage für alle Todolisten für dieses Projekt ausführen. Ebenso für eine bestimmte Todolist - wenn der cache_key dieser Liste vorhanden ist, haben sich die darin enthaltenen Aufgaben nicht geändert, und das Ganze kann aus dem Cache gezogen werden.

Beachten Sie auch, dass ich todo.cache_keydiese Vorlage nicht verwende . Es lohnt sich nicht, da, wie Sie in der Frage sagen, bodybereits aus der Datenbank gezogen wurde. Datenbanktreffer sind jedoch nicht der einzige Grund, warum Sie möglicherweise etwas zwischenspeichern. Wenn Sie beispielsweise rohen Markup-Text (z. B. das, was wir in StackExchange in Frage- / Antwortfelder eingeben) und in HTML konvertieren, kann dies ausreichend Zeit in Anspruch nehmen, damit das Zwischenspeichern des Ergebnisses effizienter ist.

In diesem Fall könnte die innere Schleife in der Vorlage folgendermaßen aussehen:

         {% for todo in list.todos.all %}
            {% cache 9999 todo todo.cache_key %}
               <li>{{ todo.body|expensive_markup_parser }}</li>
            {% endcache %}
         {% endfor %}

Um alles zusammenzuführen, kehren wir zu meinen ursprünglichen Daten oben in dieser Antwort zurück. Wenn wir annehmen:

  • Alle Objekte wurden in ihrem ursprünglichen Zustand zwischengespeichert
  • Bang3 wurde gerade aktualisiert
  • Wir rendern die geänderte Vorlage (einschließlich expensive_markup_parser)

Dann würde so alles geladen:

  • Foo wird aus der Datenbank abgerufen
  • Foo.cache_key (2014-05-16) existiert nicht im Cache
  • Foo.todolists.all()wird abgefragt: Bar1und Bar2werden aus der Datenbank abgerufen
  • Bar1.cache_key(2014-05-10) existiert bereits im Cache ; Abrufen und Ausgeben
  • Bar2.cache_key (2014-05-16) existiert nicht im Cache
  • Bar2.todos.all()wird abgefragt: Bang3und Bang4werden aus der Datenbank abgerufen
  • Bang3.cache_key (2014-05-16) existiert nicht im Cache
  • {{ Bang3.body|expensive_markup_parser }} ist gerendert
  • Bang4.cache_key(2014-04-01) existiert bereits im Cache ; Abrufen und Ausgeben

Einsparungen aus dem Cache in diesem winzigen Beispiel sind:

  • Datenbanktreffer vermieden: Bar1.todos.all()
  • expensive_markup_parservermieden 3 mal: Bang1, Bang2, undBang4

Und natürlich wird es beim nächsten Anzeigen Foo.cache_keygefunden, sodass die einzigen Kosten für das Rendern darin bestehen, Fooallein aus der Datenbank abzurufen und den Cache abzufragen.

Izkata
quelle
-2

Ihr Beispiel ist gut, wenn für jeden Kommentar Daten abgerufen oder verarbeitet werden müssen. Wenn Sie nur body nehmen und anzeigen, ist der Cache nutzlos. Sie können jedoch den gesamten Kommentarbaum zwischenspeichern (einschließlich {% für%}). In diesem Fall müssen Sie es mit jedem hinzugefügten Kommentar ungültig machen, damit Sie den Zeitstempel des letzten Kommentars oder die Anzahl der Kommentare irgendwo in Post einfügen und damit den Cache-Schlüssel für Kommentare erstellen können. Wenn Sie normalisierte Daten bevorzugen und Kommentare nur auf einer Seite verwenden, können Sie beim Speichern von Kommentaren einfach einen Cache-Schlüssel löschen.

Für mich sieht das Speichern der Anzahl der Kommentare in Post gut genug aus (wenn Sie das Löschen und Bearbeiten von Kommentaren nicht zulassen) - Sie haben einen Wert, den Sie überall mit dem Post anzeigen können, und einen Schlüssel zum Zwischenspeichern.

ilvar
quelle