Ziel ist es, dem Konfigurator und / oder Administrator von Business App eine Hilfestellung zu geben, wie Suchindexe aufgebaut werden können und wie die das Zusammenspiel von Web Server (IIS) und Suchdienst (ElasticSearch / Opensearch) idealerweise funktioniert.

Zusätzlich zur Performance wird auf die Architektur von Suchindexen eingegangen, wo das sinnvoll erscheint, um Zusammenhänge zu verstehen.

Administratives / Architektur

Es ist wünschenswert, dass es eine schnelle Anbindung zwischen IIS und Suchdienst gibt. Daher empfiehlt es sich, beides auf demselben Rechner oder zumindest im selben LAN zu hosten. Für die Performance bei der Indexierung, also dem Aufbauen des Indexes, sind die Latenz und die Bandbreite von Bedeutung; für das Suchen innerhalb der Business App ist die Latenz aber am wichtigsten.

Online gehostete Suchdienste sind technisch ebenfalls möglich, aber aus Performance-Gesichtspunkten nicht zu bevorzugen (solange Business App nicht auch dort gehostet wird).

Es ist nicht notwendig, dass von den Daten im Suchdienst Backups erstellt werden, da diese jederzeit neu aufgebaut werden können.

ElasticSearch vs. OpenSearch

Unsere Tests haben keine Performanceunterschiede zwischen den beiden Suchdiensten feststellen können. Getestet haben wir ElasticSearch 7.10.2 vs OpenSearch 2.0.1.

Für neuere Versionen könnten sich andere Ergebnisse darstellen.

Grundsätzliches zur Indexierungsperformance

Die Indexierungsgeschwindigkeit wird maßgeblich von den zu indexierenden Daten bestimmt. Doch selbst bei denselben zu indexierenden Daten können Unterschiede in der Performance auftreten, je nachdem, wie der Index aufgebaut ist.

Berechnete Spalte vs. Quellrelation und Textspalte

Die „Berechnete Spalte“ lässt sich deutlich langsamer indexieren, als eine „Text“-Spalte, die über eine Quellrelation eingebunden ist. Auf Einzeldatensatzebene sind beide Wege etwa gleichschnell aber bei mehreren Datensätzen vom selben Typ ist die Spalte in der Quellrelation schneller.

Das liegt daran, dass Quellrelationen (und auch Zielrelationen) in Paketen zusammengefasst werden und diese Pakete gleichzeitig berechnet werden. Formeln („Berechnete Spalte“) dagegen werden immer pro einzelnem Datensatz berechnet.

Bei Formeln ist darüber hinaus deren Komplexität relevant für die Performance. Je mehr Datensätze dafür geladen werden müssen, desto langsamer wird es. Eine Formel, die dagegen nur „[EntityTitle]” oder eine andere Spalte, die direkt zu dem jeweiligen Datensatz gehört enthält, ist vergleichsweise schnell.

Ergebnisanzeige

Auch für die Ergebnisanzeigen werden Formeln verwendet. Die Laufzeit dieser Formeln kann schnell über 30% der Gesamtperformance ausmachen, bezogen auf die in BA.CRM vorausgelieferten Suchindexe.

Ergebnisanzeigen sind aufgeteilt in „Suche“ und „Mobil“. Mobil wird benötigt, wenn auch goMobile Pro verwendet wird. Es ist zu empfehlen, goMobile Pro in den Anwendungseinstellungen zu deaktivieren, wenn es nicht genutzt wird, da auch hier pro Datensatz wieder bis zu 3 Formeln zusätzlich ausgeführt werden.

Dateianhänge

Die Indexierung von Dateianhängen ist relativ aufwändig, da dazu aus Dateien Text extrahiert werden muss. Daher gibt es hier eine Optimierung, die Attachments nur dann neu indexiert, wenn sich daran etwas geändert hat und natürlich bei der Vollversorgung (nächstes Kapitel). Dennoch müssen alle Dateianhänge eines Feldes innerhalb eines Datensatzes neu indexiert werden, sobald sich auch nur an einem Attachment eine Änderung ergeben hat. Im Umfeld mit vielen und / oder großen Dateien innerhalb einer Dateianhangs-Spalte kann das massive Auswirkungen haben.

Innerhalb des Suchdienstes können bestehende Datensätze nicht geändert werden. Technisch ist jede Änderung ein Erstellen einer Kopie des alten Datensatzes, wobei die Kopie die alten Attachments plus alle neu übertragenen Felder enthält. Danach ersetzt die Kopie den Ursprungsdatensatz. Das Kopieren von großen Datenmengen verursacht auch hier Overhead.

Projekte können einzelne AttachmentParser durch ihre eigenen ersetzen, um z.B. nur bestimmte Textfragmente aus Dateien auszulesen. Dies reduziert die Menge der indexierten Daten und kann sich daher positiv auf die Performance auswirken.

Zielrelationen

Zielrelationen sind an sich zwar schnell zu ermitteln (genau wie auch Quellrelationen), allerdings können Zielrelationen zu sehr großen Datenmengen führen, was dann wiederum auch die Performance beeinträchtigen wird.

Beispiel: Werden zu einer Marketing Kampagne die Kampagnenadressen mit indexiert, und enthält eine Kampagne z.B. 10.000 Adressen, wird in jeden Kampagnenindex-Eintrag jede im Suchindex konfigurierte Spalte 10.000-mal indexiert. Das Risiko großer Suchindexe steigt, wenn diese Zielrelationen geschachtelt verwendet werden. Quellrelationen haben das Risiko dagegen weniger. In der Regel besteht die Quelle aus einem Datensatz oder in den Fällen wie “Weiterer Bearbeiter” aus nur wenigen.

Sprachen

Viele Daten, die im Suchindex gespeichert werden, müssen pro Sprache gespeichert werden, damit sie in der jeweiligen Sprache auch gefunden werden können. Wird eine Sprache nicht benötigt, kann sie aus den „verfügbaren Sprachen“ in den Anwendungseinstellungen entfernt werden. Das reduziert die Größe des Indexes und wirkt sich positiv auf die Performance aus.

Vollversorgung vs. Teilversorgung

Wenn ein Suchindex verändert / gespeichert wurde, wird dieser grundsätzlich neu aufgebaut. Das bedeutet, alle dort enthaltenen Daten werden neu erhoben. Das ist pro Eintrag massiv schneller, als eine Teilversorgung, die während des laufenden Betriebs stattfindet.

Das liegt daran, dass die Daten zu Paketen zusammengefasst werden und für komplette Pakete die Quell- und Zielrelationen ermittelt werden. Außerdem ist es in diesem Fall nicht nötig, Kreuzreferenzen zu suchen (nächstes Kapitel).

Bei großen Datenänderungen (Importe etc.) kann es schneller sein, alle Suchindexe neu aufzubauen, als die normale Indexierung abzuwarten. Dazu gibt es auf der Service-Seite einen Button zum Neuanlegen aller Suchindexe. Während des Importprozesses macht es Sinn, die Indexierung zu pausieren.

p(banner tip).Bitte vorher über die Aktion „Suchindexverwaltung öffnen (Anwendungsübergreifend)“ auf der Service-Seite den passenden Index löschen. (Dadurch wird die Tabelle „OrmSearchUpdate“ geleert).

Weitere Fälle, in denen eine Vollversorgung automatisch gestartet wird sind:

  • Die erste Verwendung mit einem neu konfigurierten Suchdienst
  • Veränderung der verfügbaren Sprachen
  • Einspielen eines Datenbank-Backups, bzw. Verwenden einer neuen oder anderen Datenbank

Kreuzreferenzen

Jedes Mal, wenn ein Suchindex eine Quell- oder Zielrelation beinhaltet, sorgt das dafür, dass ein fremder Datentyp in den Hauptdatentyp eingerechnet wird. Beispiel: Ein Kontakt enthält den Namen seiner Firma (Quellrelation Firma in Kontakt und dort Textspalte „EntityTitle”). Das sorgt dafür, dass bei jedem Update einer Firma auch der Kontakteindex mit aktualisiert werden muss. Das kostet natürlich Performance, lässt sich aber im laufenden Betrieb (Teilversorgung) nicht vermeiden.

Die Kreuzreferenzen sind auch der Grund, warum nicht die Tabelle „OrmSearchUpdate” um die Einträge bereinigt werden darf, die beim Speichern eines Suchindexes (und damit dessen Vollversorgung) überflüssig zu werden scheinen. In fremden Datentabellen werden sie möglicherweise noch benötigt. Nur wenn wirklich alle Suchindexe neu aufgebaut werden, darf die Tabelle zuvor geleert werden.

Indexierte Elemente ansehen

Über die Service-Seite gibt es die Möglichkeit, nach einer “Oid” eines Datensatzes im Suchindex zu suchen und dessen Daten als JSON auszugeben.

Hier können Konfigurationsfehler gefunden werden, die z.B. den Index sehr groß werden lassen und damit auch Performance kosten. Z.B. könnte über die Parent-Relation in Kombination mit „EntityTitle” aus versehen für „Alle Datentypen“ aktiviert worden sein, anstelle eines bestimmten Typs.

Sofortige Indexierung

In einigen Situationen erscheint es sinnvoll, den Suchindex sofort zu aktualisieren, um eine gefühlte bessere Performance zu erreichen. Z.B. beim Anlegen einer Vorlage, die danach sofort im Vorlage-Wählen Dialog gewählt werden soll. Für Anlegen von Datensätzen ist das der Standard, für Änderungen an den Datensätze ist es optional konfigurierbar.

<!-- Wann soll eine sofortige Versorgung des Suchindexes stattfinden? Optionen: "create", "edit", "both", "off", default: "create", Verfügbar ab 7.0 -->
<add key="BA:Search.ImmediateIndexOrms" value="create" />
<!-- Sollen bei der Sofortindexierung auch die Attachments mit indexiert werden?  "true" / "false", default "false" -->
<add key="BA:Search.ImmediateIndexAttachments" value="false" />

Um ein Gefühl für das Ausmaß dieser Verzögerungen zu bekommen, können Infomeldungen beim Speichern aktiviert werden. Dieser Schalter ist für den produktiven Betrieb nicht empfohlen, da die Meldungen für alle Benutzer auftauchen.

<!-- Stellt per Toaster die Zeiten dar, die durch das sofortige Suchindexupdate beim Speichern und zur Versorgung von ES verbraucht wurden, Default="false", ab Version 7.0 verfügbar -->
<add key="BA:Search.ShowImmediateUpdatePerformance" value="true"/>

Diese Meldungen werden als Toaster ausgegeben.

Konfiguration / Parameter

Mit folgenden Parametern kann die Performance beeinflusst oder getrackt werden:

PeriodicCheckInterval

<!-- Wartezeit nach einem Suchindexer-lauf, bis erneut nach zu indizierenden Daten gesucht wird (default 1min) -->
<add key="BA:Search.PeriodicCheckInterval" value="00:01"/>

Das PeriodicCheckInterval wird intern oft mit nur 30 Sekunden betrieben, ohne dass dabei negative Auswirkungen festgestellt wurden. Wenn es wichtig ist, dass Daten schnell in den Index aufgenommen werden, ist diese Einstellung eine Überlegung wert.

MaxHitCount

<!-- Wieviel Treffer sollen maximal bei der Suche angezeigt werden? Min 1, Max 1.000, Default 300 -->
<add key="BA:Search.MaxHitCount" value="300"/>

Der MaxHitCount von 300 könnte reduziert werden, um die Menge an Daten zu reduzieren, die übertragen wird, wenn viele Mitarbeiter gleichzeitig suchen. Die Menge der maximal zu übertragenden vom Suchdienst zum IIS ist (Anzahl angezeigte Indexe * HitCount * 2) und zur Übertagung vom IIS zum Client ist ((Anzahl angezeigte Indexe * HitCount) + Hitcount).

LogEveryOrm

<!-- Jedes ORM wird ins Logfile geschrieben, wenn es nach ES versorgt wird. Zusätzlich muss in CustomNLog.config trace-Logging auf *.SearchHelper gesetzt werden. !-->
<add key="BA:Search.LogEveryOrm" value="true"/>

LogEveryOrm erfodert ein Trace Logging in der CustomNlog.config. Zusätzlich kann der IndexUpdateWorker noch mit aktiviert werden, dann erscheinen noch mehr Logging-Informationen.

Beispiel für die CustomNLog.config:

<nlog>
  <rules>
        <logger name="*.IndexUpdateWorker" minlevel="Trace" writeTo="file" final="true"/> 
	<logger name="*.SearchHelper" minlevel="Trace" writeTo="file" final="true"/>  -->
  </rules>
</nlog>

SearchUpdatePackageSize, ExpectedMaxTimeToEndPackage

<!-- Paketgröße zum Lesen aus der Arbeitsvoratstabelle ab Version 6.0, Default=100, Min=1, Max=1000 -->
<add key="BA:Search.SearchUpdatePackageSize" value="100"/>
<!-- Dauer, die in der aktuellen Zeitscheibe von BA:Worker.NormalMaxRuntime noch mindestens übrig sein muss,
um die Arbeit mit einem weiteren Paket fortzusetzen. Ist die verbleibende Zeit geringer, wird die Arbeit vorzeitig
unterbrochen und beim nächsten Zuteilen einer Zeitscheibe durch den Workmanager fortgesetzt. 
Verringert die Wahrscheinlichkeit, mitten in einem Paket abbrechen zu müssen und das unnötigerweise zu wiederholen.
Der hier konfigurierte Wert, darf 1/3 der NormalMaxRuntime nicht überschreiten! 
Default: 5 Minuten -->
<add key="BA:Search.ExpectedMaxTimeToEndPackage" value="0:05"/>
<!-- Max. Laufzeit für normale Prozesse. Standard "0:15" => 15 Minuten. -->
<add key="BA:Worker.NormalMaxRuntime" value="0:15" />

Wie viele Datensätze werden an einem Stück indexiert. Dieser Wert darf nicht zu groß sein, denn

  • Der SQL Server unterstützt beim Abfragen von verknüpften Daten maximal 2.100 Parameter
  • Wenn die NormalRuntime abgelaufen ist, werden bereits begonnene Pakete komplett weggeworfen und neu eingeplant. Wenn alleine ein Paket länger braucht, als die NormalRuntime, dann ist folglich keine Indexierung möglich.
  • Die maximale Menge an Daten, die gleichzeitig in einem Paket an den Suchdienst gesendet werden kann, ist technisch begrenzt und schwer vorherzusagen.

Er sollte auch nicht zu klein sein, da es kleinere Pakete einen größeren Overhead mit sich bringen.

Die Empfehlung ist, diesen Parameter nur zu verändern, wenn es Probleme gibt. Mit ExpectedMaxTimeToEndPackage kann eingestellt werden, wie groß die verbleibende Zeitscheibe der NormalRuntime noch mindestens sein muss, um ein neues Paket zu beginnen. Ist weniger Zeit verbleibend, wird das Paket erst nach der nächsten Zuteilung der Zeitscheibe durch den Workmanager bearbeitet. Die nächste Zeitscheibe beginnt frühestens nach Ablauf des PeriodicCheckInterval.