# Performance

Performance-Probleme können unterschiedliche Gründe haben.   
Häufig sind es mehrere Ursachen, die in Summe zu einer schlechten Performance führen.

# Datenbank Deadlock-Fehler

Bei einem Schreibbefehl auf einen Datenbank-Transaktion erhält die Datenbank-Zeile einen Deadlock. Der Deadlock wird nach Abschluss der Transaktion wieder aufgehoben. Wird während des Deadlocks ein weiterer Schreibbefehl auf den Datenbank-Eintrag ausgeführt, wird ein Deadlock-Fehler ausgegeben. Der Fehler entsteht somit auf der Datenbank.

Die umgesetzten Lösungen können lediglich versuchen, Schreibbefehle auf die Datenbank zu minimieren und ein entsprechendes Fehler-Handling anzubieten. Die Gentable-Funktion "**gentable.insertIntoDB()**" schreibt zum Beispiel die Rechnungs-Positionen in die Tabelle "**invoice\_posting\_pos**". Die Funktion wird bei jedem Speichern einmalig ausgeführt. Alle Rechnungs-Positionen aller Rechnungen werden in diese Datenbank-Tabelle geschrieben. Die Tabelle wächst demnach immer weiter und bei Lösch- und Update- Befehlen wird es immer aufwendiger, den gewünschten Datensatz zu ermitteln. Entsprechend ist es wichtig, Wartungsoperationen bei einer SQL Datenbank einzuplanen!

Das Schreiben in die Datenbank kann über den Parameter "**<span style="color: rgba(0, 0, 0, 0.87); font-family: Roboto, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: #eeeeee; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">GentableSuppressWritingDataIntoDb</span>**" unterdrückt werden. Bei Rechnungen mit Bestelltbezug werden die korrekten Positionsdaten in der Tabelle allerdings zwingend für die 3-Way-Match-Prüfungen benötigt. Entsprechend ist das Unterdrücken der Schreibbefehle keine generelle Lösung.

Die folgende Beispiel-Fehlerausgabe stammt von einem MS SQL-Server. Als Lösung schlägt der SQL-Server vor, die Transaktion erneut auszuführen.

ReturnCode: -1  
SqlState: 40001  
NativeError: 1205  
ErrorMsg: \[Microsoft\]\[ODBC SQL Server Driver\]\[SQL Server\]Transaction (Process ID 92) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.  
.. this.Result:=false

Ab der Invoice-Version 2.0.200 wird dieser Vorschlag befolgt. Bei einem Schreibfehler in die Datenbank wird ein util.sleep() ausgeführt und der Schreibbefehl wird wiederholt.

# Datenbank Wartungsoperationen

<div id="bkmrk-documents-ben%C3%B6tigt-p">Documents benötigt performante Datenbankzugriffe! Damit eine MS SQL-Datenbank performant bleibt, müssen Wartungsoperationen eingeplant werden. Eine fehlende Wartung der Datenbank führt dazu, dass die Datenbank-Performance im Laufe der Zeit immer langsamer wird!</div><div id="bkmrk-"></div><div id="bkmrk-es-sollte-zun%C3%A4chst-i">Es sollte zunächst immer eine vollständige Sicherung der Datenbanken erfolgen! Im Anschluss sollte eine Verkleinerung der Datenbanken durchgeführt werden und die Statistiken sollten aktualisiert werden und die Indizes sollten neu aufgebaut werden.</div><div id="bkmrk--0"></div><div id="bkmrk-wenn-das-datenbank-w">Wenn das Datenbank Wiederherstellungsmodell nicht auf simpel steht, wird die Datenbank nicht verkleinert und die Performance leidet darunter!</div>

# Datenbank MS SQL Trigger Bugfix

Alle Tabellen der **DEX\_MasterData** und **DEX\_Workflow** haben einen Trigger für die Tabellen-Spalte **UpdateTS**. Durch diesen Trigger wird bei jederem Update-Befehl der Spaltenwert aktualisiert. Hierdurch kann man bei Supportfällen schnell herausfinden, wann Datensätze zuletzt geändert wurden.

In einigen Invoice-Versionen fehlte die WHERE-Bedingung im Update-Befehl und dadurch wurde das Update immer auf alle Zeilen ausgeführt. Dies kann zum Beispiel beim Schreiben der Rechnungspositionen in die **Invoice\_Posting\_Pos** Tabelle zu Deadlock-Fehlern führen.

Als Bugfix gibt es 2 Möglichkeiten  
1\. Die WHERE-Bedingung muss manuell zu allen Triggern hinzugefügt werden  
2\. Der Trigger kann gelöscht werden

[![image-1764590647984.png](https://docs.squeeze.one/uploads/images/gallery/2025-12/scaled-1680-/image-1764590647984.png)](https://docs.squeeze.one/uploads/images/gallery/2025-12/image-1764590647984.png)

# Suche und Suchmethode

##### Suchmethode

Die Suchmethode kann in den globalen Einstellungen konfiguriert werden der Standard bei der Auslieferung war lange Zeit die Suchmethode 0. Diese Suchmethode ist komfortabel aber sehr performancelastig. Wenn die Datenmenge ansteigt und mehrere User gleichzeitig die Volltextsuche verwenden, kann die Performance schnell merklich sinken.  
Die Suchmethode sollte auf 1 oder 2 geändert werden.

##### Richtig suchen

Die Anwender sollten in der Suche geschult werden und es muss darauf hingewiesen werden, dass es für die gesamte Systemperformance besser ist, wenn die Suchbegriffe in die passenden Suchmaskenfelder eingetragen werden und die Volltextsuche möglichst vermieden wird. Wenn ein großteil der User generell mit der Suchmethode 0 im Volltext über alle Workflow- und Archivbelege sucht, kann diese Arbeitsweise schnell zu einem Performance-Thema werden!  
Wenn ich als Anwender einen Vorgang im Workflow suche, dann muss ich zum Beispiel nicht zusätzlich das Archiv durchsuchen. Wenn ich nach einer Rechnungsnummer suche, kann ich die Suche auf den Mappentypen Invoice und das entsprechende Index-Feld einschränken.

# Anzahl Vorgänge im Posteingang bzw. im Aufgabenordner

Die Anzahl der Vorgänge im persönlichen Posteingang kann sich auf die Performance auswirken. Wenn ein Anwender tausende von Vorgängen im Posteingang hat, kann bereits das Öffnen des ersten Vorgangs sehr lange dauern und auch bei der Weiterleitung eines Vorgangs dauert es lange.

Die Ablage im Posteingang der Anwender kann über die Workflow-Aktions-Konfiguration deaktiviert werden. Häufig wird zum Beispiel die Validierung von allen Rechnungen durch eine Gruppe ausgeführt. In dem Fall sollte die Ablage der Belege im persönlichen Posteingang unterdrückt werden, damit es nicht zu dem oben beschriebenen Problem kommt.

Ab der Invoice Version **2.0.500** gibt es das Job-Skript **DEXPRO\_JOB\_EmptyUserInbox**. Der Job entfernt alle Vorgänge aus den Posteingängen der definierten Anwender und verwendet den Parameter **RemoveFilesFromUsersInbox**, über den die User konfiguriert werden können.

# Kontrolle UserExits

Beim Speichern und bei Abschluss einer Aktion werden Skripte ausgeführt. Die Skripte selber sind verschlüsselt und können im Projekt nicht angepasst werden. Die Prüfungen in den verschlüsselten Bereichen können zum Teil über Parameter an- und ausgeschaltet werden.   
In den Skripten werden UserExit-Funktionen ausgeführt und in diesen UserExits kann beliebiger Code ausgeführt werden. Bei Performance-Problemen sollte dieser Code auf lange Ausführungszeiten überprüft werden.

#### Speichern

Im Invoice-Modul wird beim Speichern das Skript **Invoice\_DF\_OnSave** ausgeführt und im Skript werden UserExit-Funktionen ausgeführt. Diese befinden sich im Skript **Invoice\_\_UserExit\_DocFileLib**. Es gibt eine Funktion **ue\_OnSaveStart()**, die direkt zu Beginn ausgeführt wird. Wichtig ist zum Beispiel, dass pro Skriptausführung nur ein sync()-Befehl auf den Vorgang erfolgen sollte. In den UserExits sollte generell auf sync()-Befehle verzichtet werden! Die Rechnungsdaten werden später im Skript gespeichert. Werden im UserExit andere projektspezifische Funktionen aufgerufen, dann muss man sich auch diese Funktionen ansehen.

<p class="callout warning">In den UserExits sollten keine sync()-Befehle ausgeführt werden.</p>

Es folgen diverse Prüfungen auf Kopf-Ebene, die über diverse Parameter gesteuert werden können. Im Anschluss werden die Rechnungspositionen in das Gentable-Objekt geladen und es werden weitere Prüfungen ausgeführt. Nach den Standard-Prüfungen wird extra eine UserExit-Funktion für das Gentable bereitgestellt. Manchmal wird in den Projekten bereits im ue\_OnSaveStart() das Gentable ausgelesen und zurückgeschrieben, wodurch ein unnötiges sync() ausgeführt wird. Hier kann man den Code optimieren, indem man das korrekte UserExit verwendet. Über die UserExit Funktion **Gentable.prototype.ue\_Invoice\_OnSave** können projektspezifische Ausführungen am Gentable-Objekt vorgenommen werden. Das Objekt muss nicht in das Gentable-Feld zurückgeschrieben werden. Das passiert automatisch im Anschluss.   
Nach dem Gentable-UserExit werden die Positionsdaten in die Datenbank-Tabelle **Invoice\_Posting\_Pos** geschrieben. Über den Parameter **GentableSuppressWritingDataIntoDb** kann zum Beispiel im Invoice-Modul das Schreiben in die Datenbank deaktiviert werden. Wichtig ist aber, dass die Daten zu bestimmten Zeitpunkten in die Datenbank geschrieben werden. Bei Rechnungen mit Bestellbezug werden in der Regel zwingend aktualisierte Daten in den Tabellen benötigt.

Beim Zurückschreiben der Rechnungspositionen in das Gentable-Feld wird ein sync()-Befehl auf den Vorgang ausgeführt und das sollte auch der einzige im Skript ausgeführte sync()-Befehl sein! Die Kopfdaten zu einer Rechnung werden in die Tabelle **Invoice\_Posting\_Head** geschrieben. Dies kann über den Parameter **HeadDataSuppressWritingDataIntoDb** deaktiviert werden.

Zum Abschluss wird nach allen Prüfungen und Anpassungen die UserExit-Funktion **ue\_OnSave\_End()** ausgeführt. Hier muss berücksichtigt werden, dass der sync()-Befehl im Skript bereits erfolgt ist.

Im Procurement-Modul und weiteren Modulen sind die UserExit-Funktionen analog aufgebaut.

#### Abschluss einer Aktion

Für den Abschluss einer Aktion kann über die Aktions-Konfiguration der ausgehende Kontrollfluss als Button angezeigt werden. In dem Fall erfolgt die Weiterleitung direkt. Der ausgehende Kontrollfluss kann auch ausgeblendet werden und die Weiterleitung kann über eine im Standard enthaltene benutzerdefinierte Aktion im Skript erfolgen (Hintergrund: nur so sind aus technischen Beschränkungen andere Beschriftungen auf dem Button möglich). An den benutzerdefinierten Aktion sind die Skripte **DEXPRO\_Action\_FinishAction**, **DEXPRO\_Action\_FinishAction\_Distribute** oder **DEXPRO\_Action\_FinishAction\_Distribute\_AP** hinterlegt. Die Skripte selber haben <span style="text-decoration: underline;">keine UserExits</span> und leiten nur den Vorgang weiter.

Es kann aber auch sein, dass eine projektspezifische benutzerdefinierte Aktion für die Weiterleitung verwendet wird. In dem Fall kann im Skript beliebiger Code enthalten sein und der sollte bei Performance-Problemen kontrolliert werden.

Eine benutzerdefinierte Aktion kann im Bearbeitungs-Modus angezeigt werden. Wenn die benutzerdefinierte Aktion ausgeführt wird, wird zuerst der Speicher-Vorgang gestartet. Um die Laufzeit der Weiterleitung getrennt vom Speichern zu prüfen, sollte zuerst gespeichert werden und dann darf erst die benutzerdefinierte Aktion ausgeführt werden.

Bei einer Weiterleitung über eine benutzerdefinierte Aktion wird die Weiterleitung über ein DocFile.forwardFile() ausgeführt. Die komplette Weiterleitung wird jetzt im Kontext des Skripts ausgeführt, welches bei der benutzerdefinierten Aktion hinterlegt ist. Die Ausführungszeit des Skripts umfasst die komplette Weiterleitung von dem Start der Weiterleitung bis der Vorgang einen neuen Workflow-Schritt erreicht hat! Zunächst wird das Prüfskript **DEXPRO\_WF\_CheckActionEnd** auf dem ausgehenden Kontrollfluss ausgeführt. Auch hier gibt es wieder UserExits. Da der Workflow für alle Module derselbe ist, befinden sich die UserExits im globalen Skript **DEXPRO\_\_UserExit\_WorkflowLib**. Analog zum Speichern gibt es das UserExit **ue\_OnActionEnd\_Start()**, welches zu Beginn des Skriptes vor den Standard-Prüfungen ausgeführt wird. Dann finden Prüfungen im Gentable statt und die Gentable-UserExit-Funktion **Gentable.prototype.ue\_OnActionEnd** befindet sich im Skript **DEXPRO\_\_UserExit\_GentableAdd**. NAch den Prüfungen und nach dem sync()-Befehl gibt es noch die UserExit-Funktion **ue\_OnActionEnd\_End()**. Diese UserExits müssen auf Performance geprüft werden.

Wenn die Weiterleitung nicht durch eine Fehlermeldung gestoppt wird, wird die Folge-Aktion ermittelt; die Workflow-Regeln werden ausgewertet und ggf. werden Aktionen nach Auswertung der Workflow-Regeln übersprungen und dann wird die Folge-Aktion ermittelt und dann müssen die Workflow-Regeln auch wieder zur nächsten Aktion ausgewertet werden.

Wenn der neue Workflow-Schritt erreicht ist, wird der Vorgang den neuen zuständigen Benutzern ggf. im Posteingang hinzugefügt. Wenn einer der neu zuständigen Benutzer über neue Vorgänge im Posteingang direkt informiert wird, dann wird auch noch die Mail an den neuen Benutzer versendet und all dies passiert immer noch im Kontext des DocFile.forwardFile()! (Die Mailversendung kann über die Mandanteneigenschaft **EMailAsync** auf eine asynchrone Versendung umgestellt werden. Der Nachteil ist, dass Fehler bei einer manuellen Mailversendung nicht mehr direkt an den Anwender zurückgegeben werden.)

Am Ende wird dem Anwender ggf. direkt der nächste Vorgang angezeigt. Wenn der ausgehende Kontrollfluss als Button angezeigt wird, dann kümmert sich Documents um die Anzeige. Wenn der Button als benutzerdefinierte Aktion angezeigt wird, muss via Skripting der nächste Vorgang ermittelt werden und hier kann via UserExit **NavigationReturnObject.prototype.ue\_Next** eingegriffen werden.

Das DocFile.forwardFile() umfasst demnach viele andere Ausführungen und Einzelschritte. Im Support kommt bei Performance-Problemen häufig die Meldung an, dass das Skript **DEXPRO\_Action\_FinishAction** eine lange Laufzeiten hat und dieses Skript optimiert werden soll. Das Skript selber führt aber nicht viel mehr als das DocFile.forwardFile() aus und demnach gibt es hier nicht viel zu optimieren! Das eigentliche Problem liegt irgendwo bei den Einzelschritten im DocFile.forwardFile()!

<p class="callout info">Die Weiterleitung zum Beispiel über das Skript **DEXPRO\_Action\_FinishAction** umfasst alle Workflow-Skripte; die Zuordnung des Vorgangs in die Postangangs-Körbe der neuen User und umfasst selbst die Mailversendung über neue Vorgänge im Posteingang!</p>

Eine einfache Variante, um bei der Weiterleitung diverse Einzelschritte auszuschließen kann ereicht werden, ist eine Verzögerung. Hierzu gibt es 2 Varianten.

##### Parameter Workflow\_DelayAfterAction

Über den Parameter **Workflow\_DelayAfterAction** kann direkt nach Abschluss der Aktion eine Verzögerung angesteuert werden. Bei Abschluss der Aktion wird lediglich das Prüf-Skript **DEXPRO\_WF\_CheckActionEnd** ausgeführt. Der Vorgang läuft im Anschluss direkt in eine Workflow-Verzögerung von einer Minute. Die interne Documents-Job-Engine löst die Verzögerung auf. Der Nachteil ist, dass man auf die Documents-Job-Engine angewiesen ist. Sollte die Documents-Job-Engine mit der Ausführung diverser Jobs ausgelastet sein, kann es ggf. lange dauern, bis die Verzögerung aufgelöst wird. In dem Fall sollte man sich wiederum die Asuführungszeitpunkte und Laufzeiten der Jobs anschauen und hätte hier eine mögliche Ursache für die Performance-Probleme. Ein weiterer Nachteil ist, dass die Workflow-Aktion am Vorgang noch den alten Wert hat und der Filter am zugehörigen Ordner ggf. erweitert werden muss, um die Vorgänge in Verzögerung auszublenden.  
Durch die Verzögerung direkt nach der Aktion entfallen diverse potentiel performancelastige Aktionen. Lediglich die Anzeige des Folge-Vorgangs wird wieder ausgeführt. Sollte die Ausführungsdauer immer noch langsam sein, wird die Ursache sehr wahrscheinlich an den UserExits bei Abschluss der Aktion liegen.

##### Technische Workflow-Aktionen Delay, Delay1, Delay2, ...

Bei der Variante mit einer technischen Aktion werden einige zusätzliche Aktionen ausgeführt, die sich aber nicht großartig auf die Performance auswirken sollten. Die technischen Workflow-Aktionen **Delay**, **Delay1**, **Delay2**, ... **Delay5** können zwischen bestehenden Aktionen hinzugefügt werden. Es handelt sich nicht um eine Workflow-Verzögerung! Der Vorgang liegt in einer Workflow-Aktion und wird durch die Gruppe **TechAccessProfile** gesperrt. Dieser Gruppe sollten nur technische User zugeordnet werden und die Mailversendung über neue Posteingänge sollte bei diesen Usern deaktiviert sein! Nach Abschluss der Aktion und nach Abschluss der Prüfungen wird die Folge-Aktion ermittelt und die Workflow-Aktion am Vorgang wird auf "Delay" (bzw. "Delay1", ...) umgestellt. Dadurch müssen die Filter an den bestehenden Ordnern nicht erweitert werden. Workflow-Regeln müssen für technische Aktionen nicht berechnet werden.   
Für jedes Modul gibt es entsprechende Skripte, um die Verzögerung aufzulösen. Im Invoice-Modul muss zum Beispiel das Job-Skript **Invoice\_JOB\_ForwardDelay** als Job eingeplant werden.

# Job-Skripte

Ein wichtiges Performance-Thema ist die Ausführung von Job-Skripten. Hier verhält es sich ähnlich wie bei den Suchmethoden. Die bequemste Suchmethode ist extrem performancelastig.

Zu Beginn eines Projekts wird das Ausführungsintervall bei Jobs gerne auf stündlich oder auf eine noch häufigere Ausführung eingstellt. Das ist zu Beginn eines Projekts auch häufig kein Problem, da sich wenige Daten im System befinden. Die Anzahl der Vorgänge wächst nach dem produktiven Start langsam an. Zu Beginn hat der Kunde ggf. nur mit einem Mandanten gestartet und nach einige Zeit werden weitere Mandanten aktiv geschaltet. Die Anzahl an Stammdaten steigt ebenso wie die Anzahl an Vorgängen.

Jobs, die bei Projektbeginn nur wenige Sekunden oder wenige Minuten gebraucht haben, laufen dann ggf. über eine Stunde und blockieren mit der Ausführung ggf. auch wiederum andere Jobs. Vor allem können aufwändige Jobs die gesamte System-Performance herunterziehen.

Bei Performance-Problemen sollte man sich zu jedem einzelnen Job die Ausführungsdauer anschauen und entsprechend muss eine sinnvolle Ausführungshäufigkeit abgeleitet werden. Aufwändige Jobs, die nicht zwingend tagsüber laufen müssen, sollten generell nur am späten Nachmittag oder in der Nacht ausgeführt werden.