Worauf Sie beim Customizing achten sollten, um eine nachhaltig gute Performance zu gewährleisten.
Produktlinie
Standard
|Expert
Betriebsart
CLOUD ABO
|ON-PREMISES
Module
Leistung & CRM
Budget & Teilprojekt
Fremdkosten
Ressourcenplanung
Business Intelligence
Abgesehen von der Performance im Netz und beim Vertec Customizing ist es zentral, dass auch bei Ordner-Expressions und in Listeneinstellungen - also immer dann, wenn Vertec Daten via OCL abgefragt werden - die Performance im Blick gehalten wird.
Expressions und Vorgänge, die am Anfang und mit kleinen Datenmengen problemlos laufen, können zu Performancefressern werden, je mehr Daten im System vorhanden sind.
Es lohnt sich, die Zugriffe von Anfang an performant zu designen und die in diesem Artikel beschriebenen Dos und Don'ts zu verstehen und zu beachten.
Und auch Punkt 6 von unserem 10-Punkte-Plan für eine zukunftsfähige Vertec-Installation zu beachten: Low Code Customizing vs. Overengineering .
Vertec lädt die Objekte, auf die es zugreifen muss, von der Datenbank ins Session Memory. Sind sie einmal im Memory, ist der Zugriff darauf schnell. Sobald dann aber auf Linkobjekte, Sublisten etc. dieser Objekte zugegriffen wird, müssen neue Objekte von der Datenbank geladen werden.
Je weniger (und je weniger häufig) Objekte von der Datenbank ins Memory geladen werden müssen, desto performanter der Vorgang. Dies muss im Hinterkopf gehalten werden, wenn Listen gefiltert werden sollen: Viele Objekte zu laden, wenn davon nur eine Handvoll gebraucht werden, kann oft vermieden werden. Oder es können Objekte in einem Rutsch ins Memory vorgeladen werden, dann ist der Einzelzugriff schneller.
Es ist wichtig, dass man die Expression und was sie macht, versteht, um performante Entscheidungen treffen zu können.
Und ist eine performantere Lösung einmal nicht möglich, sollte man die Gründe dafür kennen und auch verstehen, warum eine Liste oder Auswertung lange dauert (und sie im Namen entsprechend kennzeichnen, in einer separaten Ordnerstruktur ablegen, über Nacht laufen lassen , etc.).
OCL wertet Expressions streng von links nach rechts aus und verwendet das Resultat der ersten Teilexpression dann als Quelle für die nächste Teilexpression.
Einfache Expression wie projekt
(bzw. Projekt.allInstances
) machen im Normalfall keine Performance Probleme. Erweitert man diese Expression um ein persistentes Member, z.B. "aktiv" (projekt->select(aktiv)
), so dauert das genau gleich lang, denn die Projekte sind mit allen Members schon geladen.
Selektiert man nun aber auf eine Subliste wie zum Beispiel projekt->select(phasen->size>0)
, also alle Projekte, die mindestens eine Phase haben, wird es plötzlich viel langsamer. Das ist so, weil die Berechnung wie oben beschrieben von links nach rechts abgearbeitet wird:
phasen->size>0
auswerten. Das bedeutet, dass für jedes einzelne Projekt in der Liste die Phasen dieses Projektes einzeln von der Datenbank geladen werden und dann entschieden wird, ob es in die Liste kommt oder nicht.Vertec versucht die Daten optimiert zu laden bei solchen (einfachen) Expressions. Wenn wie im obigen Beispiel klar ist, dass alle Phasen der Projekte in der Liste benötigt werden, wird bei der Durchführung dieser Expression die gesamte Phasenliste auf einmal ins Memory geladen. Es erfolgt also nur ein einmaliger Datenbankzugriff. Bei komplexen Expressions oder falls Sublisten von Sublisten geladen werden müssen, ist dies jedoch automatisiert nicht möglich und es muss selbst darauf geachtet werden.
Bei Abfragen via SQL (SQL-Ordner, vtcapp.getwithsql(), Leistungssummen) werden die Filter auf dem Datenbank Server angewendet und nur die resultierenden Objekte ins Memory geladen.
Simple Anfragen wie "alle Objekte einer bestimmten Klasse" sind deshalb gleich schnell wie bei OCL, sobald aber gefiltert wird, kann SQL schneller sein, weil die Filterung auf dem Datenbank Server zusätzlich optimiert ist und nicht alle Objekte geladen werden müssen.
Sind die Objekte schon geladen bzw. vorgeladen , lohnt sich die Verwendung von OCL statt SQL, da dann nur noch auf das Memory, nicht mehr auf die Datenbank zugegriffen werden muss.
Der Zugriff auf grössere Mengen von Vertec Daten erfolgt an diesen Orten:
Bei Listen gilt: Je länger eine Liste (potenziell) ist, desto mehr muss man aufpassen, wie man sie aufbaut bzw. welche Spaltenexpressions man darin verwendet.
Vertec Listen sind beim Laden optimiert. Nur die sichtbaren Zellen (plus eine weitere "Seite") werden berechnet. Scrollt man über diesen Bereich hinaus, werden die benötigten weiteren Objekte geladen usw.
Deshalb kann das Umsortieren oder der Export nach Excel plötzlich sehr viel länger dauern als die Anzeige der Liste selbst, weil in diesem Fall alle Objekte geladen werden müssen.
Auch wenn Spalten summiert werden, ist es so, dass alle Objekte geladen werden müssen, damit die Summe gebildet werden kann.
SQL-Ordner sind empfehlenswert, wenn aus einer grossen Datenmenge bestimmte Objekte aufgrund einer Bedingung selektiert werden sollen. Im Unterschied zum Expression-Ordner erfolgt die Selektion der Daten durch den Datenbankserver, was in den meisten Fällen schneller ist.
Der Nachteil von SQL-Ordnern ist, dass sie nicht automatisch auf Änderungen von Daten reagieren und dass die Flexibilität von OCL für die Abfragen nicht zur Verfügung steht.
Der Vorteil von Expression-Ordnern ist, dass die Daten "live" sind, das heisst, sich automatisch aktualisieren, wenn sich an den Daten etwas ändert.
Der Nachteil ist, dass die Daten nicht wie beim SQL-Ordner durch den Datenbankserver selektiert werden, sondern alle Daten reingeladen und erst dann selektiert werden.
Bei den Ordnern sind die Objekte den Ordnern direkt zugeordnet und werden ungefiltert angezeigt, somit ist das Laden der Objekte normalerweise nicht performancerelevant.
Bei diesem Ordnertyp muss vor allem auf die Spaltenexpressions geachtet werden, denn diese können sehr wohl viele weitere Daten laden und den Ordner verlangsamen.
Vorsicht bei Inclusive/Exclusive Bedingungen in Stichwort-Ordnern: Diese Bedingungen sind performancerelevant, weil die Daten geprüft werden müssen. Insbesondere Inclusive sollte mit Bedacht verwendet werden, da dabei ALLE entsprechenden Daten im System geprüft werden müssen, was sehr lange dauern kann.
Link-Container sind die "Unterordner", die über einen Custom-Link-Typ oder einen Wrapper-Link-Typ zustande kommen. Sobald Objekte verlinkt werden, erscheinen sie in diesen Listen.
Hier empfehlen sich folgende Grundsätze:
Egal, in welchem der hier genannten Ordnertypen oder Link-Containern sich die Daten befinden und wie performant sie geladen werden - meist sind es die nicht performanten Spaltenexpressions, die eine Liste in die Knie zwingen.
Werden nur persistente Attribute (also Felder auf dem Objekt selbst) angezeigt, verlangsamt sich die Liste nach dem Laden nicht mehr, weil die Werte zusammen mit den Objekten bereits geladen sind.
Sobald aber auf Sublisten oder andere Objekte zugegriffen oder Berechnungen angestellt werden, muss auf die Performance geachtet werden. Die Expressions in den Spalten werden Zeile für Zeile abgearbeitet und laden je nachdem viele verschiedene Objekte. Sehr einfache Expressions versucht Vertec optimiert zu laden (siehe oben), aber oft ist das nicht automatisch möglich.
Deshalb liegt bei den Spaltenexpressions ein grosses Optimierungspotenzial für performante Listen. Oft lohnt sich der Einsatz eines List Controllers für die Bereitstellung der Daten.
Haben Sie eine Liste, die bereits langsam ist, können Sie mit dem Script: Ordnerperformance testen herausfinden, welche Spalte(n) Sie optimieren sollten.
Bei allen hier genannten Ordnertypen und Link-Containern kann mit List Controllern gearbeitet werden, um Objekte vorzuladen oder Berechnungen zu machen.
Der List Controller hat dabei keinen Einfluss auf die geladenen Daten, er bestimmt also nicht die Liste, die angezeigt wird, und kann dabei performancemässig auch keine Verbesserung erreichen. Er dient der Performance in den Spalten und kann zwei Aufgaben übernehmen:
Basis ist eine Datenbank mit 10'000 Projekten und 42'000 Projektphasen. Die Projektliste ist dabei die Standardprojektliste.
1. Test: Selektieren nach Attributen auf dem Objekt selbst (persistente Members)
Hier sieht man gut: ->select
auf ein persistentes Attribut ist performancemässig kein Problem. SQL ist bei langen Listen schneller, wenn nur wenig Objekte selektiert werden.
2. Test: Selektieren nach Zusatzfeld
3. Test: Selektieren nach einer Subliste
Hier werden die Projekte nach der Eigenschaft aktiv auf den Phasen selektiert.
Erkenntnis: Das Selektieren einer langen Objektliste auf Eigenschaften einer "Subliste" ist extrem teuer und sollte in OCL vermieden werden. Auch Zusatzfelder sind solche "Sublisten".
Alternativ von der Subliste ausgehen oder (bei Zusatzfeldern) die zu selektierende Information in ein persistentes Member des Objektes schreiben (via Keys oder Tags ).
In Scripts, Office-Reports und bei komplexen Spaltenexpressions in Vertec-Listen lohnt es sich, die benötigten Sublisten in einem Rutsch ins Memory zu laden. Bei der Verarbeitung muss das System dann nicht jedes Mal auf die Datenbank zugreifen, sondern kann die Objekte direkt im Memory ansprechen, was viel schneller ist.
Das kann via Preloading der Objekte in einem List Controller geschehen und gilt natürlich auch für alle anderen Scripts, wo Daten geladen werden, z.B. in Office-Berichten .
Für das Preloading gibt es folgende Varianten:
Die Methode vtcapp.getwithsql() erlaubt das Laden von Objekten via SQL. Die Filterkriterien werden dabei auf dem Server ausgeführt und nur die resultierenden Objekte geladen.
Der ausführende Benutzer muss über Administratorenrechte oder über das SQL Query Recht verfügen. Für die Erteilung von temporären Administratorenrechte steht die Variante der erweiterten Berechtigungen zur Verfügung.
# Preload der offenen Leistungen in Periode von einer Liste von Bearbeitern bearbIdList = bearblist.idstring() whereclause = vtcapp.sqlwherebetweendate('datum', dateFrom, dateTo) with vtcapp.SystemContext(): leistList = vtcapp.getwithsql("OffeneLeistung", "bearbeiter IN ({}) AND {}".format(bearbIdList, whereclause), "")
Die Methode vtcapp.fetchlinkmembers()
lädt für eine Liste von Objekten die Subliste rein, also die Zielobjekte, und macht den Multilink gleichzeitig current (was heisst, dass beim Zugriff auf den Link die Liste nicht nochmal geladen wird). Deshalb sollte das fetchlinkmembers()
unmittelbar vor der Weiterverarbeitung erfolgen.
# Loop durch die Phasen von Projekten, welche mindestens eine Phase "erteilt" haben projekte = vtcapp.getwithsql("Projekt", "bold_id IN (SELECT projekt FROM projektphase WHERE status=1)", "") vtcapp.fetchlinkmembers(projekte, "phasen") for projekt in projekte: x = projekt.evalocl("phasen->select(status=1)")
Diese Methode birgt riesiges Optimierungspotenzial, siehe nachfolgende Beispiele:
Ausgangslage: In einer grossen Datenbank mit Zehntausenden von Projekten und Phasen soll durch die Phasen derjenigen Projekten geloopt werden, welche mindestens eine Phase "erteilt" haben.
projekte = vtcapp.evalocl("projekt->select(phasen->select(status=1)->size>0)") for projekt in projekte: x = projekt.evalocl("phasen->select(status=1)")
Dauer: 37s. Die Umgebung ist hier sehr performant, die Dauer könnte noch viel höher sein bei weniger performanter Umgebung.
projekte = vtcapp.getwithsql("Projekt", "bold_id IN (SELECT projekt FROM projektphase WHERE status=1)", "") for projekt in projekte: x = projekt.evalocl("phasen->select(status=1)")
Dauer: 21s, eine Verbesserung um 43%. Es müssen viel weniger Objekte reingeladen werden (nur die relevanten Projekte), aber die Phasen dann jeweils einzeln im Loop: Für die 2771 Projekte werden die Phasen durch den Zugriff via OCL im Loop einzeln reingeladen, was erneute 2771 Datenbankabfragen bedeutet.
projekte = vtcapp.getwithsql("Projekt", "bold_id IN (SELECT projekt FROM projektphase WHERE status=1)", "") phasen = vtcapp.getwithsql("Projektphase", "projekt in (SELECT projekt FROM projektphase WHERE status=1)", "") for projekt in projekte: x = projekt.evalocl("phasen->select(status=1)")
Dauer: 20s. Eine minimale Verbesserung, die grösser ausfallen würde bei Performanceproblemen auf dem SQL-Server oder Netzwerklatenz.
Es werden alle Projekte und Projektphasen reingeladen, aber der Multilink Projekt - Phasen ist nicht "current", d.h. der Link selber weiss nicht, ob er aktuell ist, und muss deshalb einzeln nachgeladen werden mit SQL's wie:
SELECT BOLD_ID, BOLD_TYPE FROM ProjektPhase WHERE (Projekt in (1234567))
phasen = vtcapp.getwithsql("Projektphase", "projekt in (SELECT projekt FROM projektphase WHERE status=1)", "") projekte = phasen.evalocl("projekt") for projekt in projekte: x = projekt.evalocl("phasen->select(status=1)")
Dauer: 21s. Keine Verbesserung, es wird trotzdem für jedes Projekt ein SQL wie oben abgesetzt. Von den Phasen her ist das Projekt zwar geladen, aber das Projekt selber weiss nicht, ob es wirklich alle Phasen schon hat.
Das vtcapp.fetchlinkmembers() lädt für eine Liste von Objekten die Zielobjekte rein und macht zusätzlich den Multilink current:
projekte = vtcapp.getwithsql("Projekt", "bold_id IN (SELECT projekt FROM projektphase WHERE status=1)", "") vtcapp.fetchlinkmembers(projekte, "phasen") for projekt in projekte: x = projekt.evalocl("phasen->select(status=1)")
Dauer: 6s! Anstatt tausende SQL's werden hier im Beispiel genau 14 SQL's abgesetzt. Die Phasen der Projekte werden alle in einem Rutsch geladen mit SQL's wie
SELECT BOLD_ID, BOLD_TYPE FROM ProjektPhase WHERE (Projekt in (8454783,8662955,9775921,9779498,9782341,979...
Das ist Faktor 6 schneller gegenüber der naiven Abfrage via OCL. Auf weniger performanten Umgebungen kann das noch viel mehr sein, denn jede Datenbankabfrage ist grundsätzlich teuer.
Hier lohnt sich die Überlegung, ob man auch über eine kürzere Liste zum gleichen Ziel kommt.
Beispiel: Es gibt sehr viel mehr Projekte im System als Projektbearbeiter. Die Expression
projekt->select(projektleiter.name = 'Christoph Keller')
dauert darum sicher länger als die Expression
projektbearbeiter->select(name = 'Christoph Keller').eigprojekte
mit demselben Resultat.
Sobald auf das Attribut einer Subliste selektiert werden muss, muss auch die gesamte Subliste geladen werden, siehe dazu den Abschnitt Wie funktioniert der Zugriff via OCL .
Von vielen Objekten abhängige Objekte reinzuladen ist sehr teuer und sollte vermieden werden.
Lässt sich das nicht vermeiden, sollte mit Preloading gearbeitet werden.
Oder die entsprechenden Informationen werden auf dem Objekt selber bereitgestellt mittels Keys
oder Tags
. So ist wie bei den persistenten Attributen das ->select
unproblematisch.
Zusatzfelder sind Sublisten und nicht "Felder" auf dem Objekt, wie der Name suggeriert.
Das Filtern nach Zusatzfeldern ist sehr ineffizient, wenn die Zusatzfelder noch nicht reingeladen sind, weil sie einzeln für jedes Objekt reingeladen werden müssen.
Deshalb geht man bei Zusatzfeld-Filtern mit Vorteil über die Zusatzfelder. Statt:
projekt->select(zusatzfeldbool('auswahl'))
schreibt man also:
zusatzfeldklasse->select(boldid=1234567).zusatzfeldInstances ->select(wertBoolean).usereintrag->oclAsType(Projekt)
In unserem Performancevergleich mit vielen Daten (siehe oben) war die 2. Expression 180x schneller!
Mit SQL ist es noch schneller:
bold_id IN (SELECT usereintrag FROM zusatzfeld WHERE metazusatzfeld=1234567 AND wertboolean=1)
einfach ist die Liste dann nicht live, d.h. sie ändert sich nicht automatisch bei Änderungen.
Oder die entsprechenden Informationen werden statt in ein Zusatzfeld in Keys
oder Tags
geschrieben. So ist wie bei den persistenten Attributen das ->select
unproblematisch.
Zusatzfelder sind Sublisten, keine persistenten Attribute. Deshalb müssen sie Zeile für Zeile reingeladen werden. Bei langen Listen kann das zu einer Performanceverschlechterung führen.
Es gibt in Vertec diverse derived Attribute , insbesondere Summenattribute, welche einen Performancevorteil bringen können gegenüber dem Ausformulieren derselben Abfrage via OCL.
Beispiel: Auf einer Tätigkeitszuordnung zu einer Phase (taetigkeitphaselink) soll der darauf erbrachte Aufwand summiert werden:
taetigkeitphaselink.evalocl("phasen.leistungen->select(typ.code='ADM').minutenInt->sum")
Es gibt auf dem taetigkeitphaselink dafür das Summenattribut sumMinutenInt
. Wird stattdessen das Attribut verwendet:
taetigkeitphaselink.sumMinutenInt
erreicht man eine mehr als doppelt so schnelle Antwort.
Ein ->select
nach einem derived Attribut
dauert beim ersten Mal länger als ein ->select
auf ein persistentes Member, da es berechnet werden muss.
In den allermeisten Fällen ist dies jedoch schneller als den gleichen Aufruf selbst zu formulieren (siehe oben). Ausserdem wird es bei erneutem Aufruf nicht nochmal berechnet, sofern sich im System nichts geändert hat.
Derived Attribute werden zur Laufzeit bei Anzeige berechnet. Das geschieht Zeile für Zeile, und je länger die Liste, desto mehr Zeit wird benötigt.
Es ist besser, ein derived Attribut anzuzeigen, als denselben Wert via OCL zu berechnen (siehe oben), es lohnt sich jedoch die Überlegung, ob auf die Spalte verzichtet werden kann.
Das Laden und Summieren von Leistungen benötigt viel Zeit, da es normalerweise sehr viele Leistungen im System gibt.
Um das zu optimieren, stehen Leistungssummen für eine beschleunigte Summierung und Gruppierung in Form von OCL Operatoren zu Verfügung.
In Listenspalten sollten sie jedoch nicht direkt verwendet werden, da sie sich, solange sie angezeigt werden, neu berechnen, wenn irgendeine Leistung im System verändert wird.
In Listenspalten kann dafür ein List Controller eingesetzt und via Custom Renderer darauf zugegriffen werden.
Listen in Vertec werden optimiert geladen: Es werden jeweils nur die sichtbaren Objekte plus eine "Seite" geladen. Beim Scrollen werden dann nach und nach die benötigten Objekte nachgeladen.
Das funktioniert nur, wenn es keine summierten Spalten hat. Denn um einen Summenwert zu bilden, müssen alle Objekte geladen werden.
Listen in Vertec werden optimiert geladen: Es werden jeweils nur die sichtbaren Objekte plus eine "Seite" geladen. Beim Scrollen werden dann nach und nach die benötigten Objekte nachgeladen.
Wird eine Liste jedoch umsortiert, müssen alle Objekte auf einmal geladen werden. Bei langen Listen kann das Umsortieren dann spürbar lange dauern.
Deshalb am besten die Standardsortierung so wählen, dass sie passt und nicht manuell umsortiert werden muss.
Der OCL Listenoperator
->asset
stellt sicher, dass jedes Objekt in der Liste nur einmal vorkommt. Es ist aber teuer, weil nochmal jedes Objekt in der Liste durchgegangen, mit allen anderen Objekten in der Liste verglichen und allenfalls aussortiert werden muss.
Deshalb sollte man das ->asset
nur verwenden, wenn ein Objekt überhaupt mehrmals in der Liste sein kann. Also nicht auf Vorrat verwenden, sondern die Expression analysieren und nur wenn nötig verwenden.
Mit dem Modul Business Intelligence (BI) können alle Daten in Vertec ausgewertet werden. Dabei werden die Daten vorberechnet, damit auch Auswertungen über längere Zeiträume und mit grossen Datenmengen performant angezeigt werden können.
Eine Vielzahl von Kennzahlen werden standardmässig schon mitgeliefert oder als Zusatzfunktionen bereitgestellt. Auch eigene Kennzahlen können hinzugefügt werden.
Das BI bietet somit eine performante Alternative zu Auswertungslisten und kann dabei noch viel mehr als eine Liste, z.B. Daten gruppiert und in Zeitreihen anzuzeigen.