Anwendungs-Performance-Engineering
Laravel-Performance & Skalierung
Anwendungen, die unter Last halten - gemessen an der Produktion statt an einem Laptop, an der Ursache behoben und durch Budgets in der CI verteidigt, damit die Zahlen bleiben.
Performance-Arbeit hat ein vorhersehbares Ende. Ein Sprint geht in die Optimierung, die Kurven gehen runter, und anderthalb Monate später sitzen sie genau dort, wo sie angefangen haben. Die Korrekturen waren nicht falsch. Es hielt sie nur nichts an ihrem Platz.
Wie ein Laravel-Performance-Projekt abläuft
Zuerst messen, aus der Produktion. Echter Traffic, in Stichproben, nach Endpunkt getrennt. Ein p95 von 2,4 Sekunden auf der Bestellübersicht ist eine handlungsfähige Aussage; „die App fühlt sich langsam an" ist es nicht, und ein Profil von einer Entwicklermaschine gegen eine befüllte Testdatenbank ebenfalls nicht.
Die Ursache finden, nicht das Symptom. Ein langsamer Endpunkt in Laravel ist fast immer eines von fünf Dingen: die Abfragezahl, eine einzelne Abfrage ohne brauchbaren Index, Arbeit die in eine Queue gehört hätte, ein Aufruf bei Dritten im Anfragepfad, oder das Serialisieren von weit mehr Daten als die Antwort braucht. Wir führen jedes davon auf die Zeile zurück, die es erzeugt hat.
Auf der richtigen Ebene beheben. Arbeit in einen Job verlagern, den Index hinzufügen, den der Abfrageplan tatsächlich will, die Relation vorab laden, eine Collection-Pipeline durch ein Aggregat ersetzen, einen Wert cachen dessen Kosten das Invalidierungsproblem wirklich rechtfertigen. Das sind die Änderungen, die halten.
Das Ergebnis verteidigen. Ein Budget in der CI: eine Zusicherung auf die Abfragezahl der Endpunkte, die zählen, und ein fehlschlagender Build für den Pull Request, der sie überschreitet. Das Budget ist das Ergebnis; die Kurve ist ein Nebeneffekt.
Warum die Kurve zurückkommt
Weil eine Kurve ein Resultat ist und niemand für ein Resultat verantwortlich ist. Ein Budget ist eine Zahl in einem Test, von einer Maschine geprüft, bei jedem Pull Request, und es lässt den Build desjenigen scheitern, der sie überschritten hat, solange er sich noch erinnert, was er geändert hat.
Deshalb brauchen die meisten Kunden uns dafür kein zweites Mal. Ohne Budget zu optimieren heißt, gegen das nächste Quartal zu borgen: dieselbe Relation verliert still ihr Vorab-Laden, derselbe Report bekommt eine weitere Spalte, die sich als Accessor mit einer Abfrage dahinter entpuppt, und der Rückfall kommt in vernünftigen Commits, einer nach dem anderen, von Leuten die es nicht wissen konnten.
Wohin die Zeit in einem Laravel-Request tatsächlich geht
Sortiert danach, wie oft es die Antwort ist:
Die Datenbank. Meist die Abfragezahl statt einer einzelnen Abfrage – weshalb es ein eigenes Projekt ist, wenn es das ganze Problem ist statt eines Teils davon.
Arbeit, die nicht in die Anfrage gehört. Ein gerendertes PDF, ein skaliertes Bild, ein zugestellter Webhook, ein erzeugter Report. Jedes davon gehört in eine Queue, und sie zu verlagern ist meist die größte verfügbare Verbesserung.
Ein Dritter im kritischen Pfad. Eine Adressprüfung, ein Steuerdienst, der Statusaufruf eines Zahlungsdienstleisters. Deren Latenz ist Ihre Latenz, deren Ausfall ist Ihr Ausfall, und beides ist in Ihrem eigenen Profiling nicht sichtbar, bis Sie danach suchen.
Serialisierung. Ein Endpunkt, der zweihundert Felder zurückgibt, weil die Resource als „alles am Modell" geschrieben wurde und Relationen hydriert, um eine Namensliste zu erzeugen.
Cache als Pflaster. Caching ist nicht umsonst – es kauft Geschwindigkeit mit Veralterung, und eine Anwendung mit einem Dutzend unzusammenhängender Cache-Schlüssel und ohne Invalidierungsstrategie hat ein Performance-Problem gegen ein Korrektheitsproblem getauscht.
Skalieren, sobald sie effizient ist
Erst dann ist Infrastruktur die Antwort, und die Reihenfolge zählt: Eine ineffiziente Anwendung horizontal zu skalieren ist eine ineffiziente Anwendung, für die Sie mehrfach zahlen.
Wo es gerechtfertigt ist, besteht die Arbeit aus Read Replicas mit explizit statt global entschiedenem Routing, Sitzungs- und Cache-Speichern die nicht die Hauptdatenbank sind, Queue-Workern die getrennt von der Web-Kapazität skaliert werden weil sie anders scheitern, und einem Deployment das laufende Arbeit nicht fallen lässt. Octane dort, wo die Messung sagt, dass der Framework-Boot tatsächlich ein nennenswerter Anteil der Anfrage ist – was seltener ist, als sein Ruf nahelegt.
Wo Laravels eigene Bequemlichkeiten messbar kosten
In dem Framework schreibt es sich angenehm, und eine Handvoll der Dinge, die das angenehm machen, haben Kosten, die erst bei Menge auftreten. Nichts davon ist ein Argument gegen ihren Einsatz - es ist das Wissen, wo man hinsieht, wenn die Zahlen kippen.
Eine Collection, wo eine Query hingehörte. Model::all() mit
anschließendem Filter lädt jede Zeile in den Speicher, um die meisten zu
verwerfen. Bei zehntausend Zeilen ist das ein langsamer Endpunkt, bei einer
Million wird der Prozess abgeschossen. chunk und cursor existieren für die
Fälle, die wirklich jede Zeile brauchen.
Ein Accessor mit einer Query dahinter. Ein berechnetes Attribut, das nach einer Relation greift, ist im Modell unsichtbar und in einer Schleife verheerend - eine Liste von zweihundert Zeilen, die es je einmal aufruft, sind zweihundert Queries, die kein Eager Load behebt, weil die Query im Accessor steckt und nicht in der Beziehung.
Model-Events in einer Massenoperation. update() auf dem Query Builder
überspringt Events vollständig, was ein Fehler ist, wenn etwas davon abhing;
iterieren und save() aufrufen löst alle aus, was das richtige Verhalten ist
und zweitausend Queries. Bewusst zu wählen und aufzuschreiben, was diese
Operation braucht, ist die ganze Lösung.
Alles selektieren. with('orders') auf einer Liste, die eine Anzahl
anzeigt, lädt jede Order, um sie zu zählen. Ein withCount, oder ein Select der
Spalten, die die View nutzt, macht aus einem Payload in Megabyte einen in
Kilobyte - und die Serialisierung von Daten, die nichts anzeigt, ist ein
überraschend großer Anteil einer langsamen Antwort.
Der Laravel-Report, der schnell sein muss und nicht gecacht werden kann
Manche Zahlen dürfen nicht veraltet sein und können nicht auf Abruf berechnet werden, und diese Kombination ist der Punkt, an dem die meisten Dashboards umfallen.
Die übliche Form ist ein Screen, der über mehrere große Tabellen aggregiert, morgens von zwanzig Leuten geöffnet wird und widerspiegeln muss, was in der Nacht passiert ist. Pro Request zu rechnen heißt eine dreißigsekündige Query, zwanzigmal ausgeführt. Eine Stunde zu cachen heißt, dass jemand eine Entscheidung auf einer falschen Zahl trifft, was schlimmer ist als langsam.
Was funktioniert, ist Voraggregation in eine eigene Tabelle, inkrementell gepflegt statt neu berechnet. Die Summen jeder Periode werden einmal geschrieben, wenn die Periode abgeschlossen ist, die laufende Periode wird aus einem viel kleineren Bereich abgeleitet, und der Screen liest Zeilen statt Historie zu scannen. Die teure Arbeit passiert planmäßig, unbeobachtet, und das Dashboard wird ein Lookup.
Inkrementell ist der Teil mit Urteilsvermögen, denn ein spät eintreffender Datensatz muss eine schon geschriebene Summe korrigieren können. Das heißt, das Aggregat weiß, welche Eingaben es berücksichtigt hat, und kann für eine Periode neu abgeleitet werden statt nur ergänzt - dieselbe Anordnung, die eine Korrektur erklärbar macht statt rätselhaft.
Eine Read-Replica hilft bei der restlichen Last und bringt ihren eigenen Vorbehalt mit, der ausgesprochen werden sollte: Replikationsverzögerung heißt, eine von einer Replica gelesene Zahl kann Sekunden hinter dem Schreibvorgang liegen, der sie erzeugt hat. Für einen Report ist das meist in Ordnung. Für den Screen, auf dem jemand direkt nach dem Speichern landet, erzeugt es einen Fehlerbericht über verschwundene Daten.
Was Sie aus der Laravel-Performance- und Skalierungsarbeit bekommen
Ein priorisiertes Befunddokument, in dem jedes Problem auf seine Ursache zurückgeführt und nach Aufwand bemessen ist, die umgesetzten Korrekturen als prüfbare Pull Requests, die Konfiguration des CI-Budgets, und einen Vorher-Nachher-Vergleich gegen echten Traffic, sobald die Änderung lange genug live war, um etwas zu bedeuten.
Wo sich die Decke als die Architektur selbst herausstellt – was vorkommt – sagen die Befunde das und stellen eine Zahl für die Alternative daneben. Das ist ein Gespräch für Woche eins und nicht für die Abschlusszusammenfassung.
