Abfragen von großen Datenmengen in SharePoint

Da ich vor Kurzem wieder mal zu einem Kunden gerufen, der in seiner SharePoint-Umgebung sehr große Performance-Probleme hatte mit einer Eigenentwicklung, möchte ich die “Lessons Learned” an dieser Stelle nieder schreiben, da ich immer wieder erleben muss, dass Entwickler in Ihrer kleinen Entwicklungsumgebung vor sich hin “coden”, den späteren Produktiveinsatz und deren Datenmengen und –Strukturen aber völlig links liegen lassen.

Ausgangslage

Die produktive Umgebung läuft derzeit auf SharePoint 2007. Innerhalb einer WebApplication existieren derzeit 130.000 Site Collections verteilt auf 100 Datenbanken. Die gesamte Datenmenge umfasst etwa 1,4TB in den Datenbanken plus eine große Datenmenge in einem angeschlossenen Archivsystem.

Die Applikation selbst besteht aus einer Vielzahl von Komponenten, die ich hier nicht alle beschreiben kann. Wichtig sind immer die Startseite der Site Collection und die Startseiten der Sub-Sites. Hier werden in verschiedenen WebParts Übersichten über die Sub-Sites, über alle in der Site Collection vorhandenen Dokumente und bestimmte Dokumente aufgelistet.

Und genau hier versteckte sich auch das Problem. Große Site Collections können durchaus mehrere GB groß werden und ein paar Hundert Dokumente beinhalten. Und unter anderem auf solchen benötigt die Startseite zum Rendern meist mehrere Sekunden bis sogar zu einer Minute. Für die Akzeptanz des Gesamtsystems ist dies natürlich tödlich.

Für meine Tests und Verbesserungen hatte ich eine größere Site Collection mit 5GB Inhalt in 74 Sub-Sites und ein paar Hundert Dokumenten genommen.

Hinweis: Die folgenden Messungen sind nicht repräsentativ, da das Entwicklungssystem nicht sonderlich performant war. Allerdings spricht das Delta zur Verbesserung eine sehr deutliche Sprache und kann durchaus als Information für eigene Entwicklungen herangezogen werden.

Problem(e)

Viele hatten sich das System bereits angeschaut und die meisten haben es auf die Infrastruktur geschoben: “Der SQL Server ist dafür nicht ausgelegt”, “Sie brauchen mehr SharePoint Server” und “Das Storage darunter passt nicht” wurden geäußert.

Das Problem versteckte sich jedoch (wie meistens) ganz woanders: Es war die Art und Weise wie der Entwickler die benötigten Daten abgefragt hatte und wie die Gesamtstruktur der Sites aufgebaut war.

Beispiel 1: Iteratives Abfragen aller Web-Objekte

Ein WebPart ging durch alle Sub-Sites durch und suchte dort ein Element in einer speziellen versteckten Liste:

 

Durch die Instanziierung von allen SPWeb-Objekten bei jedem Aufruf ging jede Menge Zeit verloren und das WebPart benötigte bereits etwa 700-800ms. Dabei hatte der Entwickler bereits die Grundlagen für eine schnelle Abfrage gelegt, indem die Liste und das Element einen eigenen Content Type verwendeten. Dies ermöglichte eine sehr einfache Verbesserung durch die Verwendung eines SiteDataQuery:

Diese Änderung führte zu zwei Vorteilen:

  1. Die Ausführungszeit reduzierte sich auf 100ms
  2. Der Code ist sehr viel einfacher. Da GetSiteData() bereits eine DataTable produziert, musste diese für das anzeigende Grid nicht erst erzeugt werden.

Doch hier war noch nicht das Ende erreicht. Da die Informationen in den Listen sehr statisch sind, kann das Ergebnis ideal zwischengespeichert werden. Hierzu bietet SharePoint bereits den Object Cache an. Wenn bereits ein SiteDataQuery verwendet wird, ist die Nutzung des Object Cache sehr einfach realisierbar:

Hierdurch erhöhte sich die Ausführungszeit beim Erzeugen des Cache zwar auf etwa 130ms, jedoch konnten fortfolgende Requests innerhalb von 10ms bedient werden.

Beispiel 2: Iteratives Abfragen aller Web-Objekte

Auch das zweite Beispiel ist sehr ähnlich. Der erste Code-Teil iteriert durch die SPWeb-Objekte:

In der aufgerufenen Methode werden die Dokumente jedoch diesmal mittels eines CAML-Query abgefragt, allerdings auch wieder einzelne für jede Dokumentbibliothek:

Wie man erkennen kann, werden nach dem Erzeugen des Ergebnisses durch das CAML-Query wieder Elemente verworfen, die bestimmten Bedingungen nicht genügen. Idealerweise sollten diese in das CAML-Query aufgenommen werden, allerdings hat CAML auch gewissen Beschränkungen, die nicht alle Varianten von Bedingungen erlauben.

Folgender Code hat jedoch auch hier für eine Verbesserung der Laufzeit von 2.500ms auf etwa 10ms nach dem Cachen und 1.300ms zum Cachen gesorgt:

Beispiel 3: Verwendung von Contains statt separater Spalten

Die schwierigsten Performance-Probleme bei der Datenabfrage generell ergeben sich, wenn Strings nicht komplett vergleichbar sind, sondern nur Teilbereiche der Strings für Bedingungen herhalten müssen. Hier lässt sich auch nicht sehr viel optimieren, jede Art der Abfrage ist hier egal ob in SharePoint oder in anderen Systemen langsam. Um dies zu optimieren, müssend die Datenstrukturen verändert werden. Hinzu kommt in diesem Beispiel, dass die Kategorisierung von Dokumenten in SharePoint über Ordner vorgenommen wurde:

Das WebPart hat folgende Aufgabe: Es sucht Dokumente in der aktuellen Website, die in Ordner stehen, die im Namen “(sensible Daten)” stehen haben:

Hier wird rekursiv jeder Ordner auf oberster Ebene in jeder Dokumentbibliothek geprüft, ob der String “(sensible Daten)” im Titel vorkommt. Wenn er dazu noch Dokumente enthält wird er einer Liste hinzugefügt, die später für das WebPart gerendert wird. Dies führt zu einer Ausführungszeit von 500ms.

Auch hier habe ich versucht über ein CAML-Query das Ergebnis zu verbessern, allerdings ist die Verwendung der CAML-Methode “<Contains>” auch nicht sehr viel schneller. Folgender Code konnte eine Reduktion der Ausführungszeit von gerade einmal 150ms auf 350ms erreichen:

Man könnte hier auch wieder durch Verwendung des Object Cache das Ergebnis wahrscheinlich deutlich verbessern, sinnvoller wäre jedoch eine Anpassung der Datenstrukturen, z.B. durch Auslagerung des Flags “Sensible Daten” auf den Content Type des Dokuments, um diese mittels “<Eq>”-Methode per CAML zu finden. Doch diese Anpassung kann sehr viele Querverbindungen innerhalb der Gesamtlösung haben und ist damit mit sehr intensiven Tests verbunden. Zudem müssen alle Bestandsdaten modifiziert werden.

Fazit

In den vorigen Kapiteln habe ich ein paar Beispiele erläutert, wie viele Entwickler auf schnelle Art und Weise versuchen ein Problem zu lösen und den offensichtlichsten, aber damit nicht den schnellsten Weg gehen. Weitere Infos zu SPSiteDataQuery und CrossListQueryCache finden sich in einem interessanten Blog-Artikel von Vardhaman Deshpande: http://www.vrdmn.com/2012/11/querying-list-items-from-large-number.html.

In einem späteren Artikel werde ich versuchen auf Gründe für dieses Vorgehen eines Entwicklers und mögliche Lösungsansätze einzugehen.

Schreibe einen Kommentar