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!
Toller Beitrag – ich werde ihn sicher für zukünftige Projekte verwenden und muss vielleicht zu ein paar alten zurückkehren!
8:30 Uhr morgens und ich habe bereits etwas Neues gelernt.
Gibt es eine Möglichkeit, den Cache zu „leeren“, ohne die Funktion bearbeiten zu müssen, falls Code-Updates/Korrekturen sofort angewendet werden sollen?
Beste Grüße
Sie müssten etwas einhaken, um delete_transient() aufzurufen -> http://codex.wordpress.org/Function_Reference/delete_transient
Ich richte gerne einen Hook ein, der auf einen angemeldeten Benutzer und einen in der URL bereitgestellten Query-String wartet. Etwas wie example.com/meine-seite/?clear-the-fragment-cache
Ryan hat diese Funktionalität fast schon. Wenn Sie sie etwas umdrehen, können Sie den Transienten aktualisieren, wann immer die Seite von einem angemeldeten Benutzer geladen wird.
Um den gesamten Cache tatsächlich zu löschen, damit Sie sich nicht anmelden und jede der geänderten Seiten neu laden müssen, müssten Sie alle Transienten finden und löschen, die mit
fragment_cache_oder einem anderen Präfix beginnen.@Julian Mallett: Sie möchten vielleicht kein Fragment für angemeldete Benutzer cachen, wenn es unterschiedliche Inhalte hat, wenn Sie angemeldet sind.
Dies wäre leicht lösbar, indem ein weiterer Parameter zur Funktion hinzugefügt wird, der angibt, ob das Caching für angemeldete Benutzer zugelassen werden soll oder nicht.
Das Leeren des Caches ist der Grund, warum ich einen Filter an die Zeile angehängt habe, an der die $key-Variable erstellt wird. Ich hänge eine zufällig generierte Zahl an den Schlüssel an und habe dann einen Knopf in der Admin-Oberfläche des Webmasters, der „Cache leeren“ heißt. Indem sie darauf klicken, generieren sie die zufällig generierte Zahl neu. Die alten Transienten werden beim Ablaufen gelöscht. Das Löschen des Transienten funktioniert, aber das würde bedeuten, den Überblick zu behalten, welche Transienten Sie erstellen und diese oder etwas anderes Raffiniertes löschen. Ich benutze einfach die Zufallszahl, weil sie schnell und einfach ist.
Ich habe auch ein paar Zeilen drin, die die gesamte Funktion umgehen, wenn der aktuelle Benutzer angemeldet ist. Ich entwickle normalerweise, während ich in WordPress angemeldet bin. Deshalb ist das da. Es ist einfach genug, diese Zeilen zu entfernen, wenn sie nicht benötigt werden.
Toller Beitrag, ich weiß, dass ich diesen sicher verwenden werde! **Danke** für diesen Tipp.
Toller Beitrag! Denken Sie jedoch daran… Caching sollte die Optimierung nicht ersetzen! Meiner Meinung nach sollte das Caching angewendet werden, wenn Sie absolut sicher sind, dass der Code, die Abfragen und die HTTP-Anfragen so schlank wie möglich sind. Wenn Sie diese haben und immer noch nicht mit der Gesamt-Ausführungszeit zufrieden sind, sollten Sie Caching anwenden. Andernfalls fühlt es sich an, als würde man seine schmutzige Wäsche unter den Teppich kehren.
Stratos, ich verstehe Ihren Punkt, aber ich denke nicht, dass Sie das Caching nach der… Optimierung anwenden sollten. Ich denke, auf Websites mit sehr hohem Traffic sollten Sie BEIDES gleichzeitig und auf aggressive Weise anwenden. In diesen Fällen sparen diese zusätzlichen Millisekunden viel Geld bei Bandbreite und Ressourcen, und denken Sie auch daran, dass Google Seiten nach ihrer Lade-Geschwindigkeit einstuft. Ich habe mit Websites bei einem der größten Zeitschriftenverlage in den USA gearbeitet, und auf diesen Websites macht eine kleine Änderung an einem Miniaturbild einen Unterschied. Also müssen wir optimieren UND so viel wie möglich cachen.
Ausgezeichneter Beitrag, aber eine Sache… die Funktion get_transient gibt boolean false zurück, wenn der Transient abgelaufen ist oder nicht existiert.
Also sollte es sein
anstelle von
Beides funktioniert. Sie könnten sogar einfach
Siehe auch: PHP-Typvergleichstabellen
Codeforest hat Recht.
Stellen Sie sich vor:
set_transient( 'hardlifting', 0, DAY_IN_SECONDS );.Ich möchte, dass diese `0` auch zwischengespeichert wird, und deshalb ist ein strikter Vergleich mit `false` erforderlich.
Sie haben so Recht. Ich habe einen Gist für dieses Code-Snippet und habe ihn basierend auf Ihrem Rat aktualisiert. Danke!
if ($output === false)
wäre dann richtig, wenn Sie die Reihenfolge umkehren, haben Sie schlecht lesbaren Code
Toller Beitrag, ist das noch relevant, wenn Plugins wie W3 Total Cache verwendet werden? Oder vergleiche ich Äpfel und Ninjas?
Es hängt alles von Ihrer spezifischen Situation ab, aber Sie möchten immer noch etwas wie W3 Total Cache oder WP Super Cache verwenden, da dies die gesamte Seite und nicht nur einen Teil der Seite zwischenspeichert.
Ich denke schon. W3TC generiert HTML-Versionen von Seiten, und wenn die HTML-Version geliefert wird, wird Ihr PHP-Code nicht ausgeführt. Also nichts zu befürchten.
Ich denke, dieser Fragment-Cache ist am besten für Widgets geeignet.
Das war sehr hilfreich. Ich werde in Zukunft viel mehr darauf achten, dies zu verwenden.
KLUG, KLUG, KLUG …… einziges Problem ………………… Ich lese es JETZT gerade, habe gerade eine 4-monatige Arbeit abgeschlossen, um Seitenfragmente auf separate Weise/Funktionen zu cachen, diese Lösung hätte uns tonnenweise Zeit gespart, danke fürs Teilen, liebe diese CSS TRICK Community, danke fürs Dasein, ihr macht Entwicklern das Leben leichter.
Nur eine Frage, ob jemand helfen kann: Ist es besser, „Transienten“ zum Caching zu verwenden oder direkt in eine Datei, die später inkludiert wird? Ich erwähne das, weil unsere Seiten mit hohem Traffic verrückt nach Datenbankanfragen werden und sobald wir mit dem Caching in Dateien beginnen, hörten die MySQL-Probleme auf, und Transienten verwenden immer noch eine Tabelle in der Datenbank.
Absolut, ich denke, das Caching von Dateien, um MySQL-Probleme zu vermeiden, wird keinen großen Nutzen bringen als die Verwendung von „Transienten“-Caching.
Wenn ich dies verwende, um die Ergebnisse einer get_posts()-Schleife auf einer single.php-Seite zu cachen, gibt es den Titel und/oder das Beitragsbild für den Hauptartikel auf der Seite zurück. Irgendwelche Ideen warum?
Was passiert, ist, dass derselbe Codeblock unterschiedliche Inhalte erzeugt, basierend auf der Seite, die angezeigt wird. Es ist völlig in Ordnung, den Fragment-Cache in dieser Situation zu verwenden. Sie benötigen lediglich eine Möglichkeit, zwischen den Seiten zu unterscheiden. Wenn ich mit einer einzelnen Seite arbeite, hänge ich manchmal einfach die Beitrags-ID an den Schlüssel des Fragment-Caches. Hier ist ein Beispiel
Nein, die get_posts()-Funktion ist nicht von der Seite abhängig, sie liefert dasselbe für jede Seite, auf der sie angezeigt wird. Irgendwelche anderen Ideen?
Ich verstehe nicht, warum Sie `is_user_logged_in()` prüfen… Ich gehe davon aus, dass Sie dem angemeldeten Benutzer nicht den zwischengespeicherten Teil servieren wollen, weil es für eine angemeldete Person etwas anderes geben könnte? Selbst für einen angemeldeten Benutzer gibt es genügend Teile, die zwischengespeichert werden könnten, vielleicht könnte das ein Parameter für die Funktion sein. Außerdem dachte ich, es wäre gut, gegen meine IP (die Entwickler-IP) zu prüfen und mir nie zwischengespeicherte Teile zu servieren, weil das Wahnsinn verursachen könnte. Darüber hinaus wäre es gut, so etwas wie `if($_get['recache'])destroyThisChunk($thechunk)` zu haben. Außerdem könnte es gut sein, Caches bei save_post zu zerstören.
Ich mag diese Idee sehr, ich werde sie an mehreren Stellen implementieren.
Es prüft, ob der Benutzer angemeldet ist, um zu vermeiden, dass der zwischengespeicherte Inhalt einem angemeldeten Benutzer angezeigt wird. Ich habe dies inzwischen in meinem Quell-Gist für dieses Snippet entfernt. Ihre Idee, nach einem URL-Parameter zu suchen, um den Cache zu löschen, ist großartig. Das wäre ein toller Fork.
Großartige Sache. Ich hatte viele Probleme mit einer WP-Installation auf einer Amazon Micro EC2-Instanz. Caching ist der Schlüssel.
Seien Sie nur vorsichtig bei Dingen wie Cache-Stampeden, denn wenn der eigentliche Datengenerierungsprozess langsam ist, kann das leicht zu Problemen führen (insbesondere in Szenarien mit hohem Traffic) und Sie müssen möglicherweise Ihren Ansatz überdenken.
Ich bin noch neu in PHP… aber ich mag WordPress, was Sie hier über Caching schreiben, ist großartig, ich werde dem später viel Aufmerksamkeit schenken… ich mag auch den Kommentarbereich Ihrer Website… etwas, das es wert ist, nachgeahmt zu werden… vielen Dank fürs Teilen
Toller Artikel. Das war sehr hilfreich. Danke!
Zack Tollman hat auch einen schönen Artikel über Fragment-Caching: http://tollmanz.com/partial-page-templating-in-wordpress/
Gute Arbeit, Ryan,
Wenn Sie die Transients API wirklich auf die nächste Stufe heben wollen, würde ich vorschlagen, ein Object-Caching-Backend wie APC zu verwenden, um Datenbankaufrufe an die Options-Tabelle komplett zu umgehen.
Die Datei object-cache.php in wp-content ist der Schlüssel dazu.
Das war sehr hilfreich. Und das WordPress Fragment Caching Revisited hat mir ein paar Ideen gegeben.
Diese Art von Caching nutze ich bei der Transkription. Wenn Sie mehr Ideen benötigen, schauen Sie hier nach. http://www.bengali-transcription.com/About-us.php
Das ist genau das, wonach ich gesucht habe, danke Ryan.
Könnten Sie bitte genauer erläutern, wie Sie den Schlüssel mit der Zufallszahl aktualisieren? Ich habe eine „Cache leeren“-Seite erstellt, die eine Funktion ausführt, aber ich kann nicht herausfinden, wie ich dies mit dem Schlüssel innerhalb der
fragment_cache()-Funktion verknüpfen kann.Ich hänge eine Funktion an „fragment_cache_prefix“ wie folgt an.
Dies holt eine Zahl, die zuvor in den WordPress-Optionen gespeichert wurde, und der Filter in der Fragment-Cache-Funktion fügt sie dem Schlüssel hinzu, der zum Speichern des Transienten verwendet wird.
Anderswo können Sie die folgende Funktion an einen Button im Backend hängen oder an die Aktion „save_post“, um den Cache zu leeren, wenn Webmaster ihre Inhalte speichern.
Es gibt andere Wege, dies zu tun, aber die Idee ist, dass, wenn sich die Zufallszahl ändert, alle zuvor gespeicherten Transienten aufgegeben werden. Sie laufen ab und werden später aus der Options-Tabelle gelöscht, so dass es nicht notwendig ist, sie manuell zu löschen.
Dies ist Beispielcode, also sollten Sie die Namen ändern, um die Unschuldigen zu schützen, wenn Sie ihn implementieren. ;)
Das ist interessant. Ich habe gerade diesen Artikel gelesen, der beschreibt, wie Transienten vor Ablauf ihrer Zeit gelöscht werden können und dass die Ablaufzeit ein Maximum und keine Mindestzeit ist. Gedanken dazu?
Außerdem erwähnen Sie Mark Jaquiths Artikel über Fragment-Caching, den ich gerade angeschaut und ausprobiert habe. Ich frage mich, ob Sie etwas mehr darüber sprechen können, wie sich Fragment-Caching im Vergleich zur Verwendung von Transienten unterscheidet.
Soweit ich es verstehe: Marks Methode, die WP_Object_Cache verwendet, speichert die Daten nicht in einem persistenten Cache, während Transienten persistent sind.
Ich untersuche dies heutzutage weiter, weil ich gerne Advanced Custom Fields verwende, um die Admin-Oberfläche meiner WP-Sites mit den benötigten Eingabefeldern stark anzupassen.
Aber ich habe den Eindruck, dass zu viele dieser benutzerdefinierten Felder zusammen mit der Logik, die benötigt wird, um sie zu durchlaufen und ihre Daten auf dem Frontend auszugeben, im Hinblick auf die Anzahl der Datenbankabfragen teuer wird. Es scheint also, dass die Verwendung von Caching wie diesem, um Codeblöcke zu speichern, die ACF-Felddaten ausgeben, hilfreich sein könnte.