comm-press: Your Drupal agency in Hamburg, Germany: Consulting, training, programming, service, webdesign and hosting

Caching mit renderbaren Arrays . Display Cache die Zweite

von Carsten Rhein
am

Im ersten Teil habe ich die Idee von Display Cache bereits vorgestellt. Nun ist ein wenig Zeit vergangen und es hat sich viel getan. Was sich genau geändert hat, stelle ich hier vor.

Caching im Core

Das Caching wird nun vom Core übernommen. Wie das? Renderbare Arrays können ein Attribut erhalten, welches das Caching für das entsprechende Element konfiguriert.

array('keys', 'for', 'cid'), // Cache-Tabelle. 'bin' => 'cache_my_cache_table', // Zeit, bis der Cache neu aufgebaut wird. 'expire' => CACHE_PERMANENT, // Granularität; Cache pro URL und Rolle oder User. 'granularity' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_USER, ); ?>

Hiermit wird, wenn $renderable_array durch drupal_render() läuft, ein neuer Cache-Eintrag in der Tabelle cache_my_cache_table mit der Cache ID keys:for:cid:my_theme:u.1:http://example.com/node/1 generiert. In diesem Cache-Eintrag befindet sich nun ein Array, bestehend aus: ['#markup] = Gerendertes HTML aus dem renderbarem Array und ['#attached'] = Ein Array, welches benötigte CSS-, JavaScript-, und Library-Verweise enthällt.

Auf Grundlage dieser Funktionalität im Core sind die im ersten Blogartikel angesprochenen Panels und Views-Plugins überflüssig geworden.

Caching von renderbaren Arrays

Da das Caching des HTML nun vom Core übernommen wird, gibt es ja nichts mehr zu tun, oder? Nun, das ist nicht ganz richtig, denn das renderbare Array muss nach wie vor erstellt werden. Im Entity API Modul besteht die Möglichkeit, den View-Callback zu überschreiben. Somit bietet Display Cache nun einen eigenen View Callback an. Dieser ruft den originalen Callback auf und cacht das zurückgegebene renderbare Array.

Leider funktioniert dies nur mit Entitäten, welche auch über eine View-Callback verfügen. Zum Glück ist dies auf jedenfall bei Nodes der Fall. Außerdem darf nicht node_view($node) verwendet werden. Um dieses Feature zu verwenden, muss entity_view('node', $node) verwendet werden.

Die Panels und Views-Plugins, welche von Entity API bereitgestellt werden, nutzen entity_view(). Also immer schön 'Rendered Entity' auswählen.

Granularisiertes Caching

Wie oben bereits angesprochen, unterstüzt der Core verschiedene Caching-Granularitäten:

  • Keine Granularität = Gecacht, egal was ist!
  • Pro URL = Jede URL erhällt einen eigenen Cache-Eintrag
  • Pro Rolle = Alle Kombinationen aus Rollen erhalten einen eigenen Cache-Eintrag. Kombinationen desshalb, weil ein User unter Umständen nicht nur eine Rolle hat. Hat ein User also die Rollen A und B, wird ein Cache-Eintrag erstellt. Hat dann ein anderer User die Rollen A, B und C, wird ein weiterer für diese Rollenkombination erstellt.
  • Pro User = Ein Cache-Eintrag pro User. Diese Option ist nicht mit dem Caching Pro-Rolle kombinierbar.

Dies ermöglicht beispielsweise folgendes Szenario: Wir alle kennen Field Collections. Ein solches Feld hat die Möglichkeit Add-, Edit- und Delete-Links mit auszugeben. Ärgerlich ist nur, wenn das Display ohne Granularität gecachet wurde. Dann passiert es, dass ein User ohne entsprechende Berechtigung diese Links sieht, welche er zwar anklicken kann, aber dann auf einer 403-Seite landet. Oder andersherum, jemand mit der Berechtigung sieht diese Links nicht. Hängt halt davon ab, wer als erstes die Seite aufgerufen hat. Dessen Ergebnis landet im Cache.

Also einfach den Cache mit der Granularität 'Pro Rolle' konfigurieren und fertig ist's.

Caching von Feldern

So, jetzt haben wir das Display gecachet. Aber ich gehe noch weiter: Warum nicht auch die Felder cachen?

Im obigen Beispiel sehen wir, dass ein Display mehrmals gerendert werden muss. Eben für alle Rollen-Sets, die darauf zugreifen. Jedoch ist dies nur wegen der Field Collection nötig. Also kommt noch ein Field-Cache hinzu. Dieser Cache funktioniert genau wie der Display-Cache mit dem magischem Cache-Array im renderbaren Array eines Feldes. Außerdem kann jedes Feld, genau wie die Displays, mit unterschiedlicher Granularität gecachet werden.

Es ergibt sich somit folgende Konfiguration:

  • Display => Cache pro Rolle
  • Body Field => keine Granularität
  • Field Collection => Cache pro Rolle

Wird die Node nun von einem User mit anderer Rollen-Kombination aufgerufen, wird nur das Display und die Field Collection neu gerendert. Das Body Field wird lediglich aus dem Cache geholt und dem Display hinzugefügt.

Hier ist zu beachten, dass Felder nur weniger granuliert als das Display sein können. Wenn das Display alles cachet, hat die Pro-User-Granularität eines Feldes keinen Einfluss auf das Ergebnis.

Field Cache Only

Was ist nun, wenn ein Feld nicht gecachet werden darf. Oder irgendetwas anderes Request-Abhängiges innerhalb einer Node gerendert wird? Schalten wir doch den Display Cache einfach ab und nutzen nur den Field-Cache. Hier können nun alle Felder unabhängig voneinander konfiguriert werden. Und es können Felder vom Caching ausgeschlossen werden. Beispiele hierfür können sein: Fivestar, Flag, Kommentar-Formular, etc.

Was hat sich eigentlich nicht geändert?

Der Ansatz. Ein Display verwaltet seinen eigenen Cache. Ändert sich die zugrunde liegende Entität, wird der Cache neu aufgebaut. Sind andere Caching-Mechanismen, wie zeitbasiertes Caching in Panels oder Varnish, keine Option, schließt Display Cache hier eine Lücke.

Benchmark

Als Abschluss nun noch ein wenig Material für die, die sich fragen: "Ist ja toll, aber was bringt es nun genau?" Für diesen Zweck habe ich drei unterschiedliche Node-Typen angelgt:

  • Simple Node = Einfache Node; Ein Titel, ein Body.
  • Complex Fields = Diese Node verfügt über eine Vielzahl von Feldern.
  • Complex Preprocess = Diese Node ist im Prinzip wie eine Simple Node. Jedoch wird in hook_node_load eine Vielzahl von Entity-Field-Queries ausgeführt, welche auf diese Node referenzieren. Während des Preprocess werden diese Kind-Nodes gerendert.

Getestet werden alle drei Modi: Ungecachet, Field Cache und Full Cache. Außerdem einmal mit und einmal ohne Panels, um das Caching des renderbaren Arrays herauszustellen.

Benchmark, ohne das renderbare Array zu cachen
Benchmark inklusive renderbares Array
Benchmark inklusive EntityCache