Mange mennesker har implementeret ASPState i deres miljø. Nogle mennesker bruger muligheden i hukommelsen (InProc), men normalt ser jeg, at databaseindstillingen bliver brugt. Der er nogle potentielle ineffektiviteter her, som du måske ikke bemærker på websteder med lav volumen, men som vil begynde at påvirke ydeevnen, efterhånden som din webvolumen stiger.
Gendannelsesmodel
Sørg for, at ASPState er indstillet til simpel gendannelse – dette vil dramatisk reducere indvirkningen på loggen, der kan være forårsaget af den høje mængde (forbigående og stort set engangs) skrivninger, der sandsynligvis vil gå her:
ALTER DATABASE ASPState SET RECOVERY SIMPLE;
Normalt behøver denne database ikke at være i fuld gendannelse, især da hvis du er i disaster recovery mode og gendanner din database, er den sidste ting du skal bekymre dig om at forsøge at vedligeholde sessioner for brugere i din webapp – som sandsynligvis vil være for længst væk, når du har genoprettet. Jeg tror aldrig, jeg er stødt på en situation, hvor punkt-i-tidsgendannelse var en nødvendighed for en forbigående database som ASPState.
Minimer/isoler I/O
Når du opsætter ASPState indledningsvis, kan du bruge -sstype c
og -d
argumenter til at gemme sessionstilstand i en brugerdefineret database, der allerede er på et andet drev (ligesom du ville med tempdb). Eller, hvis din tempdb-database allerede er optimeret, kan du bruge -sstype t
argument. Disse er forklaret i detaljer i dokumenterne Session-State Modes og ASP.NET SQL Server Registration Tool på MSDN.
Hvis du allerede har installeret ASPState, og du har besluttet, at du ville have gavn af at flytte den til sin egen (eller i det mindste en anden) volumen, så kan du planlægge eller vente på en kort vedligeholdelsesperiode og følge disse trin:
ALTER DATABASE ASPState SET SINGLE_USER MED ROLLBACK IMMEDIATE; ÆNDRE DATABASE ASPState SÆT OFFLINE; ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState, FILENAME ='{ny sti}\ASPSstate.mdf');ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState_log, FILENAME ='{ny sti}\ASPSstate_log.ldf'); før>På dette tidspunkt skal du manuelt flytte filerne til
, og så kan du bringe databasen online igen: ALTER DATABASE ASPState SET ONLINE; ALTER DATABASE ASPState SET MULTI_USER;Isoler applikationer
Det er muligt at pege mere end én applikation på den samme sessionstilstandsdatabase. Jeg fraråder dette. Du vil måske pege applikationer på forskellige databaser, måske endda på forskellige instanser, for bedre at isolere ressourceforbrug og give den største fleksibilitet til alle dine webejendomme.
Hvis du allerede har flere applikationer, der bruger den samme database, er det okay, men du vil gerne holde styr på, hvilken effekt hver applikation kan have. Microsofts Rex Tang udgav en nyttig forespørgsel for at se pladsforbruget af hver session; her er en ændring, der vil opsummere antallet af sessioner og samlet/gennemsnitlig sessionsstørrelse pr. applikation:
SELECT a.AppName, SessionCount =COUNT(s.SessionId), TotalSessionSize =SUM(DATALENGTH(s.SessionItemLong)), AvgSessionSize =AVG(DATALENGTH(s.SessionItemLong))FROM dbo.ASPStateTempSessions AS sLEFT YDER 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;Hvis du opdager, at du har en skæv distribution her, kan du oprette en anden ASPState-database et andet sted og pege en eller flere applikationer mod den database i stedet.
Foretag mere brugervenlige sletninger
Koden til
dbo.DeleteExpiredSessions
bruger en markør, der erstatter en enkeltDELETE
i tidligere implementeringer. (Dette, tror jeg, var i vid udstrækning baseret på dette indlæg af Greg Low.)Oprindeligt var koden:
OPRET PROCEDURE DeleteExpiredSessionsAS DECLARE @now DATETIME SET @now =GETUTCDATE() SLET ASPState..ASPStateTempSessions WHERE Expires <@now RETURN 0GO(Og det kan det stadig være, afhængigt af hvor du downloadede kilden, eller hvor længe siden du installerede ASPState. Der er mange forældede scripts derude til at oprette databasen, selvom du virkelig burde bruge aspnet_regsql.exe.)
I øjeblikket (fra .NET 4.5) ser koden sådan ud (nogen ved, hvornår Microsoft vil begynde at bruge semikolon?).
ÆNDRE PROCEDURE [dbo].[DeleteExpiredSessions]SOM INDSTILLET NOCOUNT ON SET DEADLOCK_PRIORITY LOW DECLARE @now datetime SET @now =GETUTCDATE() OPRET TABEL #tblExpiredSessions (SessionsID nvarchar(88) PRIMARY INSERT KEYtblsID)Expired ) VÆLG SessionsID FRA [ASPState].dbo.ASPSateTempSessions WITH (READUNCOMMITTED) WHERE Expires <@now IF @@ROWCOUNT <> 0 BEGIN DECLARE ExpiredSessionCursor CURSOR LOCAL FORWARD_ONLY READ_ONLY FORSession NÆSTE FRA ExpiredSessionCursor INTO @SessionID MENS @@FETCH_STATUS =0 BEGYND SLET FRA [ASPSate].dbo.ASPSateTempSes sions WHERE SessionID =@SessionID AND Expires <@now FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID END CLOSE ExpiredSessionCursor DEALLOCATE ExpiredSessionCursor END DROP TABLE #tblExpiredSessions RETURN 0Min idé er at have et lykkeligt medie her – prøv ikke at slette ALLE rækker i ét hug, men spil heller ikke en-efter-en whack-a-mole. Slet i stedet
n
rækker ad gangen i separate transaktioner – reducerer længden af blokering og minimerer også indvirkningen på loggen:ÆNDRE PROCEDURE dbo.DeleteExpiredSessions @top INT =1000ASBEGIN INDSTILL INGEN TÆLLING TIL; DECLARE @nu DATOTIME, @c INT; VÆLG @nu =GETUTCDATE(), @c =1; START TRANSAKTIONEN; WHILE @c <> 0 BEGIN;WITH x AS ( SELECT TOP (@top) SessionId FROM dbo.ASPSateTempSessions WHERE Expires <@now ORDER BY SessionId ) SLET x; SET @c =@@ RÆKEL; HVIS @@TRANCOUNT =1 BEGYND TRANSAKTIONEN; START TRANSAKTIONEN; SLUT SLUT HVIS @@TRANCOUNT =1 BEGYND TRANSAKTIONEN; ENDENDGODu vil gerne eksperimentere med
TOP
afhængig af hvor travlt din server er, og hvilken indflydelse det har på varighed og låsning. Du kan også overveje at implementere snapshot-isolering – dette vil tvinge en vis indvirkning til tempdb, men kan reducere eller eliminere blokering set fra appen.Som standard er jobbet også
ASPState_Job_DeleteExpiredSessions
kører hvert minut. Overvej at slå det lidt tilbage – reducer tidsplanen til måske hvert 5. minut (og igen, meget af dette vil komme ned på, hvor travlt dine applikationer er og teste virkningen af ændringen). Og på bagsiden skal du sørge for, at den er aktiveret – ellers vil din sessionstabel vokse og vokse ukontrolleret.Berøringssessioner sjældnere
Hver gang en side indlæses (og, hvis webappen ikke er blevet oprettet korrekt, muligvis flere gange pr. sideindlæsning), er den lagrede procedure
dbo.TempResetTimeout
kaldes, hvilket sikrer, at timeoutet for den pågældende session forlænges, så længe de fortsætter med at generere aktivitet. På et travlt websted kan dette forårsage en meget høj mængde opdateringsaktivitet i forhold til tabellendbo.ASPSateTempSessions
. Her er den aktuelle kode fordbo.TempResetTimeout
:ÆNDRINGSPROCEDURE [dbo].[TempResetTimeout] @id tSessionId AS UPDATE [ASPSate].dbo.ASPStateTempSessions SET Expires =DATEADD(n, Timeout, GETUTCDATE()) WHERE SessionId =@id RETURN 0Forestil dig nu, at du har et websted med 500 eller 5.000 brugere, og de klikker alle vanvittigt fra side til side. Dette er sandsynligvis en af de hyppigst kaldede operationer i enhver ASPState-implementering, og mens tabellen er indtastet på
SessionId
– så virkningen af enhver individuel erklæring bør være minimal – samlet set kan dette være væsentligt spild, herunder på loggen. Hvis din sessionstimeout er 30 minutter, og du opdaterer timeouten for en session hvert 10. sekund på grund af webappens karakter, hvad er meningen med at gøre det igen 10 sekunder senere? Så længe den session opdateres asynkront på et tidspunkt, før de 30 minutter er gået, er der ingen nettoforskel for brugeren eller applikationen. Så jeg tænkte, at du kunne implementere en mere skalerbar måde at "berøre" sessioner for at opdatere deres timeoutværdier.En idé, jeg havde, var at implementere en servicemæglerkø, så applikationen ikke skal vente på, at den faktiske skrivning sker – den kalder
dbo.TempResetTimeout
lagret procedure, og derefter overtager aktiveringsproceduren asynkront. Men dette fører stadig til mange flere opdateringer (og logaktivitet), end der virkelig er nødvendigt.En bedre idé, IMHO, er at implementere en kø-tabel, som du kun indsætter i, og på en tidsplan (sådan at processen fuldfører en fuld cyklus på nogen tid, der er kortere end timeouten), vil den kun opdatere timeoutet for enhver session. ser en gang , uanset hvor mange gange de *forsøgte* at opdatere deres timeout inden for det tidsrum. Så en simpel tabel kan se sådan ud:
CREATE TABLE dbo.SessionStack( SessionId tSessionId, -- nvarchar(88) - selvfølgelig skulle de bruge aliastyper EventTime DATETIME, Processed BIT NOT NULL DEFAULT 0); OPRET CLUSTERED INDEX og PÅ dbo.SessionStack(EventTime);GOOg så ville vi ændre lagerproceduren for at skubbe sessionsaktivitet ind på denne stak i stedet for at berøre sessionstabellen direkte:
ÆNDRINGSPROCEDURE dbo.TempResetTimeout @id tSessionIdASBEGIN SET NOCOUNT ON; INSERT INTO dbo.SessionStack(SessionId, EventTime) SELECT @id, GETUTCDATE();ENDGODet klyngede indeks er på
smalldatetime
kolonne for at forhindre sideopdelinger (til den potentielle pris af en varm side), da begivenhedstiden for en sessionsberøring altid vil være monotont stigende.Så har vi brug for en baggrundsproces for med jævne mellemrum at opsummere nye rækker i
dbo.SessionStack
og opdaterdbo.ASPStateTempSessions
tilsvarende.OPRET PROCEDURE dbo.SessionStack_ProcessASBEGIN SET NOCOUNT ON; -- medmindre du vil tilføje tSessionId til model eller manuelt til tempdb -- efter hver genstart, bliver vi nødt til at bruge basistypen her:CREATE TABLE #s(SessionId NVARCHAR(88), EventTime SMALLDATETIME); -- stakken er nu dit hotspot, så kom hurtigt ind og ud:OPDATERING dbo.SessionStack SET Processed =1 OUTPUT inserted.SessionId, inserted.EventTime INTO #s WHERE Processed IN (0,1) -- i tilfælde af, at nogen fejlede sidst time AND EventTimeDu vil måske tilføje mere transaktionskontrol og fejlhåndtering omkring dette – jeg præsenterer bare en idé uden for manchetten, og du kan blive lige så vild med dette, som du vil. :-)
Du tror måske, du vil tilføje et ikke-klynget indeks på
dbo.SessionStack(SessionId, EventTime DESC)
for at lette baggrundsprocessen, men jeg tror, det er bedre at fokusere selv de mest minimale præstationsgevinster på den proces, som brugerne venter på (hver sideindlæsning) i modsætning til en, de ikke venter på (baggrundsprocessen). Så jeg vil hellere betale omkostningerne ved en potentiel scanning under baggrundsprocessen end at betale for yderligere indeksvedligeholdelse under hver enkelt indsættelse. Som med det klyngede indeks på #temp-tabellen, er der meget "det afhænger af" her, så du vil måske lege med disse muligheder for at se, hvor din tolerance fungerer bedst.Medmindre hyppigheden af de to operationer skal være drastisk forskellige, ville jeg planlægge dette som en del af
ASPSate_Job_DeleteExpiredSessions
job (og overvej at omdøbe det job, hvis det er tilfældet), så disse to processer ikke tramper på hinanden.En sidste idé her, hvis du finder ud af, at du har brug for at skalere endnu mere ud, er at oprette flere
SessionStack
tabeller, hvor hver enkelt er ansvarlig for et undersæt af sessioner (f.eks. hashed på det første tegn iSessionId
). Så kan du behandle hver tabel efter tur og holde disse transaktioner så meget mindre. Faktisk kunne du også gøre noget lignende for sletningsopgaven. Hvis det gøres korrekt, bør du være i stand til at placere disse i individuelle job og køre dem samtidig, da DML'en – i teorien – burde påvirke helt andre sæt sider.Konklusion
Det er mine ideer indtil videre. Jeg ville elske at høre om dine erfaringer med ASPState:Hvilken type skala har du opnået? Hvilken slags flaskehalse har du observeret? Hvad har du gjort for at afbøde dem?