sql >> Database >  >> RDS >> Database

Mogelijke verbeteringen aan ASPState

Veel mensen hebben ASPState in hun omgeving geïmplementeerd. Sommige mensen gebruiken de in-memory optie (InProc), maar meestal zie ik dat de database-optie wordt gebruikt. Er zijn hier enkele potentiële inefficiënties die u misschien niet opmerkt op sites met een laag volume, maar die van invloed zullen zijn op de prestaties naarmate uw webvolume toeneemt.

Herstelmodel

Zorg ervoor dat ASPState is ingesteld op eenvoudig herstel - dit zal de impact op het logboek drastisch verminderen die kan worden veroorzaakt door het grote aantal (tijdelijke en grotendeels wegwerpbare) schrijfbewerkingen die waarschijnlijk hier terechtkomen:

ALTER DATABASE ASPState SET RECOVERY SIMPLE;

Gewoonlijk hoeft deze database niet volledig te worden hersteld, vooral omdat als u zich in de noodherstelmodus bevindt en uw database herstelt, het laatste waar u zich zorgen over moet maken, is proberen sessies te behouden voor gebruikers in uw webapp - die waarschijnlijk lang weg zijn tegen de tijd dat je hersteld bent. Ik denk niet dat ik ooit een situatie ben tegengekomen waarin herstel op een bepaald tijdstip een noodzaak was voor een tijdelijke database zoals ASPState.

Minimaliseren / isoleren van I/O

Wanneer u ASPState aanvankelijk instelt, kunt u de -sstype c . gebruiken en -d argumenten om de sessiestatus op te slaan in een aangepaste database die al op een andere schijf staat (net zoals je zou doen met tempdb). Of, als uw tempdb-database al is geoptimaliseerd, kunt u de -sstype t gebruiken argument. Deze worden in detail uitgelegd in de Session-State Modes en ASP.NET SQL Server Registration Tool-documenten op MSDN.

Als u ASPState al hebt geïnstalleerd en u hebt vastgesteld dat u er baat bij zou hebben het naar zijn eigen (of in ieder geval een ander) volume te verplaatsen, kunt u een korte onderhoudsperiode plannen of wachten en deze stappen volgen:

ALTER DATABASE ASPState SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
 
ALTER DATABASE ASPState SET OFFLINE;
 
ALTER DATABASE ASPState MODIFY FILE (NAME = ASPState,     FILENAME = '{new path}\ASPState.mdf');
ALTER DATABASE ASPState MODIFY FILE (NAME = ASPState_log, FILENAME = '{new path}\ASPState_log.ldf');

Op dit punt moet u de bestanden handmatig verplaatsen naar <new path> , en dan kunt u de database weer online brengen:

ALTER DATABASE ASPState SET ONLINE;
 
ALTER DATABASE ASPState SET MULTI_USER;

Isoleer applicaties

Het is mogelijk om meer dan één applicatie naar dezelfde sessiestatusdatabase te verwijzen. Ik raad dit af. Misschien wilt u applicaties naar verschillende databases verwijzen, misschien zelfs naar verschillende instanties, om het gebruik van bronnen beter te isoleren en de grootst mogelijke flexibiliteit te bieden voor al uw webproperty's.

Als je al meerdere applicaties hebt die dezelfde database gebruiken, is dat oké, maar je wilt wel bijhouden welke impact elke applicatie kan hebben. Microsoft's Rex Tang publiceerde een nuttige query om te zien hoeveel ruimte elke sessie verbruikt; hier is een wijziging die het aantal sessies en de totale/gem. sessiegrootte per applicatie samenvat:

SELECT 
  a.AppName, 
  SessionCount = COUNT(s.SessionId),
  TotalSessionSize = SUM(DATALENGTH(s.SessionItemLong)),
  AvgSessionSize = AVG(DATALENGTH(s.SessionItemLong))
FROM 
  dbo.ASPStateTempSessions AS s
LEFT 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.AppName
ORDER BY TotalSessionSize DESC;

Als u merkt dat u hier een scheve distributie heeft, kunt u elders een andere ASPState-database opzetten en in plaats daarvan een of meer toepassingen naar die database verwijzen.

Maak vriendelijkere verwijderingen

De code voor dbo.DeleteExpiredSessions gebruikt een cursor, ter vervanging van een enkele DELETE bij eerdere uitvoeringen. (Dit was, denk ik, grotendeels gebaseerd op dit bericht van Greg Low.)

Oorspronkelijk was de code:

CREATE PROCEDURE DeleteExpiredSessions
AS
  DECLARE @now DATETIME
  SET @now = GETUTCDATE()
 
  DELETE ASPState..ASPStateTempSessions
  WHERE Expires < @now
 
  RETURN 0
GO

(En het kan nog steeds zo zijn, afhankelijk van waar je de broncode hebt gedownload of hoe lang geleden je ASPState hebt geïnstalleerd. Er zijn veel verouderde scripts voor het maken van de database, hoewel je eigenlijk aspnet_regsql.exe zou moeten gebruiken.)

Momenteel (vanaf .NET 4.5) ziet de code er als volgt uit (weet iemand wanneer Microsoft puntkomma's gaat gebruiken?).

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 ExpiredSessionCursor
 
                FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID
 
                WHILE @@FETCH_STATUS = 0 
                    BEGIN
                        DELETE FROM [ASPState].dbo.ASPStateTempSessions WHERE SessionID = @SessionID AND Expires < @now
                        FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID
                    END
 
                CLOSE ExpiredSessionCursor
 
                DEALLOCATE ExpiredSessionCursor
 
            END 
 
            DROP TABLE #tblExpiredSessions
 
        RETURN 0

Mijn idee is om hier een gulden middenweg te hebben - probeer niet ALLE rijen in één klap te verwijderen, maar speel ook niet één voor één een mep. Verwijder in plaats daarvan n rijen tegelijk in afzonderlijke transacties - waardoor de blokkeringsduur wordt verkort en ook de impact op het logboek wordt geminimaliseerd:

ALTER PROCEDURE dbo.DeleteExpiredSessions
  @top INT = 1000
AS
BEGIN
  SET NOCOUNT ON;
 
  DECLARE @now DATETIME, @c INT;
  SELECT @now = GETUTCDATE(), @c = 1;
 
  BEGIN TRANSACTION;
 
  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;
 
    IF @@TRANCOUNT = 1
    BEGIN
      COMMIT TRANSACTION;
      BEGIN TRANSACTION;
    END
  END
 
  IF @@TRANCOUNT = 1
  BEGIN
    COMMIT TRANSACTION;
  END
END
GO

U wilt experimenteren met TOP afhankelijk van hoe druk uw server is en welke impact deze heeft op duur en vergrendeling. U kunt ook overwegen om snapshot-isolatie te implementeren - dit zal enige impact hebben op tempdb, maar kan de blokkering van de app verminderen of elimineren.

Ook is standaard de taak ASPState_Job_DeleteExpiredSessions loopt elke minuut. Overweeg om dat een beetje terug te draaien - verminder het schema tot misschien elke 5 minuten (en nogmaals, veel hiervan zal afhangen van hoe druk uw applicaties zijn en het testen van de impact van de wijziging). En aan de andere kant, zorg ervoor dat het is ingeschakeld – anders zal je sessietafel groeien en groeien ongecontroleerd.

Raak sessies minder vaak aan

Elke keer dat een pagina wordt geladen (en, als de web-app niet correct is gemaakt, mogelijk meerdere keren per pagina die wordt geladen), wordt de opgeslagen procedure dbo.TempResetTimeout wordt aangeroepen, zodat de time-out voor die specifieke sessie wordt verlengd zolang ze activiteit blijven genereren. Op een drukke website kan dit een zeer hoog volume aan update-activiteit veroorzaken tegen de tabel dbo.ASPStateTempSessions . Hier is de huidige code voor dbo.TempResetTimeout :

ALTER PROCEDURE [dbo].[TempResetTimeout]
            @id     tSessionId
        AS
            UPDATE [ASPState].dbo.ASPStateTempSessions
            SET Expires = DATEADD(n, Timeout, GETUTCDATE())
            WHERE SessionId = @id
            RETURN 0

Stel je nu voor dat je een website hebt met 500 of 5000 gebruikers, en ze klikken allemaal waanzinnig van pagina naar pagina. Dit is waarschijnlijk een van de meest genoemde bewerkingen in elke ASPState-implementatie, en hoewel de tabel is ingetoetst op SessionId - dus de impact van een individuele verklaring moet minimaal zijn - in totaal kan dit aanzienlijk verspillend zijn, ook op het logboek. Als uw sessietime-out 30 minuten is en u de time-out voor een sessie elke 10 seconden bijwerkt vanwege de aard van de web-app, wat heeft het dan voor zin om het 10 seconden later opnieuw te doen? Zolang die sessie asynchroon wordt bijgewerkt voordat de 30 minuten voorbij zijn, is er geen netto verschil voor de gebruiker of de toepassing. Dus ik dacht dat je een meer schaalbare manier zou kunnen implementeren om sessies te 'aanraken' om hun time-outwaarden bij te werken.

Een idee dat ik had was om een ​​service broker-wachtrij te implementeren, zodat de toepassing niet hoeft te wachten op het daadwerkelijke schrijven - het roept de dbo.TempResetTimeout aan opgeslagen procedure, waarna de activeringsprocedure asynchroon overneemt. Maar dit leidt nog steeds tot veel meer updates (en logactiviteit) dan echt nodig is.

Een beter idee, IMHO, is om een ​​wachtrijtabel te implementeren die u alleen invoegt en volgens een schema (zodat het proces een volledige cyclus voltooit in een tijd die korter is dan de time-out), het zou alleen de time-out bijwerken voor elke sessie die het ziet eenmaal , ongeacht hoe vaak ze *probeerden* om hun time-out binnen die periode bij te werken. Een eenvoudige tabel kan er dus als volgt uitzien:

CREATE TABLE dbo.SessionStack
(
  SessionId  tSessionId,    -- nvarchar(88) - of course they had to use alias types
  EventTime  DATETIME, 
  Processed  BIT NOT NULL DEFAULT 0
);
 
CREATE CLUSTERED INDEX et ON dbo.SessionStack(EventTime);
GO

En dan zouden we de voorraadprocedure veranderen om sessie-activiteit naar deze stapel te duwen in plaats van de sessietafel rechtstreeks aan te raken:

ALTER PROCEDURE dbo.TempResetTimeout
  @id tSessionId
AS
BEGIN
  SET NOCOUNT ON;
 
  INSERT INTO dbo.SessionStack(SessionId, EventTime)
    SELECT @id, GETUTCDATE();
END
GO

De geclusterde index staat op de smalldatetime kolom om paginasplitsingen te voorkomen (ten koste van een hot-page), aangezien de gebeurtenistijd voor een sessieaanraking altijd monotoon zal toenemen.

Dan hebben we een achtergrondproces nodig om periodiek nieuwe rijen samen te vatten in dbo.SessionStack en update dbo.ASPStateTempSessions dienovereenkomstig.

CREATE PROCEDURE dbo.SessionStack_Process
AS
BEGIN
  SET NOCOUNT ON;
 
  -- unless you want to add tSessionId to model or manually to tempdb 
  -- after every restart, we'll have to use the base type here:
 
  CREATE TABLE #s(SessionId NVARCHAR(88), EventTime SMALLDATETIME);
 
  -- the stack is now your hotspot, so get in & out quickly:
 
  UPDATE dbo.SessionStack SET Processed = 1 
    OUTPUT inserted.SessionId, inserted.EventTime INTO #s
    WHERE Processed IN (0,1) -- in case any failed last time
    AND EventTime < GETUTCDATE(); -- this may help alleviate contention on last page
 
  -- this CI may be counter-productive; you'll have to experiment:
 
  CREATE CLUSTERED INDEX x ON #s(SessionId, EventTime);
 
  BEGIN TRY
    ;WITH summary(SessionId, Expires) AS 
    (
       SELECT SessionId, MAX(EventTime) 
         FROM #s GROUP BY SessionId
    )
    UPDATE src
      SET Expires = DATEADD(MINUTE, src.[Timeout], summary.Expires)
      FROM dbo.ASPStateTempSessions AS src
      INNER JOIN summary
      ON src.SessionId = summary.SessionId;
 
    DELETE dbo.SessionStack WHERE Processed = 1;
  END TRY
  BEGIN CATCH
    RAISERROR('Something went wrong, will try again next time.', 11, 1);
  END CATCH
END
GO

Misschien wil je hier meer transactiecontrole en foutafhandeling aan toevoegen - ik presenteer slechts een kant-en-klaar idee, en je kunt hier zo gek van worden als je wilt. :-)

Je zou kunnen denken dat je een niet-geclusterde index zou willen toevoegen aan dbo.SessionStack(SessionId, EventTime DESC) om het achtergrondproces te vergemakkelijken, maar ik denk dat het beter is om zelfs de meest minuscule prestatiewinst te concentreren op het proces waarop gebruikers wachten (elke pagina die wordt geladen) in plaats van op een proces waar ze niet op wachten (het achtergrondproces). Dus ik betaal liever de kosten van een mogelijke scan tijdens het achtergrondproces dan voor extra indexonderhoud tijdens elke afzonderlijke insert. Net als bij de geclusterde index op de #temp-tabel, is er hier veel "het hangt ervan af", dus misschien wil je met deze opties spelen om te zien waar je tolerantie het beste werkt.

Tenzij de frequentie van de twee bewerkingen drastisch moet verschillen, zou ik dit plannen als onderdeel van de ASPState_Job_DeleteExpiredSessions taak (en overweeg de naam van die taak te wijzigen) zodat deze twee processen elkaar niet vertrappen.

Een laatste idee hier, als je merkt dat je nog meer moet uitschalen, is om meerdere SessionStack te maken tabellen, waarbij elk verantwoordelijk is voor een subset van sessies (bijvoorbeeld gehasht op het eerste teken van de SessionId ). Vervolgens kunt u elke tabel om de beurt verwerken en die transacties veel kleiner houden. In feite zou je ook iets soortgelijks kunnen doen voor de verwijdertaak. Als u het goed doet, zou u deze in afzonderlijke taken moeten kunnen plaatsen en gelijktijdig kunnen uitvoeren, aangezien - in theorie - de DML van invloed zou moeten zijn op totaal verschillende reeksen pagina's.

Conclusie

Dat zijn mijn ideeën tot nu toe. Ik zou graag horen over uw ervaringen met ASPState:Wat voor schaal heeft u bereikt? Wat voor knelpunten heb je geconstateerd? Wat heb je gedaan om ze te verminderen?


  1. Schemanaam toevoegen aan entiteit in Spring-gegevens?

  2. Gebruikersaccountbeheer, rollen, machtigingen, authenticatie PHP en MySQL - Deel 4

  3. Islands T-SQL-uitdaging

  4. Een gegevensbestand verwijderen uit een SQL Server-database (T-SQL)