Willkommen zum dritten und letzten Teil dieser Serie, der sich auf die Anwendungsleistung konzentriert. Um es noch einmal zusammenzufassen: In Teil 1 wurde ein umfassender Prozess zur Behandlung von Leistungsproblemen beschrieben. Teil 2 zeigte, wie wichtig Xas-Anfragen sind und wie man die Ladezeiten von Seiten verbessern kann, indem man deren Anzahl reduziert.
Der Schwerpunkt in diesem Teil liegt nicht auf der Beseitigung von Anfragen, sondern darauf, zu entscheiden, wann und wo die Logik ausgeführt werden soll, die beim Öffnen einer Seite benötigt wird.
Erinnerst du dich an die Beispielanwendung aus dem letzten Beitrag? Zur Erinnerung: Es war eine App, die die Planung von Aufgaben für verschiedene Ressourcen unterstützt. Im letzten Beitrag haben wir es geschafft, die Ladezeit der Seite von 5 Sekunden auf 1,8 Sekunden zu reduzieren, indem wir XAS-Abfragen eliminiert haben. Dies geschah entweder, indem Anfragen gruppiert oder Microflow-Aufrufe durch Nanoflows ersetzt wurden.
Anlage A
Als Nächstes führten wir eine weitere APM-Analyse der Seitenladezeit durch. Die Analyse ergab, dass fast die Hälfte der Seitenladezeit (750 Millisekunden) in einem einzigen Mikroflow namens calculateChartValues verbracht wurde. Das war das nächste offensichtliche Optimierungsziel.
Der Microflow CalculateChartValues aggregierte die Anzahl der Aufgaben nach Kategorie und Priorität. Die Daten wurden dann wie folgt in einem schönen Chart¹ dargestellt

Lassen Sie uns den Mikrofluss der geöffneten Seite untersuchen. Es verwendet einen NPE als Containerobjekt, und an diesen NPE sind viele Informationen angehängt, einschließlich des Diagrammwerts.

Hauptseite öffnen Microflow.

Jede Aktion ruft denselben Microflow (unten) auf, jedoch mit unterschiedlichen Argumenten

Jeder Aktionsaufruf ruft denselben Microflow (unten) auf, jedoch mit unterschiedlichen Argumenten
Und schließlich am Ende der Aufrufliste:

Der Code ist aufgeräumt und organisiert. Es gibt keine offensichtlichen Leistungsschwächen wie Commits innerhalb von Schleifen oder langsame Abrufe. Im Gegenteil, auf den Datenbankabruf folgt quasi eine Aggregatfunktion. Dies ist eine bekannte Optimierungstechnik in Mendix, und die fragliche Abfrage dauerte im Durchschnitt nur 5-10 ms. Diese Zeit wäre schwer zu verbessern. Indizes würden wahrscheinlich nicht helfen und könnten die Dinge sogar etwas schlimmer machen².

Jeder Abruf und jede Zählung dauert 10 Millisekunden. Insgesamt summierte sich das auf 500 Millisekunden.
Aber wenn wir uns die APM-Messungen ansehen, wird die Ursache für die lange Dauer offensichtlich. Beachten Sie, dass es 10 verschiedene Kategorien gibt. Multiplizieren Sie dies mit 4 verschiedenen Prioritäten und wir erhalten insgesamt 40 Kombinationen. Das Abrufen+Aggregieren war zwar im Allgemeinen eine gute Praxis, machte die Dinge aber sehr langsam. In diesem Fall wäre es viel besser, wenn alle Aufgaben zu Beginn einmal abgerufen und dann mithilfe einer Kombination aus Filter+Aggregat gezählt würden, wie unten gezeigt

Das Konstrukt „Retrieve+Aggregate“ wurde durch „Filter+Aggregat“ ersetzt
Wenn wir diese neue Version über APM ausführen, sehen wir eine unglaubliche Beschleunigung. Die Microflow-Ausführungszeit sinkt von 500 ms auf 17 ms³.

Durch das Abrufen ganz am Anfang und die anschließende Verwendung von Filter+Count sank die Gesamtzeit auf nur 17 Millisekunden.
Jetzt denken Sie wahrscheinlich, dass es nicht möglich ist, unter 17 Millisekunden zu gehen.
Was aber, wenn die Diagrammwerte beim Öffnen der Seite überhaupt nicht berechnet werden? Was ist, wenn die Diagrammwerte im Voraus berechnet und in der Datenbank gespeichert werden? In diesem Fall müssen die Diagrammwerte beim Öffnen der Seite nur aus der Datenbank abgerufen werden.
Es gibt zwei Hauptwege, berechnete Werte in der Datenbank zu speichern:
- Verwenden Sie Objektereignisse (vor/nach dem Festgeben/Löschen), um Änderungen im Auge zu behalten, oder
- Die Werte regelmäßig neu berechnen
Natürlich liefert Option 2 nicht immer genaue Statistiken4, aber sie ist viel einfacher zu implementieren und es ist weniger wahrscheinlich, dass sie an anderer Stelle in der App zusätzliche Leistungsprobleme verursacht (wo die Objekte festgelegt/gelöscht werden). Außerdem können wir bei Option 2 unseren Microflow problemlos für die Berechnung der Diagrammwerte wiederverwenden, sodass die Wahrscheinlichkeit, dass ein Fehler auftritt, geringer ist. Das Endergebnis:

Die schnellste Option besteht darin, überhaupt nichts zu berechnen und stattdessen vorberechnete Werte aus der Datenbank abzurufen.
Nun, das ist eine nette Beschleunigung, würdest du nicht zustimmen. Diese Ausstellung zeigt, wie die Leistung verbessert werden kann, indem die Ausführungszeit verschoben wird. Anstatt Werte beim Öffnen einer Seite zu berechnen, können diese Werte im Voraus berechnet und in der Datenbank gespeichert werden. Dies ist ein hervorragender Ansatz zur Verbesserung der Seitenladezeit. Manchmal kann es jedoch sehr schwierig sein, dies zu erreichen, wie es in
Ausstellung B
Wir alle wissen, dass virtuelle Attribute die Leistung beeinträchtigen. Sie werden jedes Mal ausgeführt, wenn ein Objekt abgerufen wird, unabhängig davon, ob das Attribut benötigt wird oder nicht. Wir zuckten also zusammen, als wir sahen, dass der am häufigsten ausgeführte Microflow auf dem Server einer für virtuelle Attribute war.

APM erstellt nicht nur Mikroflussprofile, sondern sammelt auch Statistiken über die Anzahl und Dauer aller Microflow-Ausführungen. Anhand dieser Statistik lässt sich leicht herausfinden, welche Mikroflüsse am zeitaufwändigsten sind.
Auf den ersten Blick sah das nach einem leichten Gewinn aus, denn die Refaktorierung der Verwendung virtueller Attribute ist wirklich einfach: Sie müssen in einem Ereignis vor oder nach dem Commit vorberechnet werden und speichern Sie sie als normales Attribut. In diesem Fall war das jedoch nicht möglich. Es stellte sich heraus, dass das virtuelle Attribut eine benutzerfreundliche Zeichenfolge mit der relativen Zeit seit Abschluss einer Aufgabe anzeigte:

Offensichtlich war dies in einem Ereignis vor dem Commit nicht möglich, da sich die Zeichenfolge ständig ändern muss. Eine andere Alternative wäre, ein geplantes Ereignis zu verwenden, aber das wäre wahrscheinlich noch ineffizienter, da die Werte ständig aktualisiert werden, auch wenn sie überhaupt nicht benötigt werden.
Die Lösung wurde klar, als wir herausfanden, dass die Zeichenfolge nicht in Excel- oder REST-Aufrufen verwendet wurde, sondern nur auf zwei Seiten. Anstatt einen String-Wert in einem Microflow zu berechnen, könnten wir ein kleines benutzerdefiniertes Widget erstellen, das die relative Zeit anzeigt. Das war ziemlich einfach zu bewerkstelligen. Es stellte sich heraus, dass wir das nicht einmal tun mussten, da das Format-String-Widget diese Option bereits hat.

Mit dieser endgültigen Optimierung lag die Seitenladezeit der Aufgabenanwendung endlich unter 1 Sekunde. Vergessen Sie nicht, dass das Laden der Seite zu Beginn durchschnittlich 5 Sekunden dauerte.
Dieses Leistungsproblem veranschaulicht perfekt den Konflikt zwischen Leistung und Wartbarkeit. Das Erstellen eines Widgets oder das Anpassen eines vorhandenen Widgets scheint nur ein oder zwei Stunden Arbeit zu sein. Was aber, wenn das benutzerdefinierte Widget eine neue Version mit einigen wichtigen Bugfixes oder einer großartigen neuen Funktion erhält? Es muss mehr Zeit aufgewendet werden, um die Anpassung erneut zu portieren. Wenn sich die Mendix-Client-API ändert, kann dies die Anwendung vollständig kaputt machen. Die Verwendung eines benutzerdefinierten Widgets (oder eines angepassten App Store-Widgets) bietet möglicherweise eine bessere Leistung, aber das geht mit einem höheren Wartungsaufwand einher.
Damit sind die Ausstellungen abgeschlossen. Sie können den Quellcode aus dem Mendix App Store herunterladen.
Zusammenfassung
Zusammenfassend lässt sich sagen, dass wir in dieser Reihe zunächst erörtert haben, wann und wie Leistungsprobleme angegangen werden sollten. Dann haben wir uns XAS-Abfragen und einige Tricks angesehen, wie die Anzahl solcher Abfragen reduziert werden kann. Schließlich haben wir in diesem Beitrag gesehen, wie die Ladezeiten reduziert werden können, indem die Berechnung räumlich und zeitlich verschoben wird (in einem benutzerdefinierten Widget statt in einem Microflow berechnen und vorberechnen).
Vielen Dank, dass Sie sich die Zeit genommen haben, diesen Blogbeitrag zu lesen, und ich hoffe, er hilft Ihnen dabei, schnellere Apps zu entwickeln!
¹ In den beigefügten Beispielen habe ich die gleichen Bedingungen wie in der Original-App reproduziert, aber ich habe das Diagramm durch eine einfache Tabelle ersetzt. Diese Wahl der Darstellung ist für die vorgenommenen Leistungsoptimierungen nicht relevant.
²Das Hinzufügen eines Indexes zu einer Datenbank verbessert nicht immer die Leistung, wie hier ausführlich beschrieben Artikel.
³Es wäre fair zu erwähnen, dass die Entitäten in diesem vereinfachten Beispiel nur wenige Attribute und Assoziationen haben. Die Beschleunigung in realen Situationen wäre wahrscheinlich weniger dramatisch. Schließlich muss man beim Abrufen von Objekten in großen Mengen wie diesem auch berücksichtigen, ob das Ergebnis in den Speicher passt. Dies kann problematisch sein, wenn es viele oder große Attribute oder viele Objekte gibt.
Als Entwickler müssen wir sicherstellen, dass es akzeptabel ist, dass die vorberechneten Werte bei Verwendung dieser Methode leicht ungenau sind. Darüber hinaus muss dies dem Benutzer mitgeteilt werden (z. B. durch Anzeigen einer Nachricht), um jegliche Verwirrung zu vermeiden. In einigen Fällen können Sie, wenn Echtzeitzahlen benötigt werden, eine Aktualisierungsschaltfläche hinzufügen, die bei Bedarf eine Neuberechnung auslöst.
Finden Sie heraus, wie CLEVR die Wirkung Ihres Unternehmens steigern kann
FAQ
Can't find the answer to your question? Just get in touch