Performance: Bewusstsein und Optimierung

Erkunden Sie mehrere Leitprinzipien, die die Performance fördern, und lernen Sie, sie auf Ihren Softwareentwicklungskontext anzuwenden.

Seit mehr als zehn Jahren arbeite ich nun an verschiedenen Kundenprojekten sowie Produkten und hatte dabei mehrfach Gelegenheit, an unterschiedlichen Aspekten der Performance von Webanwendungen zu arbeiten. Oftmals erwarten Kunden von meinem Team und mir, dass wir „Performance implementieren”, aber so funktioniert das natürlich nicht. Es gibt keine Liste mit zehn konkreten Dingen, die jeder tun muss, immer, um die Performance einer Anwendung zu verbessern. Was meiner Meinung nach stattdessen sehr gut funktioniert, ist eine performanceorientierte Denkweise zu entwickeln und eine Reihe von Leitprinzipien und übergeordneten Konzepten zu berücksichtigen, die sich dann in einem bestimmten Kontext in verschiedene konkrete Maßnahmen umsetzen lassen.

Ich habe mich schon immer für die Performance von Software und Systemen interessiert, und so habe ich angefangen, Dinge, die ich hier und da gelernt habe, bei verschiedenen Gelegenheiten weiterzugeben. Vor wenigen Wochen habe ich auf dem WordCamp Niederlande einen Vortrag zu diesem Thema gehalten. (Wer Interesse hat, kann sich die Slides online ansehen.)

In diesem Beitrag werde ich die Kerninhalte des Vortrags wiedergeben. Es gibt zwar Code-Beispiele, die entweder in PHP oder JavaScript geschrieben sind, aber diese dienen nur zur Veranschaulichung. Die Konzepte lassen sich in jeden beliebigen Kontext und jede beliebige Sprache übertragen, aber es ist ohnehin nicht zwingend nötig, den Code zu verstehen oder anzuschauen.

Was ist Performance überhaupt?

Bevor wir uns mit Best Practices und damit zusammenhängenden Überlegungen beschäftigen, ist es essenziell zu verstehen, was wir mit „Performance” meinen. Für viele Menschen bedeutet „Performance” im Zusammenhang mit Webentwicklung Frontend-Performance, Google Lighthouse, PageSpeed Insights oder Core Web Vitals. Das ist zwar nicht per se falsch, aber es ist entweder zu allgemein oder zu spezifisch und fokussiert.

Im Allgemeinen bedeutet das Betrachten von Performance, dass man sich die folgende Frage stellt:

“Wie gut wird eine bestimmte Aufgabe ausgeführt?”

Das Wichtige dabei ist die Definition von „gut”. Dies bedeutet, dass die (wichtigsten) Kriterien und Erwartungen festgelegt werden müssen. Die Performance eines Systems kann sich auf Zeit beziehen, z.B. auf die Verarbeitungszeit (d.h. auf Server-/Rechnerseite) oder die Antwortzeit (d.h. auf der Benutzer-/Verbraucherseite). Oder es könnte bedeuten, die verwendeten Ressourcen oder die Verfügbarkeit des Systems zu betrachten. Oder auch persönliche Vorlieben: „Hat Ihnen der Auftritt dieser Band oder dieser Künstlerin gefallen?” In der Webentwicklung bezieht sich Performance oft auf eine Mischung aus Zeit und Ressourcen.

Leitprinzipien

Schauen wir uns die folgenden Leitprinzipien an. Wenn wir die grundlegenden Konzepte verstehen und diese dann auf einen bestimmten Kontext anwenden, so werden wir eine Performance-Denkweise entwickeln.

  • Arbeit nicht zu früh erledigen.
  • Arbeit nicht immer wiederholen.
  • Keine unnötig komplizierte Arbeit ausüben.
  • Hinterher wieder alles „aufräumen”.
  • Nicht zu früh optimieren.
  • Performance messen.
  • Das richtige Werkzeug für die jeweilige Aufgabe verwenden.

Diese Punkte sind offensichtlich nichts Besonderes. Tatsächlich sind die meisten nichts anderes als Allgemeinwissen und gesunder Menschenverstand.

Wie wäre es, wenn wir uns nun einige Beispiele aus der Praxis ansehen?

Frühzeitig abbrechen

Es macht keinen Sinn, Zeit oder Ressourcen für etwas zu verschwenden, das niemand braucht. Wenn wir an eine Funktion denken, bei der Daten verarbeitet oder eine andere Aktion durchgeführt werden soll, kann es sinnvoll sein, relevante Vorbedingungen zu prüfen und frühzeitig abzubrechen, wenn etwas nicht stimmt. Es geht also um die Frage „Soll ich überhaupt etwas tun?“, und zwar sowohl in Bezug auf den aktuellen Kontext (z.B. Frontend vs. Admin-Seite, REST-API-Anfrage vs. normale Frontend-Anfrage, oder angemeldete Benutzer vs. anonyme Besucher), als auch in Bezug auf die Daten oder die Verarbeitungslogik.

Den Kontext prüfen

Nehmen wir eine PHP-Funktion in einem WordPress-Kontext, die nur für veröffentlichte Beiträge eines bestimmten benutzerdefinierten Beitragstyps ausgeführt werden soll. Hier gibt es mindestens zwei Bedingungen: Die Überprüfung des Beitragstyps, und die Überprüfung des Beitragsstatus. Spielt es eine Rolle, in welcher Reihenfolge wir dies tun? In diesem Beispiel kann es eine Rolle spielen, muss es aber nicht. Dies hängt einzig und allein vom Projektkontext ab. Gibt es mehr veröffentlichte als unveröffentlichte Beiträge? Gibt es nur eine Handvoll Beiträge dieses speziellen benutzerdefinierten Beitragstyps? Wie oft wird diese Funktion und damit die Bedingung ausgeführt?

function myFunction( WP_Post $post ) {

    if ( $post->post_type !== 'special' ) {
        return /* data */;
    }

    if ( $post->post_status !== 'publish' ) {
        return /* data */;
    }

    // Perform actual task...
}

Im Allgemeinen ist dies eine Gelegenheit, eine performanceorientierte Denkweise zu entwickeln. Auch wenn es keinen riesigen Unterschied macht, ob zuerst der Beitragsstatus oder der Beitragstyp geprüft wird, müssen wir uns für eine der beiden Optionen entscheiden. Also können wir zumindest zur Kenntnis nehmen, dass es zwei Möglichkeiten gibt, und angesichts der verfügbaren Informationen entscheiden, welche die „bessere” ist.

Wenn wir eine etwas andere Funktion betrachten, eine, die nur bei veröffentlichten Beiträgen ausgeführt werden soll, aber nur, wenn eine andere Bedingung auf der Grundlage einer externen API zutrifft, sieht die Situation plötzlich anders aus. Es sollte offensichtlich sein, dass es angebracht ist, zuerst den (skalaren und sofort verfügbaren) Beitragstyp eines bestimmten Post-Objekts zu prüfen und erst dann die Anfrage an den Remote-Server zu senden, um auf Daten zu warten, die wir dann weiterverarbeiten.

function myFunction( WP_Post $post ) {

    if ( $post->post_type !== 'special' ) {
        return /* data */;
    }

    if ( ! expensiveCheck() ) {
        return /* data */;
    }

    // Perform actual task...
}

Daten prüfen

Ähnlich verhält es sich, wenn wir Code haben, der verschiedene Arten von Verarbeitungslogik auf Daten in Bezug auf bestimmte Eingabewerte anwendet. Wenn entweder keine Daten zu finden sind oder keine Prozessoren registriert sind, sollte diese Funktion lediglich nach Daten und Prozessoren (oder vielleicht nur nach einem dieser Dinge) suchen und dann aufhören.

function processData( int $postId ) {

    // Get $data from somewhere...
    if ( ! $data ) {
        return;
    }

    // ...
}

Caching

Nachdem im vorigen Abschnitt die Frage „Soll ich überhaupt etwas tun?” beantwortet wurde, ist eine weitere wichtige Frage: „Soll ich diese Sache noch einmal tun?”. Das bedeutet, dass wir uns Gedanken darüber machen müssen, nach welcher Zeit eine bestimmte Aktion erneut ausgeführt werden sollte oder welche Zustands- oder Bedingungsänderung stattgefunden haben muss, damit diese Aktion ein weiteres Mal ausgeführt werden kann. Wir sprechen hier also unter anderem von Caching.

Manche sagen, dass Caching nicht das Richtige ist, um ein schlecht funktionierendes System zu verbessern. Dem stimme ich im Allgemeinen zu. Aber nicht in allen Kontexten. Es gibt verschiedene Gründe zu cachen, es gibt verschiedene Arten und auch Ebenen des Caching, es gibt verschiedene Teile von Daten oder Logik, die man cachen könnte, es gibt Lebensdauer und Invalidierung, es gibt verschiedene Bereiche, in denen gecacht werden kann und so vieles mehr…

Es stimmt, dass ein Cache in manchen Situationen nicht die Grundursache behebt, sondern „nur” ein oder mehrere Symptome behandelt. Caching ist also kein Allheilmittel, und es sollte nie die einzige Maßnahme sein, die man im Hinblick auf Performance ergreift. Aber überhaupt nicht zu cachen, kann ein noch größeres Problem sein.

Caching-Ebenen

Verschiedene Codebasen besitzen verschiedene Ebenen und Bereiche, in denen Caching angewendet werden kann. Die meisten Leute denken beim Thema Caching an einen richtigen, langlebigen Cache. Zum Beispiel ein spezieller Objekt- oder Seiten-Cache, ein Browser-Cookie, localStorage oder eine spezielle Tabelle in der bestehenden Datenbank. In diesen Fällen ist es wichtig, eine korrekte Logik für Lebensdauer und Invalidierung zu haben.

Eine Ebene “tiefer” wäre eine semi-persistente Caching-Schicht, wie die aktuelle PHP- oder MySQL-Session (oder eine andere CLI-Session). Als Nächstes würden wir so etwas wie die aktuelle Anfrage betrachten, also den Lebenszyklus einer einzelnen Interaktion. In PHP wäre die Verwendung einer globalen Variable eine Form des anfragebasierten Caching.

Die nächste Ebene wäre wiederum ein lokaler Cache, der nur einen Teil der aktuellen Anfrage/Interaktion verwendet. Zum Beispiel das Caching von Daten in einer statischen Variable in einer Funktion oder einer Eigenschaft einer Klasseninstanz. Die letzte Ebene ist so etwas wie ein „hyperlokaler” Cache, also der Kontext einer einzelnen, nicht-statischen Variable in einer längeren Funktion. Bezogen auf JavaScript könnte dies sogar eine Variable (const oder let) in einem “Sub-Scope” sein.

Kostenintensive Vorgänge cachen

Wo Caching unbedingt zu empfehlen ist, sind kostenintensive Vorgänge. In diesem Zusammenhang kann der Begriff „kostenintensiv” entweder Zeit oder Ressourcen oder auch beides bedeuten. Wenn ein lokaler Vorgang länger dauert, wenn mit einer Remote-API kommuniziert wird, oder wenn diese API eine Aktion durchführt, die eine Weile dauert, sind dies gute Kandidaten, um Caching in oder um diese Vorgänge einzusetzen.

Je nach konkreter Caching-Implementierung sind verschiedene Dinge zu beachten. Zum Beispiel Lebensdauer, Invalidierung oder Refreshing.

Sich wiederholende Vorgänge cachen

Selbst wenn etwas nicht viel Zeit oder eine größere Menge an Ressourcen in Anspruch nimmt, kann es sinnvoll sein, über Caching nachzudenken, wenn ein bestimmter Teil der Logik immer wieder neu ausgeführt wird. Bei einem Codeblock (der so kurz wie eine einzige Zeile sein kann), der zu einem einzigen Wert führt, der in einer Variablen gespeichert oder auf dem Bildschirm ausgegeben wird, ist es vielleicht nicht ideal, diesen Wert wiederholt zu ermitteln. Stattdessen könnte er gespeichert und dann für jeden späteren Anwendungsfall wiederverwendet werden, für den er erforderlich ist. Dabei kann es sich um die Interaktion mit E/A, einem Netzwerk oder um die Ausführung eines komplexen Algorithmus handeln, aber auch um den einfachen Zugriff auf Daten in einem Array oder Objekt, die drei Ebenen tief definiert sind.

// Cache function call.
$foo = getFoo();

// Cache array/object lookup.
$foo = $item->fooBar['data']->foo;

Allerdings sollte beachtet werden, dass es manchmal Sinn macht, ein Ergebnis erneut abzufragen und nicht ein früheres Ergebnis wiederzuverwenden. Dies kann der Fall sein, wenn sich das Ergebnis geändert haben könnte und der neueste Wert anstelle des vorherigen Wertes verwendet werden soll. Es gibt aber auch Fälle, in denen es jetzt einen neuen Wert gibt, der vorherige aber noch verwendet werden soll oder muss.

Memoisation

Eine besondere Form, einen Ausgabewert für einen gegebenen Satz von Eingabewerten zu cachen, nennt man Memoisation oder Memoisierung. Eine memoisierte Funktion oder ein memoisierter Wert liefert den zuvor gespeicherten Rückgabewert einer (reinen) Funktion für genau die angegebene Menge von Argumenten.

const categoryOptions = useMemo( () => {
     return [
         {
             label: '',
             value: 0,
         },
         ...(
             categories.map( ( { id, name } ) => ( {
                 label: unescape( name ),
                 value: Number( id ),
             } ) )
         ),
     ];
 }, [ categories ] );

Während Memoisation ein allgemeines Konzept ist, das auf viele Programmiersprachen und Kontexte anwendbar ist, ist es für WordPress eher durch JavaScript bekannt geworden, zum Beispiel in Form der Funktionen React.memo oder React.useMemo, oder memoize in Lodash.

Funktionsausführung kontrollieren

Einige Implementierungen von auslöserbasierten Aktionen sind “naiv” und nicht besonders leistungsfähig. Beispiele hierfür sind die Reaktion auf die Bewegung des Cursors durch Benutzer/Besucher oder eine Eingabe in ein Formularfeld. Oftmals ist es nicht sinnvoll, auf jeden neuen Auslöser eine Aktion auszuführen. Stattdessen sollte lieber nur auf eine ausgewählte Anzahl von Auslösern reagiert werden oder eine bestimmte Schwelle oder Abklingzeit zwischen den Aktionen eingehalten werden.

Drosseln

Eine Aktion nur in einem angemessenen Intervall auszuführen, anstatt auf jede Benutzeraktion sofort zu reagieren, wird oft als Drosselung (engl. throttling) bezeichnet. Anstatt den auszuführenden Code an den Benutzerauslöser zu koppeln, wird eine spezielle Funktion eingesetzt, die den Code einschließt und intern festhält, wann der Code zuletzt ausgeführt wurde. Spätere Anfragen zur erneuten Ausführung des Codes werden verworfen, solange ein vordefinierter Schwellenwert noch nicht erreicht ist. Der Versuch, den Code auszuführen, nachdem der Schwellenwert überschritten wurde, funktioniert problemlos, und der interne Zeitstempel wird aktualisiert.

// Execute callback for every single position change.
window.addEventListener( 'dragover', onDragOver );

// Execute callback every 200ms, at maximum.
const throttledHandler = throttle( onDragOver, 200 );
window.addEventListener( 'dragover', throttledHandler );

Eine gedrosselte Funktion wird bei der allerersten Anfrage sofort ausgeführt. Für jedes Zeitintervall wird sie jedoch nur ein einziges Mal oder gar nicht ausgeführt, jedoch nie mehr als einmal. Eine bekannte JavaScript-Implementierung ist die Funktion throttle in Lodash.

Entprellen

Das Gegenstück zur Drosselung ist das Entprellen (engl. debouncing). Hier umhüllen wir unseren eigentlichen Code mit einer weiteren benutzerdefinierten Funktion, die auf weitere Anfragen „wartet”. Sobald ein vordefiniertes Intervall seit der letzten Anfrage verstrichen ist, wird der Code ausgeführt. Ein wichtiger Anwendungsfall für das Entprellen ist die Reaktion auf Benutzereingaben, z.B. durch die Veränderung vorhandener Daten oder die Abfrage einer Remote-API und das Rendern der erhaltenen Ergebnisse. Es macht keinen Sinn, Daten für „p”, dann „pr” und dann „pro” anzufordern, bis der Benutzer mit dem Schreiben von „programming” fertig ist, vielleicht sogar mit ein paar Tippfehlern und Korrekturen.

// Perform search request for every single user input.
input.addEventListener( 'change', searchPosts );

// Perform search once the user stopped typing for 300ms.
const debouncedSearch = debounce( searchPosts, 300 );
input.addEventListener( 'change', debouncedSearch );

Eine entprellte Funktion wird mit der Zeit ausgeführt, sofern keine neuen Aufrufe eingehen. Der allerletzte Aufruf einer entprellten Funktion wird immer ausgeführt. Als Implementierungsbeispiel beinhaltet Lodash die oft genutzte Funktion debounce.

Bereinigen (von Speicherlecks)

In einem dynamischen und interaktiven Kontext können und werden sich viele verschiedene Dinge verändern. Dennoch kann es einige Zeit dauern, bis etwas in Gang kommt und/oder abgeschlossen ist. Aus diesem Grund dreht sich ein Großteil der Performance-Überlegungen um Dinge, die in einem bestimmten Kontext eingerichtet wurden und sich seitdem geändert haben. Es ist wichtig, diese Dinge im Auge zu behalten und zu bereinigen, was sowohl Speicherlecks (d.h. die Ressourcenbindung oder -verschwendung ohne jeglichen Nutzen) als auch eine schlechte Benutzererfahrung (z.B. die Reaktion auf Daten, die von alten Anfragen stammen und nicht mehr benötigt oder gewünscht werden) oder sogar Fehlerzustände aufgrund unvorhergesehener Programmabläufe betrifft.

Event-Listener entfernen

Eine bekanntere Art von Speicherlecks basiert auf JavaScript-Event-Handlern oder -Listenern. Ein Event-Listener umfasst in der Regel eine Komponente, die einen bestimmten Code ausführt, wenn ein bestimmtes Ereignis auf einem bestimmten Element auf der Seite eintritt. Wenn nun eine der beiden Komponenten oder das Element entfernt wurden, gibt es keinen Grund mehr für die Existenz dieses Event-Listeners.

Ein gutes Beispiel, das die manuelle Entfernung von Event-Listenern veranschaulicht, findet sich in einer React-Codebasis in einem useEffect hook. Die Funktion stellt sicher, dass ein Event-Listener existiert, sowie entfernt wird, wenn die Komponente selbst abgesetzt wurde.

useEffect( () => {
    window.addEventListener( 'dragover', onDragOver );

    return () => {
        window.removeEventListener( 'dragover', onDragOver );
    };
}, [ onDragOver ] );

Zeitabhängige Funktionen entfernen

Ähnlich wie bei der obigen ereignisbasierten Funktion ist es wichtig, alle geplanten oder zeitbasierten Callbacks zu entfernen, die auf Daten oder Komponenten reagieren, die nicht mehr existieren. In einem Browser- bzw. JavaScript-Kontext sind dies vor allem Callbacks, die nach einer benutzerdefinierten Zeitüberschreitung oder innerhalb eines bestimmten Intervalls unter Verwendung der API-Funktionen setTimeout und setInterval ausgeführt werden.

Diese Callbacks zu entfernen kann einfach über die entsprechenden Funktionen clearTimeout und clearInterval erfolgen. In einem React-Kontext könnte dies ähnlich dem obigen Beispiel mit dem useEffect-Hook aussehen, wobei die set*-Funktion im Hook-Callback aufgerufen würde, während der clear*-Aufruf in die Cleanup-Funktion verpackt würde.

Ausstehende Anfragen abbrechen

Nehmen wir an, dass eine Texteingabe für die Suche nach Beiträgen auf einem externen Server verwendet wird. Wir haben bereits gelernt, dass wir vielleicht einen entprellten Callback verwenden wollen, um auf die Eingabe des Benutzers zu reagieren und dann die Suche durchzuführen. Auf diese Weise wird nicht für jedes einzelne neue oder gelöschte Zeichen eine Anfrage gesendet. Was aber, wenn der Benutzer noch etwas langsam tippt? Müssen wir auf Daten aus einer früheren Anfrage mit einem unvollständigen Suchbegriff warten und diese verarbeiten, da der Benutzer jetzt ja erneut getippt hat? Nein, das müssen wir normalerweise nicht. Also sollten wir die Anfrage und damit die Verarbeitung der Daten abbrechen.

Eine (moderne) Möglichkeit, dies zu tun: einen AbortController verwenden. Dank dieser Web-API können wir eine oder mehrere laufende Anfragen abbrechen, und fast alle Bibliotheken oder APIs zum Abrufen von Daten unterstützen dieses (Konzept).

useEffect( () => {
    const abortController = new AbortController();

    apiFetch( {
        path: '/some/path',
        signal: abortController.signal,
    } )
        .then( /* Process data... */ )
        .catch( () => {
            if ( abortController.signal.aborted ) {
                return;
            }

            // Handle other errors...
        } );

    return () => {
        abortController.abort();
    };
}, [ /* Dependencies required for querying and/or processing the data */ ] );

Das obige Beispiel zeigt, wie eine Anfrage gesendet, aber auch abgebrochen werden kann, im Falle eines abgesetzten useEffect-Hooks, z.B. weil die Komponente selbst abbricht oder wenn sich eine Abhängigkeit geändert hat. In diesem Fall wird der Abort-Controller direkt innerhalb des Callbacks instanziiert und verlässt den Scope nicht.

Allerdings ist es auch möglich, die oben genannte Anfrage manuell abzubrechen, z.B. wenn der Benutzer auf eine spezielle „Abbrechen”-Schaltfläche geklickt hat, die dazu dient, alle anstehenden Anfragen auf der Seite abzubrechen. In diesem Fall müsste der Abort-Controller außerhalb des Hooks instantiiert und als Abhängigkeiten übergeben werden (oder zumindest signal und abort).

Geplante Funktionen abbrechen

Genau wie bei anderen ereignisbasierten, zeitbasierten oder remote-request-Callbacks kann es auch vorkommen, dass eine entprellte Funktion abgebrochen werden muss. Dies ist der Fall, wenn ein etwas längeres Intervall verwendet wird und ein Kontext sich nach dem vom Benutzer initiierten Auslöser, aber vor der eigentlichen Ausführung des eingeschlossenen Codes ändert. Wir wollen nicht, dass die benutzerdefinierte entprellte Funktion den Code ausführt.

const debouncedFetchData = useMemo( () => {
    return debounce( fetchData, 300 );
}, [ fetchData ] );

useEffect( () => {
    return () => {
        debouncedFetchData.cancel();
    };
}, [ debouncedFetchData ] );

Falls Lodash verwendet wird: Sowohl die debounce-, als auch die throttle-Funktion unterstützt von Hause aus eine cancel-Methode zum Abbrechen eines verzögerten Funktionsaufrufs.

Performance messen

Mit den oben genannten Leitprinzipien und Beispielen im Hinterkopf sind wir nun auf einem guten Weg, Performance von Anfang an zu berücksichtigen und eine performanceorientierte Denkweise anzunehmen. Dennoch wird es viele Situationen und Implementierungen geben, in denen die Gesamtleistung des Systems, das aufgebaut werden soll, verbessert werden kann. Um jedoch etwas zu verbessern, muss zuerst festgestellt werden, was zu verbessern ist. Der einfachste Weg, dies zu tun, besteht darin, die Performance ausgewählter Workflows, Prozesse und Routinen der Anwendung zu messen.

Profiling von PHP, MySQL und WordPress

Es gibt eine Vielzahl von Funktionen und Diensten, die vielleicht sogar in die Programmiersprachen und Werkzeuge, die wir tagtäglich verwenden, integriert sind. Wir müssen nur lernen, sie zu benutzen. Wenn wir zum Beispiel unsere PHP-Anwendung auf einer niedrigeren Ebene besser verstehen wollen, sollten wir Dinge wie den Profiler verwenden, der in Xdebug integriert ist (wenn dies in unserem Setup möglich ist). Zu den Alternativen und Begleitern gehören XHProf, PHPBench und Tracy.

In einem WordPress-Kontext ist eines der wichtigsten Werkzeuge, um zu verstehen, was in der Website vor sich geht, das Query Monitor-Plugin, zusammen mit seinen eigenen, sowie den Debug Bar-Add-ons. Hier erfahren wir mehr über die aktuelle Anfrage, Datenbankabfragen, geladene Asset-Dateien, HTTP-Anfragen und vieles mehr.

Wer mehr Informationen über die genauen Vorgänge bei einer oder mehreren Datenbankabfragen benötigt, den wird es vielleicht überraschen, dass MySQL (und andere DBs) über integrierte Profiling-Funktionen. Dazu müssen lediglich das Profiling aktiviert und einige Anfragen ausgeführt werden, um das gesamte Leistungsprofil sowie die einzelnen Anfragen zu betrachten.

Infrastruktur-Performance

Je nach Host und allgemeiner Art der Infrastruktureinrichtung können wir leistungsstarke Integrationen und Dienste nutzen. Bei AWS Cloud würde ich zum Beispiel empfehlen, AWS X-Ray einzurichten. Andere Arten von Cloud-Lösungen sind Azure Monitor und die Google Cloud Operations Suite, oder plattform- und infrastrukturunabhängige Dienste wie New Relic.

Performance-Einblicke im Browser

In den letzten Jahren sind Browser immer leistungsfähiger und nützlicher geworden, wenn es darum geht zu verstehen, was auf einer Website und auch im Hintergrund passiert. Alle modernen Browser verfügen über eine flexible und konfigurierbare Netzwerkansicht, die verschiedene (Unter-)Anfragen in Bezug auf Zeiten, Dateigrößen, Integration und Einrichtung aufschlüsselt. Es gibt spezielle Einblicke in die Performance und auch sehr flexible und leistungsstarke Browser-Erweiterungen, zum Beispiel für Dinge wie React oder Redux.

Zusammenfassung

Sowohl das Schreiben des ursprünglichen Codes eines Projekts als auch die Messung und Verbesserung seiner Leistung erfordern ein gutes Verständnis aller beteiligten Komponenten. Die Programmiersprache, die Architektur, die Implementierungsdetails, die angewandten Programmier- und Algorithmus-Prinzipien, die Infrastruktur, die zu verwendenden Bibliotheken von Drittanbietern oder umgebungsspezifischen APIs… Es führt kein Weg daran vorbei, seine Tools (und seine Umgebung) zu kennen.

Wollen Sie ein neues Projekt starten und dabei vom allerersten Tag an auf Performance achten? Oder haben Sie eine Live-Site, deren Leistung weit unter Ihren Erwartungen und derer Ihrer Besucher oder Kunden liegt? Wir würden uns freuen, Ihnen als Ihr technischer Partner zur Seite zu stehen und dafür zu sorgen, dass Performance nie zur Nebensache wird.