Von veröffentlicht am

Der Stache. Ein Überblick über den Motor von Statamic

Der Stache macht Statamic effizient im Umgang mit den Dateien und ermöglicht den schnellen Zugriff auf Inhalte

10 min.

dalle-2022-11-01-14.58.19---a-colorful-illustration-of-a-stache,-which-is-the-motor-inside-a-computer..png

Statamic speichert Daten nicht in einer Datenbank oder einer anderen externen Applikation. Statamic speichert Daten als Markdown Files und indexiert diese Daten im Stache. Der Stache (zu deutsch Schnauzer) ist ein Index, der den Zugriff auf die Daten effizient macht und all die Magic von Antlers ermöglicht. Es ist so was wie eine simple Datenbank im Laravel Application Cache. Blicken wir etwas unter die Haube von Statamic und lernen etwas über den Stache, den Basislayer von Statamic. Die ist nur eine Einführung, um besser zu verstehen, was Statamic kann und wo auch es Grenzen gibt und keine abschliessende Erklärung der Funktionsweise des Stache.

Content in Statamic

Statamic ist ein Flat-File CMS. Sprich es kommt ohne Datenbank aus und speichert den Inhalt der Webseite in einzelnen Dateien. Wenn wir uns den Beispielblog anschauen, dann sehen wir, dass im Verzeichnis /content eine Vielzahl von Ordner mit *.md Dateien abgelegt sind. Die Ordner repräsentieren den Type des Contents z.B. Collections oder Taxonomies. Öffnen wir den /content/collections Ordner, dann sehen wir für jede von uns angelegte Collection eine {collection-name}.yaml Datei (posts.yaml und ein Order {collection-name} (/content/collections/posts/). In der posts.yaml Datei auf der ersten Ebene ist die Konfiguration der Collection abgelegt und im Ordner posts befinden sich die einzelnen Entries.

Wenn wir eine Statamic-Seite im Browser aufrufen, dann müssen wir die entsprechenden Dateien von der Festplatte lesen. Meist ist dies nicht nur eine Datei, sondern die Datei des Artikels, die Konfiguration, die Datei für die Navigation und eine Globals Datei. Wollen wir eine Liste der letzten zehn Artikel (sortiert nach Erstellungsdatum), dann müssen wir alle Dateien einer Collection von der Festplatte lesen, die Erstellungsdaten vergleichen, und die richtigen Artikel zurückgeben. Dies ist inneffizient und dafür wurden Datenbanken erfunden.

Stache statt einer Datenbank

Der Stache adressiert das Effizienz-Problem von einzelnen Dateien. Der Stache setzt sich zusammen aus einzelnen Stores, die einen Type von Daten repräsentieren. So gibt es einen Store für Collections, für Entries, für Navigations, für Taxonomies usw... Jeder Store ist auch eine Klasse, die die entsprechende Funktionalität zur Verfügung stellt. Der einzelne Store verwaltet Indexes. Indexes sind Key-Value Elemente, die einzelne Aspekte eines Entry sammeln und zugänglich machen. Wollen wir z.B. nach Erstellungsdatum sortieren, dann wird ein Index date erstellt, in dem alle Daten aller Einträge abgelegt sind. Dieser Index kann effizient durchsucht werden und entsprechend können wir schnell herausfinden, welches die letzten Einträge einer Collection sind.

Der Stache wird im Laravel Application Cache gespeichert. Er baut also auf Laravel auf und erstellt Dateien im Verzeichnis /storage/framework/cache/data/stache/. Pro Index wird eine Datei angelegt. So findet sich z.B. /storage/framework/cache/data/stache/indexes/entries/posts/id für den id Index aller Posts. Die Dateien beinhalten serialisierte php Objekte, die von php schnell geladen werden können.

9999999999a:4:{s:36:"a6953f9d-ba3c-42b4-bcee-4ffc5578c785";s:36:"a6953f9d-ba3c-42b4-bcee-4ffc5578c785";s:4:"home";s:4:"home";s:36:"4f70a47a-017c-471a-8341-852e15c1b199";s:36:"4f70a47a-017c-471a-8341-852e15c1b199";s:36:"edc49d38-f560-45c0-b3f3-0e4202f64ed5";s:36:"edc49d38-f560-45c0-b3f3-0e4202f64ed5";}

Je nach Webseite, die im Browser aufgerufen wird, werden diese Dateien im Cache gelesen. Wird beispielsweise die Page /about-us aufgerufen, dann wird der Index uri /storage/framework/cache/data/stache/indexes/entries/pages/uri gelesen. Dort findet Statamic den gesuchten Wert /about-us und liesst die entsprechende Id. Im Index /storage/framework/cache/data/stache/indexes/entries/pages/path kann Statamic den Filepfad für die Id auflösen und die Datei /content/collections/pages/about-us.md einlesen. Die Daten aus dem Markdown werden in die Blueprint geladen und an Antlers übergeben. Damit können wir dann unser Frontend bauen. Zumindest ist dies der schematische Ablauf :)

Der Stache ist ephemeral, sprich er kann gelöscht werden ohne dass die Inhalte verloren gehen. Er dient nur der Optimierung. Wenn wir den Stache löschen z.B. mit php please stache:clear, dann werden die entsprechenden Daten aus dem Laravel Cache gelöscht. Dies hat einen Impact auf die Geschwindkeit unserer Webseite. Denn beim nächsten Aufruf muss der Stache neu gebaut werden.

Der Statche wird zu Querytime erstellt. Statamic weiss nicht, welche Indexe es möglicherweise gibt. Erst wenn über Antlers (oder anderer Code) nach einem Feld gefiltert wird, weiss der Stache, dass für dieses Feld ein Index benötigt wird und erstellt diesen. Wird dann das nächste Mal nach dem Feld gefitert, dann liest Statamic die Werte aus dem Stache. Der erste Aufruf der Webseite ohne Stach dauert dadurch eine gewisse Zeit. Statamic muss die entsprechenden Indexes generieren und im Stache ablegen.

Perfomance

Der Laravel Cache Layer ist sehr performant und kann zusätzlich auch auf Redis oder Memcached ausgelagert werden. Doch auch der Cache auf Filebasis ist erstaunlich schnell und kann bis zu 13'164 queries/sec umsetzen. Trotzdem wollen wir mal den Stache Layer benchmarken, um zu testen wie das Erstellen des Staches skaliert. Eine Anleitung für diesen simplen Benchmark findest du hier

100 Posts

0.684s

1'000 Posts

3.418s

10'0000 Posts

30,558s

Wie zu erwarten, verlangsamt sich das Erstellen des Staches je grösser die Seite ist. Es müssen alle Dateien gelesen werden. Ab einer gewissen Grösse wird es ineffizient den Stache zu erstellen. Dann wird es Zeit die Daten in eine richtige DB zu migrieren.

Stache optimieren

Den Stache neu zu befüllen ist für eine kleine Seite kein Problem. Wird ein Query ausgeführt, dann werden die notwendigen Indexes ergänzt. Der Aufwand dies zu tun, wächst wie gezeigt, je mehr Artikel in Statamic verwaltet werden. Entsprechend macht es Sinn, Statamic mitzuteilen, dass dieser oder jene Index benötigt wird. So muss der Index nicht beim ersten Aufruf des entsprechenden Query hinzugefügt werden. Statamic definiert pro Store gewisse Standard-Indexes. So werden für ein Collection Entry Indexes für Slug, Id, Uri, Date, Author und Path erstellt. Meist wissen wir als Programmierer:innen, welche zusätzlichen Indexes benötigt werden. Filtern oder sortieren wir nach Feldern, dann können wir die entsprechenden Indexes in der Konfiguration hinterlegen und bei einem Warm up des Stache auch gerade diese Felder indexieren. Somit reduzieren wir den Overhead den Index zu erstellen, wenn das Query zum ersten Mal abgefeuert wird. Dazu editieren wir die Konfiguration für den Stache und fügen unseren Index zur Konfiguration hinzu. Dies fügt zu allen Entries einen neuen Index für feature_image hinzu.

return [
    'stores' => [
        'entries' => [
            'class' => Stores\EntriesStore::class,
            'directory' => base_path('content/collections'),
            'indexes' => [
                'feature_image',
            ]
        ],
    ],
]

Stache managen

Wenn eine Seite produktiv ist, dann brauchen wir Wege den Stache zu managen. Wir wollen ihn löschen, ihn neu bauen und generell die Kontrolle darüber haben. Den Stache zu löschen oder neu zu bauen macht nur Sinn, wenn wir die Dateien in Statamic von Hand anpassen. Änderungen über das Control Panel werden automatisch im Stache abgebildet. Für die lokale Entwickelung steht uns zusätzlich der Watcher zur Verfügung. Wir können diesen durch die .env variable STATAMIC_STACHE_WATCHER oder direkt im /config/statamic/stache.php aktivieeren.

Der Watcher überwacht alle Dateien im Pfade /content auf Änderungen und invaldiert den Cache entsprechend. Wenn wir lokal entwickeln, dann müssen wir uns um den Stache keine Sorgen machen. Durch den Watcher wird er automatisch aktualisiert.

Produktiv möchten wir dies jedoch nicht, da der Watcher Overhead mit sich bringt. Wenn wir Änderungen auf unseren produktiven Server pullen, dann müssen wir zwingend unseren Stache aktualisieren. Dies können wir über unsere CLI machen

# löscht den stache und baut ihn neu
php please stache:refresh
# löscht den stache
php please stache:clear
# erzeugt den stache für alle konfigurierten indexes
php please stache:warmup

Alternativ können wir den Stache über das Control Panel löschen. Dazu navigieren wir Uilitites -> Cache Manager und klicken Clear Stache.