Bei einem meiner letzten Kundenprojekte kam kürzlich die Meldung rein, die Anwendung würde nicht mehr und/oder nur langsam reagieren. Kann nicht sein, dacht ich mir. Eine kurzer Blick in den IIS und die offenen Anfragen belehrte mich dann doch recht beeindruckend – es waren einige hunderte, gar tausende Anfragen offen. Ein paar Tests und einiges an Recherche später war der Übeltäter schnell ausgemacht. Sein Name – HttpSessionState.
Unwissenheit und Faulheit brachte mich dazu das Session-Objekt der aktuellen Sitzung als Caching-Mechanismus zu missbrauchen. Der Plan: Das Ergebnis sich oft wiederholender und statischer Datenbankabfragen zwischen zu speichern um dadurch die Anzahl der Zugriffe zu minimieren und die schlussendlich die Performance zu verbessern. Das Problem: Eine Anfrage an eine .NET-Webanwendung führt zu einem Session-Lock der erst nach Auslieferung der Antwort wieder gelöst wird. IIS arbeitet Anfragen die das Session-Objekt nutzen also nicht parallel sondern nacheinander ab.
Um zu dieser Erkenntnis zu kommen musste ich das Thema Caching jedoch erst einmal verstehen.
Die Session
Eine Session ist definiert durch eine bestimmte Zeit die zwischen einer Web-Anwendung und dem Benutzer geteilt wird.Jeder Benutzer einer Web-Anwendung hat eine eigene Session. In einer Session können Elemente/Objekte gespeichert werden die sich dann als diese für den Nutzer definiert. Zugriff auf die gespeicherten Werte erfolgt durch einen eindeutigen Schlüssel ähnlich einer Hash-Tabelle. Gelöscht wird eine Session durch die Anwendung selbst oder einem definierbaren TimeOut.
Session["key"] = value;
Das Session-Objekt sollte nur in Bezug auf den aktuellen Benutzer verwendet werden. Es speichert Informationen auf der Server-Seite. Der Unterschied zum Cache ist, dass der Cache auf globaler bzw. Anwendungsebene verfügbar ist. Jede Anfrage verwendet also den Selben Cache, unabhängig vom Benutzer. Es können zwar beliebig große Objekte in einem Session-Objekt gespeichert werden, jedoch belastet, besonders bei einem hohen Benutzer-Aufkommen extrem den Speicher, da jeder Benutzer eine eigene Kopie im Speicher vorhällt und außerdem wie eingangs bereits erwähnt, bei jeder Anfrage ein Session-Lock auftritt. Dieser kann nur umgangen werden, wenn man mittels Anfrage nur lesend auf die Session zugreift.
Ein möglicher Workaround wäre also den SessionState auf ReadOnly zu setzen.
<% @Page EnableSessionState="ReadOnly" %>
Dies funktioniert jedoch nur, wenn die Anfrage niemals schreibenden Zugriff auf das Session-Objekt benötigt. Darauf wollte ich mich nicht verlassen, also ging ich auf die Suche nach einer anderen, besseren Lösung um komplexe Datenstrukturen richtig zwischen zu speichern.
Der ViewState
Der ViewState ist ein verstecktes Formularfeld innerhalb einer .NET-Seite. Änderungen werden mittels Postback mitgeteilt. Alle Server-Steuerelemente haben einen eigenen ViewState der mittels EnableViewState-Eigenschaft gesteuert werden kann.
Befinden sich viele Daten im ViewState, büst eine Website an Performance ein, da bei jedem PostBack der gesamte ViewState zurück an den Server geschickt wird. Beliebige Daten können im ViewState gespeichert werden, jedoch gilt zu beachten das der ViewState nur bis zum Ende des LifeCycles der Seite bestand hat.
ViewState["key"] = value;
Der Cache
Den Cache kann man sich als Erinnerung der Anwendung vorstellen. Aus ihm kommt der gesamte Quellcode und in Ihm können beliebige Daten gespeichert werden die jederzeit und global wiederverwendet werden könne. Die perfekte Stelle also um das Ergebnis von komplexen Berechnungen zwischen zu speichern, vor allem wenn deren Datenbasis sich selten ändert. Ein Cache-Objekt wird entweder durch die Anwendung oder durch ein definierbares Ablaufdatum gelöscht und Daten können direkt aus dem Cache gebunden werden.
public void AddItemToCache(string key, object value) { if (Cache[key] == null){ Cache.Add(key, value, null, DateTime.Now.AddSeconds(60), TimeSpan.Zero, CacheItemPriority.High, null); } } public void RemoveItemFromCache(string key) { if(Cache[key] != null) Cache.Remove(key); }
Fazit
Für meine Anforderung, nämlich Daten aus Performancegründen zwischen zu speichern, kommt eigentlich nur der Cache in Frage. Einmal hinterlegte Daten können Benutzerunabhängig geladen werden und beschleunigen so die Anwendung extrem. Es muss nur darauf geachtet werden das die Objekte im Cache bei einer Änderung in der Anwendung auch aktualisiert/entfernt/hinzugefügt werden, damit der Cache immer auf aktuellem Stand ist.