Archiv nach Schlagworten: Performance

cm:name – Die erzwungene Eigenschaft

Im letzten Post habe ich ein Performanceproblem beschrieben, welches auf die Nutzung von cm:name (bzw. cm:cmobject als Vatertyp) bei der Modellierung / Instanziierung von 500.000+ Datensätzen im Standard ContentStore zurückgeführt werden konnte. Entsprechend eines der aufgeführten Ansätze wollte ich diese Tage eine kleine Migration umsetzen, bei welcher die redundante Eigenschaft cm:name durch Wechsel auf den Vatertyp sys:base und anschließender Löschung der persistenten Werte von den 500.000+ Datensätzen eliminiert werden sollte. Dabei wurde mir bewusst, dass cm:name unabhängig von der Definition meiner Inhaltstypen im Data Dictionary durch das System immer gespeichert und indiziert wird – lediglich die Prüfung auf Pflicht und Constraint basiert auf der tatsächlichen Typdefinition.

Damit wird natürlich der Ansatz zur Bekämpfung des Performanceproblems wertlos – wenn es unmöglich ist, ein Node ohne cm:name in Datenbank und Index anzulegen, dann lassen sich Seiteneffekte auf die Standardnavigation von Share durch große Datensätze nicht vermeiden.

Wie äußert sich dieses Verhalten?

  • Wird ein Node ohne cm:name angelegt, so wird kein Wert gespeichert, aber beim Auslesen die UUID der NodeRef als vorgetäuschte cm:name zurück gegeben (z.B. DBNodeServiceImpl.getProperty(NodeRef, QName) oder ReferenceablePropertiesEntity.addReferenceableProperties(Node, Map<QName,Serializable>)).
  • Nur die lt. Typ definierten Eigenschaften werden bei der Anlage / einer Aktualisierung validiert. Da cm:name nur für cm:cmobject und davon abgeleitete Typen definiert ist, wird der Constraint nur für diese Typen geprüft. Die Prüfung auf Pflicht ist zwecklos, da Alfresco die Eigenschaft transparent auf die UUID setzt, wenn sie nicht explizit angegeben wurde.
  • Beim Indizieren gilt nichtmehr die Typdefinition, sondern die vorhandenen Eigenschaften und deren Definition. D.h. für Knoten, die nicht von cm:cmobject ableiten wird cm:name trotzdem indiziert, weil es a) automatisch den Wert der UUID hat, wenn es nicht gesetzt wurde und b) eine Definition der Eigenschaft gibt, welche die Indizierung vorgibt (nämlich an cm:cmobject).

Lt. SVN ist dieses Verhalten soweit von mir geprüft seit 3.2 so anzufinden und auch in der aktuellen 4.0 unverändert. Für welche Funktionalität muss diese Eigenschaft erzwungen werden, auch wenn ich sie nicht in meinem Datenmodell (bzw. den definierenden Typ) verwendet habe? Bei der in solchen Fällen üblichen Frage “Bug oder Feature” stimme ich aktuell für “Bug”. Als Partner im Auftrag eines Enterprise Kunden habe ich diesen Punkt entsprechend dem Alfresco Support übermittelt. Wenn das Verhalten bewusst so sein soll, dann wäre es zumindest sauberer, cm:name – ähnlich wie die Eigenschaften von sys:referencable – an sys:base zu hängen.

cm:name – Grenzen der Sortierbarkeit

Auf Basis von Alfresco Share 3.2.2 haben wir für einen Kunden ein Compliance Management System entwickelt, welches zusätzlich zu Vertrags- bzw. Dokumentvorlagen, organisatorischen Strukturen und komplexen Workflows zur Einhaltung der Prüfungs-, Freigabe- und Dokumentationsprozesse auch die Verwaltung von ca. 500.000+ Stammdatensätze umfasst. Letztere sind als abstrakter Inhaltstyp mit Aspekten zur Gruppierung von Datenfeldern modelliert und werden regelmäßig durch einen Import mit einem externen System abgeglichen. Die Datensätze liegen allesamt im regulären ContentStore “workspace://SpacesStore”, denn 500.000 Objekte mit ca. 20 Metadatenfeldern sind nicht aureichend, um eine nennenswerte Auswirkung auf die Performance des Gesamtsystems erwarten zu können.

Entgegen den Erwartungen wurde ein Problem auf Basis von Alfresco-Eigenheiten bei der Sortierung von Lucene-Suchen beobachtet. Da der Inhaltstyp cm:cmobject als Vatertyp nutzt, hat jedes Objekt auch die geerbte Eigenschaft cm:name, welche wir mit dem Wert des fachlich eindeutigen Datensatzschlüssels belegen. Nach der ersten Synchronisierung enthielt der Index 500.000+ zusätzliche, 100% distinkte Werte für das Feld @cm:name, was zu einem merklichen Einbruch der Performance der Share Documentlibrary Navigation führte. Für uns ergab sich für jede auf @cm:name sortierende Suche ein Grund-Overhead von 3 – 5 s, bevor überhaupt die eigentliche Suche lief. Da jede Navigation innerhalb der Share Documentlibrary eine sortierende Suche in doclist.get.js auslöst, ist die Gesamtanwendung daher untragbar beeinflusst.

Warum verhält sich Alfresco schon bei einer (scheinbar) kleinen Datenmenge so schlecht? Hierfür gibt es technisch gesehen 2 Hauptgründe:

  1. Alle Feldwerte werden bei einer sortierenden Suche aus dem Index in den Speicher geladen, um vorsortiert zu werden (bei uns ca. 800.000+ distinkte Werte bei üblicherweise < 50 zu sortierenden Ergebnissen auf einer Seite).
  2. Der Lucene interne FieldCache kann bei wiederholten Anfragen nicht optimierend wirken. Bei jeder Suche kommt aufgrund der mehrfachen Kapselung des eigentlichen IndexReader eine unterschiedliche “Kapsel”-Instanz zur Geltung – der FieldCache kann per Kontrakt nur für die exakt gleiche Instanz schon geladene Werte zurückgegeben. Es werden also immer alle Feldwerte brav neu geladen. (Wer Zeit und Muße hat, kann sich den Cache mal mit einem Java Debugger anschauen und wird feststellen, dass die benötigten Daten i.d.R. schon mehrfach vorliegen aber nicht abgegriffen werden können.)

Je nachdem wie performant die I/O des Datenträgers ist, auf welchem der Index abgelegt wird, ergibt sich ein mehr oder weniger starker Effekt. Mit meinem persönlichen Arbeitslaptop inklusive SSD hab ich für gewöhnlich mehr Leistung als der Kunde für seine produktiven Maschinen zu zahlen bereit ist, und bleibe mit 1 – 2 s Verzögerung relativ verschont – bei intensiver Navigation fällt Anwendern aber auch das schnell negativ auf.

Welche Lösungen / Ansätze gibt es für die Performanceprobleme bei sortierenden Suchen?

  • Große Mengen an Stammdatensätzen sollten in andere ContentStores ausgelagert werden (separater Index). Dies geht jedoch nur, wenn keine oder nur einfach abbildbare Hierarchien mit sonstigen Inhaltsstrukturen bestehen (sollen).
  • Wenn möglich sollten Merkmale zur Sortierung über eigene, fachliche Metadaten abgebildet werden. Sobald standardisierte Eigenschaften verwendet werden, ist die Gefahr groß, durch größere Datensätze Seiteneffekte bzgl. der Sortierperformance anderer Datenbestände zu verursachen.
  • Bei Suchen über kleinere Teilmengen kann im Extremfall die Sortierung (inkl. Paging) auf JavaScript- oder Java-Seite schneller sein als mittels Lucene. (Dies wäre z.B. bei uns im Fall der Documentlibrary Navigation so, da i.d.R. nur 5 – 15 Elemente in einer Ebene liegen.)
  • Migration auf Alfresco 4.0 mit SOLR / Canned Queries

Für mich war das eine eher überraschende Erkenntnis, bedeutet es doch, dass in Alfresco bzw. Share nur wenige 100.000e Dokumente verwaltet werden können, bevor die Kernkomponente Documentlibrary langsamer reagiert. Das passte nun gar nicht zu meiner bisherigen Erfahrung, die u.a. die Verwaltung von mehreren Millionen von Objekten in einer einzigen Alfresco Instanz umfasst …

Die Probleme die Sortierung betreffend sind Alfresco schon längere Zeit bekannt. Zusammen mit ähnlichen Problemen bei PATH-basierten Suchen und Berechtigungsprüfungen bei großen Ergebnismengen wurde diese als Anlass / Bestärkung genommen, mit Alfresco 4.0 den Wechsel auf SOLR zu vollziehen sowie zentrale Abfragen auf die Datenbank zu verlegen. Gerade die Canned Queries garantieren bei Alfresco 4.0, dass bei einer sortierenden Abfrage nur die Eigenschaften der tatsächlich in der abgefragten Ebene befindlichen Objekte berücksichtigt werden.