Viele Leute haben ASPState in ihrer Umgebung implementiert. Einige Leute verwenden die In-Memory-Option (InProc), aber normalerweise sehe ich, dass die Datenbankoption verwendet wird. Hier gibt es einige potenzielle Ineffizienzen, die Sie auf Websites mit geringem Volumen möglicherweise nicht bemerken, die sich jedoch auf die Leistung auswirken, wenn Ihr Webvolumen steigt.
Wiederherstellungsmodell
Stellen Sie sicher, dass ASPState auf einfache Wiederherstellung eingestellt ist – dies wird die Auswirkungen auf das Protokoll drastisch reduzieren, die durch das hohe Volumen an (vorübergehenden und weitgehend entfernbaren) Schreibvorgängen verursacht werden können, die wahrscheinlich hier landen:
ALTER DATABASE ASPState SET RECOVERY SIMPLE;
Normalerweise muss diese Datenbank nicht vollständig wiederhergestellt werden, zumal wenn Sie sich im Notfallwiederherstellungsmodus befinden und Ihre Datenbank wiederherstellen, das Letzte, worüber Sie sich Gedanken machen sollten, der Versuch ist, Sitzungen für Benutzer in Ihrer Web-App aufrechtzuerhalten – was wahrscheinlich der Fall sein wird schon lange weg sein, wenn Sie wiederhergestellt sind. Ich glaube nicht, dass ich jemals auf eine Situation gestoßen bin, in der eine Point-in-Time-Wiederherstellung eine Notwendigkeit für eine vorübergehende Datenbank wie ASPState war.
I/O minimieren/isolieren
Beim erstmaligen Einrichten von ASPState können Sie den -sstype c
verwenden und -d
Argumente zum Speichern des Sitzungsstatus in einer benutzerdefinierten Datenbank, die sich bereits auf einem anderen Laufwerk befindet (genau wie Sie es mit tempdb tun würden). Wenn Ihre tempdb-Datenbank bereits optimiert ist, können Sie auch den -sstype t
verwenden Streit. Diese werden ausführlich in den Dokumenten Session-State Modes und ASP.NET SQL Server Registration Tool auf MSDN erklärt.
Wenn Sie ASPState bereits installiert haben und festgestellt haben, dass Sie davon profitieren würden, es auf ein eigenes (oder zumindest ein anderes) Volume zu verschieben, können Sie einen kurzen Wartungszeitraum einplanen oder darauf warten und diesen Schritten folgen:
ALTER DATABASE ASPState SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ALTER DATABASE ASPState SET OFFLINE; ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState, FILENAME ='{neuer Pfad}\ASPState.mdf');ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState_log, FILENAME ='{neuer Pfad}\ASPState_log.ldf');An diesem Punkt müssen Sie die Dateien manuell nach
<new path>
verschieben , und dann können Sie die Datenbank wieder online bringen:ALTER DATABASE ASPState SET ONLINE; ALTER DATABASE ASPState SET MULTI_USER;Anwendungen isolieren
Es ist möglich, mehr als eine Anwendung auf dieselbe Sitzungsstatusdatenbank zu verweisen. Ich rate davon ab. Möglicherweise möchten Sie Anwendungen auf verschiedene Datenbanken verweisen, vielleicht sogar auf verschiedene Instanzen, um die Ressourcennutzung besser zu isolieren und größtmögliche Flexibilität für alle Ihre Webeigenschaften bereitzustellen.
Wenn Sie bereits mehrere Anwendungen haben, die dieselbe Datenbank verwenden, ist das in Ordnung, aber Sie sollten die Auswirkungen der einzelnen Anwendungen im Auge behalten. Rex Tang von Microsoft veröffentlichte eine nützliche Abfrage, um den von jeder Sitzung verbrauchten Speicherplatz zu sehen; Hier ist eine Änderung, die die Anzahl der Sitzungen und die Gesamt-/Durchschnittssitzungsgröße pro Anwendung zusammenfasst:
SELECT a.AppName, SessionCount =COUNT(s.SessionId), TotalSessionSize =SUM(DATALENGTH(s.SessionItemLong)), AvgSessionSize =AVG(DATALENGTH(s.SessionItemLong))FROM dbo.ASPStateTempSessions AS sLEFT OUTER JOIN dbo. ASPStateTempApplications AS a ON SUBSTRING(s.SessionId, 25, 8) =SUBSTRING(sys.fn_varbintohexstr(CONVERT(VARBINARY(8), a.AppId)), 3, 8) GROUP BY a.AppNameORDER BY TotalSessionSize DESC;Wenn Sie feststellen, dass Sie hier eine einseitige Verteilung haben, können Sie an anderer Stelle eine andere ASPState-Datenbank einrichten und stattdessen eine oder mehrere Anwendungen auf diese Datenbank verweisen.
Führen Sie freundlichere Löschungen durch
Der Code für
dbo.DeleteExpiredSessions
verwendet einen Cursor, der ein einzelnesDELETE
ersetzt in früheren Implementierungen. (Ich glaube, dies basierte größtenteils auf diesem Beitrag von Greg Low.)Ursprünglich lautete der Code:
CREATE PROCEDURE DeleteExpiredSessionsAS DECLARE @now DATETIME SET @now =GETUTCDATE() DELETE ASPState..ASPStateTempSessions WHERE Expires <@now RETURN 0GO(Und das kann immer noch der Fall sein, je nachdem, wo Sie die Quelle heruntergeladen haben oder wie lange Sie ASPState installiert haben. Es gibt viele veraltete Skripte zum Erstellen der Datenbank, obwohl Sie wirklich aspnet_regsql.exe verwenden sollten.)
Derzeit (ab .NET 4.5) sieht der Code so aus (weiß jemand, wann Microsoft anfangen wird, Semikolons zu verwenden?).
ALTER PROCEDURE [dbo].[DeleteExpiredSessions]AS SET NOCOUNT ON SET DEADLOCK_PRIORITY LOW DECLARE @now datetime SET @now =GETUTCDATE() CREATE TABLE #tblExpiredSessions ( SessionID nvarchar(88) NOT NULL PRIMARY KEY ) INSERT #tblExpiredSessions (SessionID ) SELECT SessionID FROM [ASPState].dbo.ASPStateTempSessions WITH (READUNCOMMITTED) WHERE Expires <@now IF @@ROWCOUNT <> 0 BEGIN DECLARE ExpiredSessionCursor CURSOR LOCAL FORWARD_ONLY READ_ONLY FOR SELECT SessionID FROM #tblExpiredSessions DECLARE @SessionID nvarchar(88) OPEN Expired.FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID WHILE @@FETCH_STATUS =0 BEGIN DELETE FROM [ASPState].dbo.ASPStateTempSes sions WHERE SessionID =@SessionID AND Expires <@now FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID END CLOSE ExpiredSessionCursor DEALLOCATE ExpiredSessionCursor END DROP TABLE #tblExpiredSessions RETURN 0Meine Idee ist, hier einen goldenen Mittelweg zu haben – versuchen Sie nicht, ALLE Zeilen auf einen Schlag zu löschen, aber spielen Sie auch nicht eins nach dem anderen. Löschen Sie stattdessen
n
Zeilen gleichzeitig in separaten Transaktionen – Reduzierung der Blockierungsdauer und Minimierung der Auswirkung auf das Protokoll:ALTER PROCEDURE dbo.DeleteExpiredSessions @top INT =1000ASBEGIN SET NOCOUNT ON; DECLARE @now DATETIME, @c INT; SELECT @now =GETUTCDATE(), @c =1; TRANSAKTION BEGINNEN; WHILE @c <> 0 BEGIN; WITH x AS ( SELECT TOP (@top) SessionId FROM dbo.ASPStateTempSessions WHERE Expires <@now ORDER BY SessionId ) DELETE x; SET @c =@@ROWCOUNT; WENN @@TRANCOUNT =1 BEGIN COMMIT TRANSACTION; TRANSAKTION BEGINNEN; END END IF @@TRANCOUNT =1 BEGIN COMMIT TRANSACTION; ENDENDGOSie werden mit
TOP
experimentieren wollen abhängig davon, wie ausgelastet Ihr Server ist und welche Auswirkungen dies auf Dauer und Sperrung hat. Sie sollten auch die Implementierung einer Snapshot-Isolierung in Betracht ziehen – dies erzwingt einige Auswirkungen auf tempdb, kann jedoch die von der App sichtbare Blockierung verringern oder beseitigen.Außerdem standardmäßig der Job
ASPState_Job_DeleteExpiredSessions
läuft jede Minute. Erwägen Sie, das etwas zurückzunehmen – reduzieren Sie den Zeitplan auf vielleicht alle 5 Minuten (und auch hier hängt viel davon ab, wie ausgelastet Ihre Anwendungen sind und wie Sie die Auswirkungen der Änderung testen). Und auf der anderen Seite stellen Sie sicher, dass es aktiviert ist – sonst wächst und wächst deine Session-Tabelle ungebremst.Sitzungen seltener berühren
Jedes Mal, wenn eine Seite geladen wird (und wenn die Web-App nicht korrekt erstellt wurde, möglicherweise mehrmals pro Seitenladevorgang), wird die gespeicherte Prozedur
dbo.TempResetTimeout
aufgerufen, wodurch sichergestellt wird, dass das Timeout für diese bestimmte Sitzung verlängert wird, solange sie weiterhin Aktivität generieren. Auf einer ausgelasteten Website kann dies zu einem sehr hohen Volumen an Aktualisierungsaktivitäten für die Tabelledbo.ASPStateTempSessions
führen . Hier ist der aktuelle Code fürdbo.TempResetTimeout
:ALTER PROCEDURE [dbo].[TempResetTimeout] @id tSessionId AS UPDATE [ASPState].dbo.ASPStateTempSessions SET Expires =DATEADD(n, Timeout, GETUTCDATE()) WHERE SessionId =@id RETURN 0Stellen Sie sich nun vor, Sie haben eine Website mit 500 oder 5.000 Benutzern, und alle klicken wie verrückt von Seite zu Seite. Dies ist wahrscheinlich eine der am häufigsten aufgerufenen Operationen in jeder ASPState-Implementierung, und während die Tabelle auf
SessionId
verschlüsselt ist – daher sollte die Auswirkung einer einzelnen Aussage minimal sein – insgesamt kann dies eine erhebliche Verschwendung sein, auch im Protokoll. Wenn Ihr Sitzungs-Timeout 30 Minuten beträgt und Sie das Timeout für eine Sitzung aufgrund der Art der Web-App alle 10 Sekunden aktualisieren, welchen Sinn hat es dann, dies 10 Sekunden später noch einmal zu tun? Solange diese Sitzung irgendwann vor Ablauf der 30 Minuten asynchron aktualisiert wird, gibt es keinen Nettounterschied für den Benutzer oder die Anwendung. Also dachte ich, dass Sie eine skalierbarere Methode implementieren könnten, um Sitzungen zu "berühren", um ihre Timeout-Werte zu aktualisieren.Eine Idee, die ich hatte, war, eine Service-Broker-Warteschlange zu implementieren, damit die Anwendung nicht auf den eigentlichen Schreibvorgang warten muss – sie ruft
dbo.TempResetTimeout
auf gespeicherte Prozedur, und dann übernimmt die Aktivierungsprozedur asynchron. Dies führt jedoch immer noch zu viel mehr Aktualisierungen (und Protokollaktivitäten), als wirklich notwendig sind.Eine bessere Idee, IMHO, ist es, eine Warteschlangentabelle zu implementieren, in die Sie nur einfügen, und nach einem Zeitplan (so dass der Prozess einen vollständigen Zyklus in einer kürzeren Zeit als dem Timeout abschließt), würde es nur das Timeout für jede Sitzung aktualisieren es sieht einmal , egal wie oft sie *versucht* haben, ihre Zeitüberschreitung innerhalb dieser Zeitspanne zu aktualisieren. Eine einfache Tabelle könnte also so aussehen:
CREATE TABLE dbo.SessionStack( SessionId tSessionId, -- nvarchar(88) - natürlich mussten sie Aliastypen verwenden EventTime DATETIME, Processed BIT NOT NULL DEFAULT 0); CREATE CLUSTERED INDEX und ON dbo.SessionStack(EventTime);GOUnd dann würden wir die Bestandsprozedur ändern, um die Sitzungsaktivität auf diesen Stack zu schieben, anstatt die Sitzungstabelle direkt zu berühren:
ALTER PROCEDURE dbo.TempResetTimeout @id tSessionIdASBEGIN SET NOCOUNT ON; INSERT INTO dbo.SessionStack(SessionId, EventTime) SELECT @id, GETUTCDATE();ENDGODer gruppierte Index befindet sich auf
smalldatetime
Spalte, um Seitenaufteilungen zu verhindern (auf Kosten einer heißen Seite), da die Ereigniszeit für eine Sitzungsberührung immer monoton ansteigt.Dann benötigen wir einen Hintergrundprozess, um regelmäßig neue Zeilen in
dbo.SessionStack
zusammenzufassen und aktualisieren Siedbo.ASPStateTempSessions
entsprechend.CREATE PROCEDURE dbo.SessionStack_ProcessASBEGIN SET NOCOUNT ON; -- es sei denn, Sie möchten tSessionId zu model oder manuell zu tempdb hinzufügen -- nach jedem Neustart müssen wir hier den Basistyp verwenden:CREATE TABLE #s(SessionId NVARCHAR(88), EventTime SMALLDATETIME); -- der Stack ist jetzt Ihr Hotspot, also schnell ein- und aussteigen:UPDATE dbo.SessionStack SET Processed =1 OUTPUT inserted.SessionId, inserted.EventTime INTO #s WHERE Processed IN (0,1) -- falls einer zuletzt fehlgeschlagen ist Zeit UND EventTimeVielleicht möchten Sie mehr Transaktionskontrolle und Fehlerbehandlung hinzufügen – ich präsentiere nur eine spontane Idee, und Sie können so verrückt werden, wie Sie wollen. :-)
Vielleicht möchten Sie einen nicht gruppierten Index zu
dbo.SessionStack(SessionId, EventTime DESC)
hinzufügen um den Hintergrundprozess zu erleichtern, aber ich denke, es ist besser, selbst die kleinsten Leistungssteigerungen auf den Prozess zu konzentrieren, auf den die Benutzer warten (jedes Laden einer Seite), als auf einen, auf den sie nicht warten (den Hintergrundprozess). Daher zahle ich lieber die Kosten für einen potenziellen Scan während des Hintergrundprozesses als für die zusätzliche Indexpflege bei jeder einzelnen Einfügung. Wie beim geclusterten Index in der #temp-Tabelle gibt es hier eine Menge "es kommt darauf an", also sollten Sie vielleicht mit diesen Optionen spielen, um zu sehen, wo Ihre Toleranz am besten funktioniert.Sofern die Häufigkeit der beiden Vorgänge nicht drastisch unterschiedlich sein muss, würde ich dies als Teil der
ASPState_Job_DeleteExpiredSessions
einplanen job (und erwägen Sie gegebenenfalls, diesen Job umzubenennen), damit sich diese beiden Prozesse nicht gegenseitig mit Füßen treten.Eine letzte Idee hier, wenn Sie feststellen, dass Sie noch weiter skalieren müssen, besteht darin, mehrere
SessionStack
zu erstellen Tabellen, wobei jede für eine Teilmenge von Sitzungen verantwortlich ist (z. B. gehasht auf das erste Zeichen derSessionId
). Dann können Sie jede Tabelle der Reihe nach verarbeiten und diese Transaktionen so viel kleiner halten. Tatsächlich könnten Sie etwas Ähnliches auch für den Löschjob tun. Wenn Sie es richtig gemacht haben, sollten Sie in der Lage sein, diese in einzelne Jobs zu stecken und sie gleichzeitig auszuführen, da die DML – theoretisch – völlig unterschiedliche Sätze von Seiten betreffen sollte.Schlussfolgerung
Das sind meine bisherigen Vorstellungen. Ich würde gerne von Ihren Erfahrungen mit ASPState hören:Welche Größenordnung haben Sie erreicht? Welche Art von Engpässen haben Sie beobachtet? Was haben Sie getan, um sie zu mindern?