Sunday, April 15, 2012

Peformance Trick beim Durchlesen von Views


Eines vorweg für alle die mit Java auf Kriegsfuß stehen. Der Beispielcode ist zwar in Java. Das selbe sollte jedoch auch in anderen Sprachen z.B. Lotusscript funktionieren.  Ich zeige dann am Ende des Posts den Code auch in Lotusscript versprochen

In vielen meiner Programme oder Agenten kommt Code vor, der alle Dokumente einer bestimmten View lesen und verarbeiten muss.  wie z.B.


...
Document doc = view.getFirstDocument();
while (doc != null) {
     Document tempdoc = doc;
    
//Mach irgendwas mit diesem doc

     doc = view.getNextDocument(doc);
     tempdoc.recycle();
}

...

Ausführungsdauer  dieses Code bei unserer CRM Datenbank ca. 43 Sekunden.

Dabei war ich bis jetzt immer sehr enttäuscht über die Performance die Notes hier bietet. Das lesen von größeren Datenmengen dauert einfach ewig. Was hauptsächlich daran liegt, dass jeder Lesevorgang in der View eine Transaktion auslöst. Viel effizienter wäre es wenn Notes gleich ganze Blöcke lesen würde. Bisher gab es dazu keine Möglichkeit im Domino API.  Bei der view gibt es das auch nach wie vor nicht. Aber im Rahmen der Performanceverbesserungen bei xPages wurden neue Features beim ViewNavigator eingeführt und die kann man sich hier zunutze machen.

Hier der selbe Code wie oben nur umgebaut auf die Verwendung eines ViewNavigators:
...
ViewNavigator navigator = view.createViewNav();
ViewEntry entry = navigator.getFirstDocument();
while (entry != null) {
   Document doc = entry.getDocument();
 
//Mach irgendwas mit diesem doc
   ViewEntry tempEntry = entry;
   entry = navigator.getNextDocument();
   tempEntry.recycle();
}

...
Das bringt natürlich noch keine Verbesserung des Laufzeitverhalten, sondern bringt sogar eine Verschlechterung auf 53 Sekunden.

Aber der ViewNavigator besitzt neue Methoden die ein geblocktes Lesen erlauben. Die Voraussetzung dafür ist einmal, dass man die View aus dem man den Navigator erstellt das Autoupdate abgewöhnt mit view.setAutoUpdate(false).

Dann kann man den ViewNavigator mit navigator.setBufferMaxEntries(x) mitteilen, dass man gerne x Sätze auf einmal lesen will. Dies verbessert die Performance enorm. (Update laut Domino Wiki darf der Wert x zwischen (2 und 400) sein. Ich habe festgestellt, dass höhere Werte als 400 eine leicht bessere performance bringen. Siehe auch Kommentag von Ulrich Krause)

Folgender Code von oben ergänzt mit Blocking läuft auf meiner Maschine in unter 10 Sekunden statt der 43 Sekunden im usprünglichen Code ohne Blocking. 

...
view.setAutoUpdate(false);
ViewNavigator navigator = view.createViewNav();
navigator.setBufferMaxEntries(1024);
ViewEntry entry = navigator.getFirstDocument();
while (entry != null) {
   Document doc = entry.getDocument();
 
//Mach irgendwas mit diesem doc
   ViewEntry tempEntry = entry;
   entry = navigator.getNextDocument();
   tempEntry.recycle();
}

...
Das ist ja schon mal nicht schlecht.

Es gibt aber noch 2 zusätzliche Optionen mit denen man unter bestimmten Umständen noch zusätzliche Performance erzielen kann.

Wenn man wie in den obigen Beispiel die viewEntrys Spalten nicht benötigt, dann kann man das laden der Spaltenwerte unterdrücken. Je nach dem wie viele Spalten die View enthält, habe ich in meinem Tests nocheinmal eine Laufzeitreduktion um 10% erreichen können. Diese Option kann man mit navigator.setEntryOptions(ViewNavigator.VN_ENTRYOPT_NOCOLUMNVALUES) setzen.

Bei manchen Arten von Views kann es auch etwas bringen, die Entryoption navigator.setEntryOptions(ViewNavigator.VN_ENTRYOPT_NOCOUNTDATA); zu setzen. Laut meinen Informationen verhindert es die automatische Zählung bei kategorisierten Ansichten. Ich habe es aber noch nicht praktisch verwendet.

Noch einen zusätzlichen Performancegewinn, kann man erzielen, wenn man nur bestimmte Dokumente aus der View verarbeiten will, in dem man in der View die Selektionsfelder als Spalten aufnimmt, und dann das Dokument nur dann liest, wenn im Viewentry die Spaltenwerte den Selektionskriterien entsprechen. Hier sind die Performancegewinne noch stärker ausgeprägt als bei der klassischen Methode. Außerdem kann man natürlich noch die ganzen anderen Vorteile eines ViewNavigators verwenden. z.B. einen ViewNavigator mit allen Dokumenten einer bestimmten Kategorie einer kategorisierten View zu erstellen.

Hier noch wie am Anfang versprochen das oben angeführte Beispiel in Lotus script:

...
view.Autoupdate=false
Set navigator=view.Createviewnav()
navigator.Buffermaxentries=1024
navigator.Entryoptions=1  ' keine Spaltenwerte lesen.
Set entry=navigator.Getfirst()
While Not entry Is Nothing
    Set doc=entry.Document
    'Mach was mit dem Dokument
    Set entry=navigator.Getnext(entry)
Wend

... 

Würde mich über Kommentare zu den Performanceverbesserungen die Ihr in euren Anwendungen erzielen konntet freuen.

3 comments:

  1. Buffermaxentries=1024 ??
    Der Wert hat eine range von 2 bis 400.

    ReplyDelete
  2. Und noch eine kleine Ungereimtheit:

    ViewNavigator.getNext() (or getPrev) must be used

    siehe zu den Voraussetzungen für das Caching: http://www-10.lotus.com/ldd/ddwiki.nsf/dx/Fast_Retrieval_of_View_Data_Using_the_ViewNavigator_Cache

    ReplyDelete
  3. Hallo Ulrich!

    Danke für deine Anmerkungen. Ich werde noch ergänzen, dass die Doku einen Range von 2 bis 400 angibt. Ich bekomme aber bei höheren Werten eine leicht bessere Performance. Bezüglich der Problematik mit getNext() zu getNextDocument(). Wenn du die notes.jar decompilierst (Habe ich natürlich aus rechtlichen Gründen nicht wirklich gemacht ;-) ) wirst du sehen, dass die Methoden exakt identisch sind. Man kann also beide verwenden. Warum es in Lotus script kein getNext() sondern nur ein getNext(viewEntry) gibt, ist mir persönlich ein Rätsel. Aber auch in dem Link zum Wiki ist beim Beispielcode getNext(viewEntry) verwendet worden.

    ReplyDelete

ad