WordPress Fragment Caching Revisited

Avatar of Ryan Burnette
Ryan Burnette am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der folgende Text ist ein Gastbeitrag von Ryan Burnette. Wie Sie unten lesen werden, arbeitete Ryan an einer WordPress-Website, die ein Plugin nutzte, das die Instagram-API verwendete, um Fotos herunterzuladen. Er nutzte es auf eine etwas unkonventionelle Weise, die zu vielen Anfragen und einer sehr langsamen Website führte. Bei der Suche nach verschiedenen Lösungen stieß er auf Fragment-Caching. Aber leider waren einige der Informationen, die er fand, veraltet, also hat er sie, wie ein guter Entwickler, aktualisiert. Hier ist die Hintergrundgeschichte und die Reise.

Wir alle wissen, dass Web-Performance wichtig ist. Für Entwickler, die benutzerdefinierte WordPress-Themes erstellen, steht sie jedoch ganz unten auf der Prioritätenliste, wenn es um das eigentliche Schreiben von Code geht. Der Code, der Elemente auf der Seite rendert, wird normalerweise auf die einfachste, benutzerfreundlichste Weise mit den verfügbaren Funktionen geschrieben. Dies führt zu Code, der einfach erstellt, gelesen und gewartet werden kann. Es führt auch zu Elementen mit einem sehr ineffizienten Rendering-Prozess mit unnötigen Schleifen und Datenbankabfragen.

Ein paar zusätzliche Millisekunden summieren sich wirklich. Kombinieren Sie dies mit zunehmendem Website-Traffic, und es können ernsthafte Performance-Probleme auftreten.

Viele sehr kluge Leute haben sich bereits mit diesem Problem auseinandergesetzt. Die WordPress-Community hat einige großartige Caching-Plugins hervorgebracht. W3 Total Cache ist eines davon. Ich liebe sie und nutze sie häufig, aber manchmal brauche ich nicht all diese Leistung. Ich möchte vielleicht Konfigurationen vermeiden oder Elemente haben, die nicht Cache-freundlich sind. Es ist auch schön, Plugins auf ein Minimum zu beschränken, um zukünftige Wartungsprobleme zu vermeiden.

Dies führte dazu, dass ich einen anderen Ansatz verfolgte. Ich wollte mit einer sehr kleinen Menge Code nur wenige Elemente auf der Seite cachen, die für das Rendering bei jedem Aufruf zu umständlich sind.

Fragment Caching

Wenn eine WordPress-Seite geladen wird, wird PHP verarbeitet und die MySQL-Datenbank abgefragt. Manchmal macht ein Codeblock viele Abfragen und braucht eine Weile, um ausgeführt zu werden. Fragment-Caching speichert die Ausgabe eines Codeblocks für eine vorbestimmte Zeit. Wenn der Code ausgeführt wird und die Zeitspanne noch nicht abgelaufen ist, wird der Block ignoriert und die gespeicherte Ausgabe zurückgegeben und auf der Seite ausgegeben.

Fragment-Caching ist nichts Neues. Der WordPress Core Developer Marc Jaquith schrieb über Fragment-Caching. Ich fand später einen Gist, der Jaquiths Klasse in eine Funktion vereinfachte. Ich habe diesen geforkt und von dort aus modifiziert.

In WordPress-Versionen vor 2.5 konnten WP_Cache-Objekte wie in Jaquiths Beispiel für persistentes Caching verwendet werden, also Caching, das länger als ein Seitenaufruf dauert. Die Transients API kann persistente Datenbankobjekte mit einer praktischen Ablauf Funktion erstellen. Mein Fragment-Caching-Snippet verwendet diese Methode, um Fragmente zu speichern.

Hier sind ein paar Zeilen Code, die in die functions.php-Datei aufgenommen werden können und es ermöglichen, jede Ausgabe als Fragment zu cachen. Hier ist der Code.

function fragment_cache($key, $ttl, $function) {
  if ( is_user_logged_in() ) {
    call_user_func($function);
    return;
  }
  $key = apply_filters('fragment_cache_prefix','fragment_cache_').$key;
  $output = get_transient($key);
  if ( empty($output) ) {
    ob_start();
    call_user_func($function);
    $output = ob_get_clean();
    set_transient($key, $output, $ttl);
  }
  echo $output;
}

Die Funktion nimmt drei Argumente an

  • Schlüssel: Ein einfacher String, der das Fragment identifiziert. Beachten Sie, dass die Funktion ein Präfix hinzufügt, um Kollisionen mit anderen Transienten zu vermeiden. Sie können das Präfix ändern, indem Sie die Funktion bearbeiten oder einen Filter hinzufügen, der dem Tag 'fragmentcacheprefix' entspricht.

  • Lebensdauer: Eine Zeitspanne in Sekunden, für die der Cache gültig ist. Ich verwende normalerweise Zeitkonstanten. Zum Beispiel ist DAYINSECONDS 86400, die Anzahl der Sekunden in einem Tag. Das hilft denjenigen von uns, die zu faul für einfache Mathematik sind.

  • Funktion: Die Funktion, die die Ausgabe erzeugt. Das kann alles sein, wie die Beispiele in diesem Beitrag zeigen.

Anwendungsbeispiele

Die Verwendung von Fragment-Caching ist so einfach wie das Einpacken von HTML und PHP in eine Funktion.

Hier ist ein Beispielcode, den ein Entwickler auf einer WordPress-Site oder Anwendung schreiben könnte.

<p>Here's some HTML.</p>

<?php
// Here's some PHP
$args = array(
  'post_type' => 'my_data',
  'posts_per_page' => -1
);
$posts = get_posts($args);
foreach ( $posts as $p ) {
  echo '<pre>';
  echo get_post_meta($p,'some_meta',true);
  echo '</pre>';
}?>

<p>The PHP in this block runs and executes queries with every page load. :(</p>

Hier ist derselbe Code, implementiert mit dem Fragment-Caching-Snippet. Beachten Sie, dass wir HTML und PHP verwenden und dies von der Funktion erfasst und zwischengespeichert wird.

Fassen wir die drei Argumente der Funktion zusammen

  • Ein Tag, der den Cache repräsentiert. Hier ein Tipp. Wenn dieser Code pro Seite variiert, verketten Sie die Beitrags-ID in den Tag, um für jede Seite einen separaten Cache zu erstellen. Dies wäre wichtig, wenn die Hauptschleife per Fragment-Caching zwischengespeichert wird.
  • Das Timeout. Ich verwende normalerweise WordPress-Zeitkonstanten, aber jede Zeitspanne in Sekunden kann verwendet werden.
  • Der Ausgabe-Code selbst. Beachten Sie, dass er in einer Funktion gehalten wird. Diese Funktion wird an die Fragment-Cache-Funktion übergeben. Richtig, Sie können eine Funktion als Argument in PHP übergeben.
<?php
// After
fragment_cache('my_footer', DAY_IN_SECONDS, function() { ?>

<p>Here's some HTML.</p>

<?php
// Here's some PHP
$args = array(
  'post_type' => 'my_data',
  'posts_per_page' => -1
);
$posts = get_posts($args);
foreach ( $posts as $p ) {
  echo '<pre>';
  echo get_post_meta($p,'some_meta',true);
  echo '</pre>';
}
?>

<p>And everything this block outputs will be fragment cached. :)</p>

<?php }); ?>

Beispiele

Hier sind einige Beispiele für Orte, an denen ich meine Datenbank von der Mühe entlaste, ein Element öfter als nötig zu rendern.

Benutzerdefinierte Footer

Der häufigste Ort, an dem ich diese Funktion implementiere, ist ein benutzerdefinierter Footer. Ich erstelle oft einen Footer, der nicht nur WordPress-Menüs enthält, sondern auch Menüs, die ich basierend auf der Funktion get_posts() und zusätzlichen get_post_meta()-Funktionen für jeden iterierten Beitrag generiere. Ich habe viele Fälle festgestellt, in denen das Rendern eines großen Footers 100-200 Millisekunden dauert. Fragment-Caching macht die Ladezeit solcher Elemente irrelevant.

Datentabellen

WordPress gewinnt immer mehr an Popularität als Plattform für die Anwendungsentwicklung. Im Moment ist viel Hype darum. Ob man es mag oder nicht, Leute werden Anwendungen in WordPress entwickeln. Dies führt oft zu Situationen, in denen das, was normalerweise eine Gruppe von Datenbankobjekten wäre, als Beiträge in einem benutzerdefinierten Post-Typ gespeichert wird. Jedes Attribut wird zu einem Teil der Metadaten dieses Beitrags, anstatt zu einem Attribut eines echten Datenbankobjekts. Das Abfragen und Rendern einer auf diese Weise gespeicherten Datentabelle dauert lange. Fragment-Caching kann das Problem lösen.

Peinlich lange Schleifen

Es gibt Tausende von peinlich langen und verschlungenen Schleifen da draußen. Ich habe einige davon geschrieben. Egal, welches ineffiziente Stück Code Sie geschrieben haben, Sie können es in einen Fragment-Cache einfügen und es wird schnell geladen.

Ein Testfall

Ich bin der Webmaster für STUDIOCRIME, eine Website, die Street-Art-Videos aggregiert. Update Dezember 2019: Link entfernt, da die Seite nicht mehr existiert und jetzt Spam ist.

WordPress bietet ein fantastisches, einfaches CMS für unsere Kuratoren, das sie beim Posten und Organisieren von Videoinhalten für die Website nutzen können. Die Video-Sammlungsseiten laden jedes Mal über 80 Beiträge, wenn sie aufgerufen werden. Jede dieser Iterationen fragt auch die Datenbank nach Metadaten ab.

Wir zeigen auch viele Inhalte in der Seitenleiste mit einem Plugin, das Daten von der Instagram-API authentifiziert und abruft. Das Plugin war nicht dafür gedacht, auf die Art und Weise verwendet zu werden, wie wir es verwenden. Jedes Widget instanziiert das Plugin separat. Dies führt zu sehr langen Ladezeiten.

Es war sicherlich schnell und einfach zu erstellen, aber Millisekunden hier und da summierten sich zu einer Seite, die zwischen 1500 und 5000 Millisekunden zum Rendern benötigt. Fünf Sekunden sind eine lange Zeit, wenn man auf das Laden einer Webseite wartet.

Wir haben uns entschieden, kein Caching-Plugin wie W3 Total Cache zu verwenden, da Entscheidungen darüber, wie eine Seite geladen werden soll und Benutzerdaten innerhalb von PHP verfolgt werden, durch das Seiten-Caching nicht ausgeführt werden würden.

Dies bot die perfekte Gelegenheit, Fragment-Caching zu nutzen und die Gewinne zu testen, die durch das Caching von Fragmenten erzielt werden können, die langsam geladen werden.

Ich habe diese Tests mit Apache Bench durchgeführt. Apache Bench führt eine oder mehrere Anfragen gleichzeitig oder nacheinander aus und berichtet über die Zeit, die der Webserver zum Ausliefern der Seiten benötigte. Beachten Sie, dass eine einzelne Anfrage ohne Caching etwa dreimal länger dauerte. Kombinieren Sie dies mit mehreren Anfragen, und die Zeit, um eine Antwort zu erhalten, wird ziemlich hoch, 3 bis 5 Sekunden. Durch Fragment-Caching der langsamen Teile der Website konnten die Zeiten wieder reduziert und die Leistung erzielt werden, die wir unter höherer Last benötigten.

Diese Tests zeigen die Rendering-Zeiten für eine einzelne Seite unter einer gleichzeitigen Last von 10, 100 und 1000 Anfragen.

Apache Bench Test Ohne Caching Mit Caching
10 Anfragen 1426 ms 518 ms
100 Anfragen 3498 ms 658 ms
1000 Anfragen 5116 ms 895 ms

Viel Spaß beim Caching!