Marco Savini

Reduzierung unnötiger Datenbankzugriffe im Microsoft Commerce Server 2007


Geposted von Marco Savini am 13.03.2009, 12:57

Der Einsatz von Microsoft Commerce Server 2007 für E-Commerce Projekte bietet eine Fülle von Vorteilen und Möglichkeiten. Deshalb kommt dieses Produkt auch bei mehreren komplexeren Shop-Lösungen zum Einsatz.

Letzte Woche haben wir uns mit einem seltsamen Verhalten auseinandergesetzt, deren Beschreibung und Lösungsansätze nachfolgend beschrieben werden.

Beschreibung Problem

Das Problem begann mit der Analyse, weshalb der SQL Server einer so grosse Last ausgesetzt ist, obwohl eine erhebliche Anzahl von Objekten auf dem Frontend gecacht werden. Bei der Betrachtung der Top10 Queries stellte sich heraus, dass eine Unmenge von scheinbar unnötigen Zugriffen auf die UserObject Tabelle in der Profiles Datenbank stattfinden. Diese Abfrage, die sehr häufig stattfindet, sieht in etwa folgendermassen aus (mit wechselnder GUID):

SELECT  "i_access_level_id","u_account_status","u_addresses","u_application_name","u_campaign_history", "b_change_password", "u_credit_cards","dt_csadapter_date_last_changed", "dt_date_address_list_last_changed","dt_date_created", "dt_date_credit_card_list_last_changed", "dt_date_last_changed", "dt_last_logon","dt_date_last_password_changed","dt_date_registered", "b_direct_mail_opt_out","u_email_address", "b_express_checkout","u_fax_extension", "u_fax_number", "u_first_name","i_keyindex", "u_language", "dt_last_activity_date", "dt_last_lockedout_date","u_last_name", "u_logon_error_dates", "u_org_id","u_password_answer_error_dates", "u_preferred_address","u_tel_extension","u_tel_number", "u_user_catalog_set", "u_user_id_changed_by","u_user_security_password","u_user_type"  FROM dbo."UserObject" WHERE "u_user_id" = N'{a37b39e8-9310-4712-9f9c-6344fc963f19}'

Erstaunlich daran ist, dass das Tupel mit der GUID nach der gesucht wird, gar nicht in der Relation vorhanden ist. Ein erstes Eintauchen in die Tiefen des Commerce Server Codes eröffnete uns, dass beim Zugriff auf das CommerceContext.Current.UserProfile Objekt diese Query aufgerufen wird. Ausserdem wird auf dieses Objekt in den verschiedensten Situationen zugegriffen. Aber woher stammt diese GUID, die ja scheinbar nicht existiert und weshalb wird der Aufruf auch gemacht, wenn der Benutzer gar nicht authentifiziert ist? Hier ist das Problem, dass automatisch pro Session eine GUID generiert wird, auch wenn der Benutzer gar nicht bekannt ist. Dies wird natürlich benötigt, um z.B. anonyme Warenkörbe zu erlauben.

Die erste naheliegende Aktion war es, den Zugriff auf das CommerceContext.Current.UserProfile Objekt zu kapseln, so dass der SQL Aufruf nur ausgeführt wird, wenn der Benutzer im aktuellen HTTP Kontext eine Identität hat, das heisst, er sich am System angemeldet hat. Ansonsten kann man immer null zurückgeben, da der Zugriff ohnehin immer null zurückgibt (der Benutzer existiert ja nicht).

Das Resultat dieser ersten Verbesserung war etwas enttäuschend; es wurden nur unwesentlich weniger Aufrufe auf dem SQL Server ausgeführt. Vorallem Seiten mit vielen Produkten generierten nach wie vor sehr viele Aufrufe. Deshalb haben wir dann die Funktionalität zum Holen eines Produktes unter die Lupe genommen.

Im der Klasse CatalogHelper, welche mit der StarterSite mitgeliefert wird, wird pro Produkteanfrage jeweils geprüft, ob der aktuelle Benutzer Zugriff auf dieses Produkt hat (CatalogHelper::IsCatalogInUserCatalogSet()). Diese Überprüfung findet anhand der Catalog Sets statt. Catalog Sets werden normalerweise verwendet, um Produkte an bestimmte Zielgruppen zu binden und so beispielsweise authentifizierten Wiederverkäufern andere Produkte zu anderen Konditionen darzustellen. Diese Überprüfung ist ziemlich zeitaufwendig gemacht, da jeder Aufruf die Catalog Sets jedes einzelnen Produkts prüft. Pro Überprüfung findet wird dann in den Tiefen des Commerce Server Codes der aktuelle Benutzer identifiziert, das heisst, es findet wieder eine Anfrage auf die Datenbank statt; und wieder, auch wenn der Benutzer gar nicht authentifiziert ist. Diese Anfrage wird jedoch pro Produkt mehrfach gemacht und auf Seiten mit vielen Produkten jeweils mehrfach.

Da im aktuellen Projekt, welches analysiert wurde, gar keine Catalog Sets verwendet werden, haben wir die Abfrage abgekürzt, so dass keine Überprüfungen stattfinden. Dies hat dann endlich die erhoffte starke Reduzierung der Abfragen mit sich gebracht! Zur Zeit werden bedeutend weniger Datenbankabfragen pro Request abgesetzt.

Fazit

Manchmal muss man Probleme nicht nur Top-Down anschauen, sondern sich auch damit beschäftigen, was genau schlussendlich passiert. Diese Problemstellung war ein klassisches Beispiel dafür; erst die Analyse der Datenbanklast auf einer Datenbank, mit der ein normaler Commerce Server Entwickler nur selten direkt in Berührung kommt, hat das Problem zum Vorschein gebracht.

Allgemein kann man festhalten, dass

  • Die Methode CatalogHelper::IsCatalogInUserCatalogSet() deaktiviert werden sollte, wenn man keine Catalog Sets benötigt. Wenn man sie benötigt, macht es wohl Sinn, ein Caching einzuführen, welches pro Benutzer/Produkt die Ergebnisse cacht oder die Abfrage nur bei klar eingrenzbaren Produkten ausführt.
  • Der Zugriff auf das CommerceContext.Current.UserProfile sollte möglichst gekapselt stattfinden.

Erschwerend kommt hinzu, dass das Problem mit der Anzahl von Katalogen ansteigt (exponentiell, da pro Produkt n Kataloge überprüft werden). Das heisst, dass im Laufe des Betriebs die Last zunimmt.

Für unser Projekt werden wir nun die Änderungen aufschalten, und die Performance Indikatoren überprüfen, inwiefern auch andere Messwerte wie beispielsweise die Renderzeit der Seite, von der Anpassung profitieren können.

0 Kommentare | Trackback Url

Kommentare
Ihr Kommentar zum Blog-Eintrag





Feedburner
aseantic RSS Feed
Feedburner RSS Feed
Add to Favorites
Add to Delicious
aseantic @ Facebook
Add to Technorati Favorites
Bookmark and Share
Creative Commons