Zum Inhalt springen

Wenn Caching eine Laravel-Anwendung langsamer macht

Caching ist das Erste, wonach gegriffen wird, und das Letzte, was gemessen wird. Vier Muster, die Latenz hinzufügen statt sie zu entfernen, und die Frage, die vor jedem davon steht.

4 Min. Lesezeit

Jede langsame Anwendung bekommt irgendwann einen Cache. Es ist der Eingriff mit der besten Geschichte: Die Arbeit ist teuer, also mache sie einmal und behalte die Antwort. Oft funktioniert das, und die Seite, die zwei Sekunden brauchte, braucht jetzt zweihundert Millisekunden.

Oft genug, um darüber zu schreiben, funktioniert es nicht. Hier sind die Formen, die es dann annimmt.

Ein Cache-Aufruf pro Zeile

Die häufigste Variante und die am schwersten zu sehende, weil jedes einzelne Stück davon korrekt ist.

foreach ($orders as $order) {
    $rate = Cache::remember("fx.{$order->currency}", 3600, fn () =>
        $this->rates->for($order->currency)
    );
}

Jeder Aufruf ist eine Millisekunde. Es gibt vierhundert Bestellungen, also verbringt die Seite vierhundert Millisekunden damit, einem schnellen System wiederholt dieselbe Handvoll Fragen zu stellen. Die ursprüngliche Abfrage, die ersetzt wurde, lief einmal und brauchte zwölf.

Der Cache hat hier nicht versagt. Er wurde in eine Schleife gesetzt, was eine teure Operation in hunderte billige verwandelt und sie dann aufsummiert. Cachen Sie die Sammlung statt des Einzelstücks, oder lesen Sie die Menge der Unterschiedlichen einmal, bevor die Schleife beginnt.

Etwas cachen, das billiger ist als die Suche danach

Ein indizierter Select über den Primärschlüssel einer warmen Tabelle liegt deutlich unter einer Millisekunde. Ein Redis-Roundtrip über das Netz ist vergleichbar, manchmal langsamer, und jetzt betreiben Sie zwei Systeme, wo eines gereicht hätte – plus die Invalidierung.

Das gehört deutlich gesagt, weil hier der meiste Aufwand für den geringsten Ertrag betrieben wird. Bevor Sie irgendetwas cachen, messen Sie, was Sie gleich cachen wollen. Liegt die Antwort im einstelligen Millisekundenbereich, ist der Cache mit ziemlicher Sicherheit nicht die Verbesserung, die Sie suchen.

Das Stampede

Ein Schlüssel mit einem Report, dessen Aufbau vier Sekunden dauert, läuft um Mitternacht ab, auf einer Seite mit Traffic um Mitternacht. Jede Anfrage, die in den nächsten vier Sekunden ankommt, findet den Schlüssel leer und beginnt ihn aufzubauen. Nichts wird aus dem Cache bedient, die Datenbank führt dieselbe schwere Abfrage hundertfach parallel aus, und der Ausfall wird vom Cache verursacht statt von ihm verhindert.

Laravel liefert die Antwort mit:

$report = Cache::lock('report:monthly', 10)->block(5, function () {
    return Cache::remember('report:monthly', 3600, fn () => $this->build());
});

Eine Anfrage baut neu auf; die übrigen warten und lesen dann, was sie geschrieben hat. Die Alternative – und die bessere, wo Veralterung hinnehmbar ist – besteht darin, den Schlüssel unter Last gar nicht erst ablaufen zu lassen: Aktualisieren Sie ihn aus einer geplanten Aufgabe, damit Lesende immer etwas vorfinden.

Invalidierung, die mehr kostet als die Lesevorgänge

Ein Cache ist wert, was er spart, minus dem, was es kostet ihn korrekt zu halten. Binden Sie einen Schlüssel an die Aktualisierungen eines Modells, und ein Massenimport über fünfzigtausend Zeilen feuert fünfzigtausend Invalidierungen, jede davon ein Schreibvorgang im Cache-Speicher. Die Lesevorgänge sparten je acht Millisekunden. Der Import dauert jetzt vier Minuten länger.

Dieselbe Falle steckt in zu breiten Tags. Ein Tag über alle Schlüssel eines Mandanten ist bequem, bis das Leeren einen großen Schlüsselraum scannen muss – und ab da ist die Invalidierung der langsame Pfad, und sie läuft bei jedem Schreibvorgang.

Die Frage, die zuerst kommt

Nicht was sollten wir cachen, sondern warum ist das langsam.

Ein Cache vor einem N+1 macht das N+1 billiger in der Ausführung, und es wird immer noch da sein, und es wird immer noch laufen – jetzt mit einem zweiten System und einem Korrektheitsproblem im Anhang. Ein Cache vor einem fehlenden Index ist eine Art, den Index nicht hinzuzufügen. Beides ist als Notbehelf legitim, aber beides sollte als solcher aufgeschrieben werden, denn ein Notbehelf, den niemand aufgeschrieben hat, ist im Folgejahr Architektur.

Wo die Antwort wirklich lautet, dass die Arbeit teuer und korrekt ist und ohnehin passieren muss – ein Aufruf bei Dritten mit Ratenbegrenzung, ein Aggregat über Millionen Zeilen, ein gerendertes Dokument – ist Caching genau richtig. Das ist eine engere Menge an Fällen, als der Reflex nahelegt.

Was zu messen ist

Drei Zahlen, alle billig zu bekommen und selten angesehen:

Trefferquote je Schlüsselpräfix. Unter achtzig Prozent frisst der Fehltreffer die Gewinne. Unter fünfzig zahlen Sie für einen Cache, den Sie faktisch nicht haben.

Cache-Aufrufe pro Anfrage. Einer ist ein Cache. Vierzig sind eine Schleife, die Sie noch nicht bemerkt haben.

Die Seite mit abgeschaltetem Cache. Ist der Unterschied klein, ist der Cache Komplexität ohne Gegenwert – und ihn zu löschen ist eine Performance-Verbesserung in dem einzigen Sinn, der zählt: die Anzahl der Dinge, die kaputtgehen können.

Oft war die Abfrage darunter von Anfang an das Problem, und N+1 ist die übliche Form davon. Geht es um den gesamten Request-Pfad und nicht um eine Abfrage, beginnt Performance-Arbeit mit einer Messung. Der Cache kommt später, oder er kommt gar nicht.

Verwandte Fragen

Ist Redis nicht schnell genug, damit sich das erledigt?
Ein Redis-Zugriff ist ein Netzwerk-Roundtrip: schnell, aber nicht umsonst, und die Kosten fallen pro Aufruf an statt pro Seite. Ersetzen Sie eine Abfrage von 8 ms durch vierzig Cache-Zugriffe von je 0,4 ms, und Sie haben die Seite langsamer gemacht, während jede Grafik sagt, der Cache sei gesund.
Woran erkenne ich, ob ein Cache sich lohnt?
Messen Sie die Seite mit und ohne ihn, auf produktionsähnlichen Daten, und sehen Sie sich die Trefferquote an. Ein Cache unter etwa achtzig Prozent Treffern zahlt die Kosten eines Fehltreffers meist oft genug, um die Gewinne aufzuheben, und einer unter fünfzig kostet Sie schlicht Geld.
Was ist ein Cache Stampede?
Viele Anfragen, die denselben Schlüssel im selben Moment abgelaufen vorfinden und ihn alle gleichzeitig neu berechnen. Die teure Abfrage, die Sie gecacht haben, um sie seltener auszuführen, läuft nun hundertmal in einer Sekunde - unter genau der Last, die sie teuer gemacht hat.
Lohnen sich Cache-Tags?
Sie sind der sauberste Weg, eine Gruppe von Schlüsseln zu invalidieren, und brauchen einen Speicher, der sie unterstützt - Redis oder Memcached, nicht file oder database. Der Preis ist, dass das Leeren eines Tags scannt, sodass ein Tag über sehr viele Schlüssel die Invalidierung zur langsamen Operation macht statt des Lesens.

← Zurück zu allen Artikeln

Anrufen+1 848 272 7583WhatsApp+90 850 308 5436E-Mailinfo@codefacture.comKontaktseite