Im ersten Teil dieses Artikels"Drupal Caching Teil 1" haben wir einen Überblick über die unterschiedlichen Caching-Ebenen in Drupal gegeben. Nun wenden wir uns den in den Drupal-Core integrierten Caching-Methoden zu, also dem Datenbank-basierten Caching von Drupal.
Caching in der Datenbank
Eine pure Core-Installation von Drupal bringt in der Datenbank bereits 10 entsprechende Tabellen mit, die anhand ihrer Namen schnell zu identifizieren sind:
cache
Diese Tabelle steht allen Modulen zur Verfügung, um Informationen abzulegen. Sie wird z.B. verwendet, um Listen von Inhaltstypen, Bildstilen, Textformaten oder Plugins zu cachen
cache_block
Enthält gerenderte Blockinhalte, falls das Block Caching aktiviert wurde
cache_bootstrap
Enthält Informationen die zum Beginn eines Seitenabrufs benötigt werden, z.B. die Liste der Variablen, oder der implementierten Hooks
cache_field
Das field - Modul legt hier Informationen über Felder ab (Strukturen, Widgets, Formatter, usw.)
cache_filter
Speichert die Ergebnisse von Textfiltern im Zusammenhang mit Drupals Eingabeformaten
cache_form
Enthält Formularstrukturen und -Zustände, die kurzfristig nochmals benötigt werden, z.B. bei Multistep-Formularen
cache_menu
Informationen über Menüstrukturen (Hierarchien, Linktexte, Seiten)
cache_page
Hier werden komplett gerenderte HTML-Seiten für anonyme Benutzer abgelegt, falls das Page Caching aktiviert wurde
cache_path
Das path Modul speichert hier Informationen über URL-Aliase
cache_update
Hier hinterlegt das update Modul Informationen z.B. über Modulversionen
Die Struktur dieser Tabellen ist dabei immer die selbe:
Spalte | Typ | Null | Standard | Beschreibung |
---|---|---|---|---|
cid | varchar(255) | Nein | Primärschlüssel: eine eindeutige Cache ID, das kann z.B. ein Hash-Tag sein, aber auch ein Pfad oder eine Benutzer-ID, je nach Anwendungszweck | |
data | longblob | Ja | NULL | Die Daten in diesem Cache-Eintrag |
expire | int(11) | Nein | 0 | UNIX Timestamp mit dem Ablaufzeitpunkt der Information, 0 bedeutet nie, -1 bedeutet bei nächster Gelegenheit |
created | int(11) | Nein | 0 | UNIX Timestamp mit dem Erzeugungszeitpunkt dieser Information |
serialized | smallint(6) | Nein | 0 | Ein Flag welches anzeigt ob die Information serialisiert abgelegt wurde (1) oder nicht (0). PHP-Arrays oder -Objekte werden z.B. automatisch serialisiert gespeichert |
Andere Module können für ihren eigenen Bedarf selbst solche Tabellen erzeugen, so speichert z.B. das ModulViews Ergebnisse von Datenbank-Abfragen und gerenderte Listen von Inhalten in seiner eigenen Tabelle cache_views
Es gibt auch Module, die eine völlig autarke Form von Datenbank-Cache implementieren, dazu gehört z.B. CTools.
Caching-Konfiguration im Backend
Es gibt zwei Arten von Cache in Drupal, deren Funktionsweise man im Verwaltungsbereich der Oberfläche beeinflussen kann: den Seiten-Cache und den Block-Cache.
Davon betroffen sind auch nur die entsprechenden Cache-Tabellen, also cache_page und cache_block, nur diese lassen sich mit einfachen Mitteln deaktivieren. Es ist also nicht möglich, das Caching für die Theme-Registry oder die Menüstrukturen abzuschalten, und das ist der Grund, warum man als Drupal-Entwickler immer mal wieder manuell den Cache löschen muss, sei es über den oberen Button auf der Konfigurationsseite, oder auch mittels des entsprechenden Drush-Befehls.
Der Drupal Seitencache
Für anonyme Benutzer bietet Drupal die Möglichkeit, komplett gerenderte Seiten im Cache abzulegen. Das kann natürlich die Performance einer Anwendung erheblich beschleunigen, da. z.B. die Zahl der erforderlichen Datenbankzugriffe für den Aufbau der Seite drastisch abnimmt.
Man hat sich entschieden, diesen Cache nur für anonyme Benutzer zu implementieren, da eingeloggte Benutzer häufig individuell modifizierte Inhalte zu sehen bekommen, und sei es nur durch einen unauffälligen Link zum jeweils eigenen Benutzerprofil.
Wenn dieser Cache aktiviert ist, werden die gerenderten Seiten in der Tabelle cache_page hinterlegt. Beim Abruf einer Seite wird geprüft, ob sie im Cache vorhanden ist, und entsprechend die dort abgelegte Version angezeigt, anstatt die Seite komplett neu zu erzeugen.
Als Cache-ID wird hier konsequenterweise die URL der jeweiligen Seite verwendet.
Der Drupal Block Cache
Auch gerenderte Blockinhalte können bei Bedarf in einem Cache gespeichert werden, wobei dieser Cache völlig unabhängig vom Seitencache funktioniert. Das heißt, Blöcke werden ggf. auch für angemeldete Benutzer im Cache gespeichert. Die Besonderheit an diesem Cache ist, dass Blockinhalte oft kontextabhängig sind, also ihr Inhalt auf verschiedenen Seiten oder für unterschiedliche Benutzerrollen nicht der selbe ist. Standardmäßig werden Blöcke pro Benutzerrolle gecacht. Ein Modul, welches einen oder mehrere Blöcke bereitstellt, kann in seiner Implementierung von hook_block_info() die Art des Caching-Verfahrens vorgeben. Hier ein Beispiel:
function mymodule_block_info() { $blocks['myblock'] = array( 'info' => t('This is my block'), 'cache' => DRUPAL_CACHE_GLOBAL, ); return $blocks; }
Hier wird einfach ein Block definiert, der auf allen Seiten identisch ist, also im Cache auch nur einmal auftaucht.
Noch ein kleiner Tipp: für den Administrator einer Seite (UID 1) werden Blöcke grundsätzlich nicht gecacht! Das gilt es beim Testen zu berücksichtigen.
Wie lange werden Cache-Einträge eigentlich gespeichert?
Module, welche Einträge in Drupals Cache-Tabellen erzeugen, können selbst bestimmen, wie lange diese vorgehalten werden sollen. Dabei gibt es drei Varianten:
- die Einträge können als permanent gekennzeichnet werden, dann müssen sie explizit gelöscht werden.
- die Einträge werden als temporär markiert, dann werden sie z.B. beim nächsten Cron-Lauf automatisch entfernt.
- die Einträge werden mit einem Verfallszeitpunkt ersehen, bis zu diesem werden sie als permanent betrachtet, nach diesem Zeitpunkt als temporär.
Die meisten Cache-Einträge in Drupal werden als permanente Einträge erzeugt und somit nur bei Änderungen explizit gelöscht und neu erstellt. Zu den Ausnahmen gehören jedoch sowohl der Seitencache als auch der Block Cache. Während Seiten entweder temporär oder mit einem Verfallszeitpunkt gespeichert werden (falls eine minimale Cache-Lebensdauer eingestellt wurde, s.o.), sind Blöcke grundsätzlich temporäre Cache-Objekte. In beiden Fällen erfolgt die Löschung der Cache-Einträge jeweils beim Cron-Lauf (bei Seiten natürlich nicht vor dem Ablaufzeitpunkt).
Wenn man auf einer Drupal-Seite Cron nicht benutzt und auch keine manuelle Bereinigung vornimmt, werden die Cache-Tabellen im Laufe der Zeit also immer mehr anwachsen! Wenn man im Gegensatz dazu Cron zu häufig startet, verschenkt man Leistung, da der Cache erst wieder neu aufgebaut werden muss.
Beim Seitencache gibt es noch ein anderes Detail zu beachten: dieser Cache wird nicht nur beim Cron-Lauf bereinigt, sondern auch beim Bearbeiten von Content, also z.B. Editieren von Nodes. Wenn man also eine Seite mit sehr vielen Bearbeitungsvorgängen hat (z.B. ein News-Portal), wird der Seitencache ständig bereinigt und damit fast wirkungslos - es sei denn, man stellt eine Verfallszeit für das Seitencaching ein!
Cache in eigenen Modulen verwenden
Module können das Caching-System von Drupal benutzen, hierzu steht ihnen ein entsprechendes API mit einigen grundlegenden Funktionen zur Verfügung:
Mit Hilfe der Funktionen cache_set und cache_get bzw. cache_get_multiple können Module Cache-Einträge erzeugen bzw. lesen.
Die Funktion cache_clear_all() dient dazu, einzelne oder mehrere Cache-Einträge aus Drupals Caching-Tabellen zu entfernen. Wenn keine Parameter angegeben sind, werden alle temporären oder abgelaufenen Einträge aus den Tabellen cache_block und cache_page gelöscht.
Die Funktion cache_is_empty ermittelt, ob eine Cache-Tabelle leer ist.
Das ist wirklich eine überschaubare Menge an Funktionen, und mit ihrer Verwendung ist ein unschätzbarer Vorteil verbunden: das Datenbank-Caching-System von Drupal ist eine Klasse, diese kann auf einfache Art und Weise teilweise oder komplett durch ein alternatives System ersetzt werden. Dies wird beispielsweise von Modulen wie Memcache oder APC benutzt, um Cache-Informationen nicht in der Datenbank, sondern im RAM des Webservers zu speichern. Dazu implementieren solche Module das Interface DrupalCacheInterface und können sich anschließend z.B. über einige Variablen in der settings.php als Cache Backend für bestimmte oder alle Cache-Tabellen eintragen. Ach ja, spätestens jetzt hat es eigentlich keinen Sinn mehr, von "Tabellen" zu sprechen, da es sich ja gar nicht mehr um Cache in einer Datenbank handeln muss. Deshalb wird in der Drupal Terminologie meistens der allgemeinere Begriff cache bin (also Cache-Behälter) verwendet.
Die Funktion drupal_flush_all_caches geht in ihrer Funktionsweise weit über das eigentliche Caching-System hinaus: sie entfernt nicht nur abgelaufene Elemente aus den Cache-Tabellen, sondern baut auch sämtliche Menüstrukturen neu auf, erzeugt die Theme-Registry neu, löscht aggregierte JS- und CSS-Dateien, und mehr - eine radikale Lösung, die dementsprechend zeitaufwändig ist, aber wirkungsvoll. Module, welche eigene Cache-Tabellen erzeugen, sollten diese über hook_flush_caches bekanntgeben, damit sie beim Leeren der Cache-Tabellen berücksichtigt werden können. Der Button "Gesamten Cache löschen" im Bild oben löst genau diese Funktion aus, ähnlich wie der Drush-Befehl 'cache-clear all'. Aber in beiden Fällen werden wie gesagt nur abgelaufene Cache-Objekte entfernt!
Praxisbeispiel
Nach so viel geballter Theorie wird es Zeit für ein abschließendes Beispiel für die Nutzung von Drupals Cache-Funktionen in einem eigenen Modul. Dabei soll es ausnahmsweise mal nicht um Leistungssteigerung gehen, denn der Cache von Drupal kann natürlich auch für andere Zwecke verwendet werden, wenn Informationen kurzfristig gespeichert werden müssen.
Betrachten wir einfach mal ein kleines Formular auf der Produktseite eines Shops, das dazu dient den Warenkorb zu befüllen. Der Kunde würde z.B. im Formular Anzahl, Größe und Farbe des Artikels eintragen, beim Klick des Buttons würde ein Skript aufgerufen werden, das den Artikel in den Warenkorb einträgt. Diese Werte würden als Parameter in der URL übergeben.
Das könnte z.B. im Submit Handler des Formulars folgendermaßen aussehen:
function order_form_submit() { ... $form_state['redirect'] = array( 'order', array( 'query' = array( 'article' => 5143, 'size' => $form_state['values']['size'], 'color' => $form_state['values']['color'], 'count' => $form_state['values']['count'], ), ), ); ... }
Hier würde also beim Abschicken des Formulars folgende URL aufgerufen:
/order?article=5143&size=XL&color=blue&count=2
Das funktioniert, würde aber dem Kunden auch die Möglichkeit geben, die URL manuell zu ändern, z.B. in /order?article=9999&size=S&color=pink&count=999
. Unser Warenkorbskript müsste das abfangen.
Solche Anwendungsfälle kann man mit Hilfe des Drupal Caches wesentlich sicherer gestalten. Ändern wir den Code ein wenig:
function order_form_submit() { ... $parms = new stdClass(); $parms->article = 5143; $parms->size = $form_state['values']['size']; $parms->color = $form_state['values']['color']; $parms->count = $form_state['values']['count']; $cid = uniqid(); cache_set($cid, $parms, 'cache', time() + 300); $form_state['redirect'] = "order/$cid"; ... }
Wir speichern also die Bestellparameter im Cache von Drupal, in diesem Fall für mindestens 5 Minuten, und übergeben an unser Warenkorbskript nur noch eine eindeutige und schwer manipulierbare ID, die URL wäre also z.B. /order/4b3403665fea6
Unser Warenkorbskript müsste diesen Vorgang jetzt umkehren:
... $cid = arg(1); if ($cid) { $cache = cache_get($cid); if ($cache) { cache_clear_all($cid,'cache'); $parms = $cache->data; $article = $parms->article; $size = $parms->size; $color = $parms->color; $count = $parms->count; ... } } ...
Die Cache-ID aus der URL wird verwendet, um das Cache-Objekt aus der Datenbank zu lesen und anschließend zu löschen. Danach werden die Bestellparameter weiterverarbeitet.
Es steckt also wirklich nicht viel dahinter, Drupals Cache in eigenen Modulen zu benutzen, wo es sich aus Leistungs- oder anderen Gründen anbietet.