sql >> Databáze >  >> RDS >> Database

Potenciální vylepšení ASPState

Mnoho lidí implementovalo ASPState ve svém prostředí. Někteří lidé používají možnost in-memory (InProc), ale obvykle vidím, že se používá možnost databáze. Jsou zde určité potenciální nedostatky, kterých si na webech s nízkým objemem nemusíte všimnout, ale které začnou ovlivňovat výkon, jakmile objem vašeho webu poroste.

Model obnovy

Ujistěte se, že je ASPState nastaveno na jednoduché obnovení – tím se dramaticky sníží dopad na protokol, který může být způsoben velkým objemem (přechodných a převážně jednorázových) zápisů, které sem pravděpodobně zamíří:

ALTER DATABASE ASPState SET RECOVERY SIMPLE;

Obvykle tato databáze nemusí být v plném zotavení, zejména proto, že pokud jste v režimu zotavení po havárii a obnovujete databázi, poslední věc, o kterou byste se měli starat, je pokusit se udržovat relace pro uživatele ve vaší webové aplikaci – kteří pravděpodobně být dávno pryč v době, kdy jste se obnovili. Nemyslím si, že jsem se někdy setkal se situací, kdy by pro přechodnou databázi, jako je ASPState, bylo nutné obnovení v určitém okamžiku.

Minimalizujte / izolujte I/O

Při počátečním nastavování ASPState můžete použít -sstype c a -d argumenty pro uložení stavu relace do vlastní databáze, která je již na jiné jednotce (stejně jako u tempdb). Nebo, pokud je vaše databáze tempdb již optimalizována, můžete použít -sstype t argument. Ty jsou podrobně vysvětleny v dokumentech Session-State Modes a ASP.NET SQL Server Registration Tool na MSDN.

Pokud jste již nainstalovali ASPState a rozhodli jste se, že by vám prospělo přesunout jej na jeho vlastní (nebo alespoň jiný) svazek, můžete naplánovat nebo počkat na krátkou dobu údržby a postupovat podle těchto kroků:

ALTER DATABASE ASPState SET SINGLE_USER S OKAMŽITÉM ROLLBACK; ALTER DATABASE ASPState SET OFFLINE; ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState, FILENAME ='{nová cesta}\ASPState.mdf');ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState_log, FILENAME ='{nová cesta}\ASPState_log.ldf'); 

V tomto okamžiku budete muset ručně přesunout soubory do <new path> a poté můžete databázi uvést zpět online:

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

Izolace aplikací

Je možné nasměrovat více než jednu aplikaci na stejnou databázi stavu relace. Proti tomu doporučuji. Možná budete chtít nasměrovat aplikace na různé databáze, možná dokonce na různé instance, abyste lépe izolovali využití zdrojů a poskytli maximální flexibilitu pro všechny vaše webové vlastnosti.

Pokud již máte více aplikací používajících stejnou databázi, je to v pořádku, ale budete chtít sledovat, jaký dopad může mít každá aplikace. Rex Tang společnosti Microsoft publikoval užitečný dotaz na zobrazení místa spotřebovaného každou relací; zde je úprava, která shrnuje počet relací a celkovou/průměrnou velikost relace na aplikaci:

SELECT a.AppName, SessionCount =COUNT(s.SessionId), TotalSessionSize =SUM(DATALENGTH(s.SessionItemLong)), AvgSessionSize =AVG(DATALENGTH(s.SessionItemLong))OD dbo.ASPState stempleINessions 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;

Pokud zjistíte, že zde máte nevychýlenou distribuci, můžete nastavit jinou databázi ASPState jinde a místo toho na ni nasměrovat jednu nebo více aplikací.

Proveďte více přátelských smazání

Kód pro dbo.DeleteExpiredSessions používá kurzor, který nahrazuje jeden DELETE v dřívějších implementacích. (Myslím, že to bylo založeno převážně na tomto příspěvku Grega Lowa.)

Původně byl kód:

VYTVOŘENÍ POSTUPU DeleteExpiredSessionsAS DECLARE @now DATETIME SET @now =GETUTCDATE() DELETE ASPState..ASPStateTempSessions WHERE Expires <@now RETURN 0GO

(A stále může být, v závislosti na tom, kde jste si stáhli zdroj nebo jak dlouho jste nainstalovali ASPState. Existuje mnoho zastaralých skriptů pro vytváření databáze, i když byste opravdu měli používat aspnet_regsql.exe.)

Aktuálně (od .NET 4.5) vypadá kód takto (víte někdo, kdy Microsoft začne používat středníky?).

ALTER PROCEDURE [dbo].[DeleteExpiredSessions]AS SET NOCOUNT ON SET DEADLOCK_PRIORITY LOW DECLARE @now datetime SET @now =GETUTCDATE() CREATE TABLE #tblExpiredSessions (ID relace nvarSKEYes #NULLtbl PRIMARYes) ) SELECT ID relace FROM [ASPState].dbo.ASPStateTempSessions WITH (READUNCOMMITTED) WHERE vyprší <@now IF @@ROWCOUNT <> 0 ZAČÁTE DECLARE ExpiredSessionCursor CURSOR CURSOR LOCAL FORWARD_ONLY SELECTred8SesionENLY FORWARD_ONLY READ_8SEBLROMID FORWARDied. NEXT FROM ExpiredSessionCursor DO @SessionID WHILE @@FETCH_STATUS =0 ZAČNĚTE ODSTRANIT Z [ASPSate].dbo.ASPStateTempSes sions WHERE ID relace =@ID relace A vyprší <@nyní NAČTÍT DALŠÍ Z ExpiredSessionCursor DO @SessionID KONEC ZAVŘÍT ExpiredSessionCursor DEALOCATE ExpiredSessionCursor END DROP TABLE #tblExpiredSessions> NÁVRAT 0 

Moje představa je mít zde šťastné médium – nesnažte se najednou smazat VŠECHNY řádky, ale ani nehrajte jeden po druhém. Místo toho odstraňte n řádků najednou v samostatných transakcích – zkrácení délky blokování a také minimalizace dopadu na protokol:

ALTER PROCEDURE dbo.DeleteExpiredSessions @top INT =1000ASBEGIN SET NOCOUNT ON; DECLARE @now DATETIME, @c INT; SELECT @now =GETUTCDATE(), @c =1; ZAČÍT TRANSAKCI; WHILE @c <> 0 BEGIN;WITH x AS ( SELECT TOP (@top) SessionId FROM dbo.ASPStateTempSessions WHERE Expires <@now ORDER BY SessionId ) DELETE x; SET @c =@@ŘÁDEK; POKUD @@TRANCOUNT =1 ZAČNĚTE ZAKÁZAT TRANSAKCI; ZAČÍT TRANSAKCI; END END IF @@TRANCOUNT =1 BEGIN COMMIT TRANSACTION; ENDENDGO

Budete chtít experimentovat s TOP v závislosti na tom, jak je váš server vytížený a jaký to má dopad na trvání a zamykání. Můžete také zvážit implementaci izolace snímků – to bude mít určitý dopad na tempdb, ale může snížit nebo odstranit blokování viděné z aplikace.

Ve výchozím nastavení je také úloha ASPState_Job_DeleteExpiredSessions běží každou minutu. Zvažte to trochu vytočit – zkraťte plán na možná každých 5 minut (a opět hodně z toho bude záviset na vytíženosti vašich aplikací a testování dopadu změny). A na druhé straně ujistěte se, že je povoleno – jinak bude vaše tabulka relací nekontrolovaně růst a růst.

Dotýkat se relací méně často

Pokaždé, když se stránka načte (a pokud webová aplikace nebyla vytvořena správně, možná i několikrát za načtení stránky), uložená procedura dbo.TempResetTimeout se zavolá, což zajišťuje, že časový limit pro danou konkrétní relaci se prodlouží, dokud budou nadále generovat aktivitu. Na rušném webu to může způsobit velmi vysoký objem aktualizační aktivity proti tabulce dbo.ASPStateTempSessions . Zde je aktuální kód pro dbo.TempResetTimeout :

ZMĚŇTE POSTUP [dbo].[TempResetTimeout] @id tSessionId JAKO AKTUALIZACE [ASPState].dbo.ASPStateTempSessions SET Vyprší =DATEADD(n, časový limit, GETUTCDATE()) WHERE SessionId =@id RETURN 0

Nyní si představte, že máte webovou stránku s 500 nebo 5000 uživateli a všichni šíleně klikají ze stránky na stránku. Toto je pravděpodobně jedna z nejčastěji nazývaných operací v jakékoli implementaci ASPState, a zatímco je tabulka klíčována na SessionId – takže dopad každého jednotlivého prohlášení by měl být minimální – v souhrnu to může být značně plýtvání, včetně protokolu. Pokud je časový limit vaší relace 30 minut a aktualizujete časový limit relace každých 10 sekund kvůli povaze webové aplikace, jaký má smysl opakovat to o 10 sekund později? Dokud je tato relace asynchronně aktualizována v určitém okamžiku před uplynutím 30 minut, neexistuje žádný čistý rozdíl pro uživatele nebo aplikaci. Takže jsem si myslel, že byste mohli implementovat škálovatelnější způsob, jak „dotýkat se“ relací a aktualizovat jejich hodnoty časového limitu.

Jeden nápad, který jsem měl, bylo implementovat frontu zprostředkovatele služeb, aby aplikace nemusela čekat na skutečný zápis – volá dbo.TempResetTimeout uložená procedura a poté asynchronně převezme proceduru aktivace. To však stále vede k mnohem více aktualizacím (a aktivitám protokolů), než je skutečně nutné.

Lepším nápadem, IMHO, je implementovat tabulku fronty, do které pouze vložíte, a podle plánu (tak, že proces dokončí celý cyklus za nějakou dobu kratší, než je časový limit), aktualizuje časový limit pouze pro jakoukoli relaci, kterou vidí jednou , bez ohledu na to, kolikrát se *pokusili* aktualizovat svůj časový limit v tomto rozpětí. Jednoduchá tabulka tedy může vypadat takto:

CREATE TABLE dbo.SessionStack( SessionId tSessionId, -- nvarchar(88) - samozřejmě museli použít typy aliasů EventTime DATETIME, Processed BIT NOT NULL DEFAULT 0); VYTVOŘTE CLUSTEROVANÝ INDEX a NA dbo.SessionStack(EventTime);GO

A pak bychom změnili akciovou proceduru tak, abychom přesunuli aktivitu relace do tohoto zásobníku namísto přímého dotyku tabulky relací:

ALTER PROCEDURE dbo.TempResetTimeout @id tSessionIdASBEGIN SET NOCOUNT ON; INSERT INTO dbo.SessionStack(SessionId, EventTime) SELECT @id, GETUTCDATE();ENDGO

Clusterový index je na smalldatetime sloupec, aby se zabránilo rozdělení stránky (za potenciální cenu horké stránky), protože doba události pro dotyk relace bude vždy monotónně narůstat.

Potom budeme potřebovat proces na pozadí k pravidelnému shrnutí nových řádků v dbo.SessionStack a aktualizujte dbo.ASPStateTempSessions podle toho.

CREATE PROCEDURE dbo.SessionStack_ProcessASBEGIN SET NOCOUNT ON; -- pokud nechcete přidat tSessionId do modelu nebo ručně do databáze tempdb -- po každém restartu zde budeme muset použít základní typ:CREATE TABLE #s(SessionId NVARCHAR(88), EventTime SMALLDATETIME); -- zásobník je nyní vaším aktivním bodem, takže se rychle připojujte a odejděte:AKTUALIZUJTE SET dbo.SessionStack Zpracováno =1 vložený VÝSTUP. Id relace, vloženo. Čas události INTO #s WHERE Zpracováno V (0,1) -- v případě, že některý selhal jako poslední čas AND Čas události  

Možná budete chtít přidat více transakční kontroly a řešení chyb – jen předkládám nestandardní nápad a můžete se do toho zbláznit, jak chcete. :-)

Možná si myslíte, že byste chtěli přidat neshlukovaný index na dbo.SessionStack(SessionId, EventTime DESC) pro usnadnění procesu na pozadí, ale myslím si, že je lepší zaměřit se i na ty nejnepatrnější výkony na proces, na který uživatelé čekají (každé načtení stránky), než na proces, na který nečekají (proces na pozadí). Takže raději zaplatím náklady na potenciální skenování během procesu na pozadí, než abych platil za další údržbu indexu během každé jednotlivé vložky. Stejně jako u seskupeného indexu v tabulce #temp je i zde hodně „záleží“, takže si možná budete chtít pohrát s těmito možnostmi, abyste viděli, kde vaše tolerance funguje nejlépe.

Pokud se frekvence těchto dvou operací nemusí výrazně lišit, naplánoval bych to jako součást ASPState_Job_DeleteExpiredSessions job (a pokud ano, zvažte jeho přejmenování), aby se tyto dva procesy vzájemně nešlapaly.

Jeden poslední nápad, pokud zjistíte, že potřebujete ještě více škálovat, je vytvořit více SessionStack tabulky, kde každá zodpovídá za podmnožinu relací (řekněme hash na prvním znaku SessionId ). Potom můžete postupně zpracovávat každou tabulku a udržovat tyto transakce mnohem menší. Ve skutečnosti byste mohli udělat něco podobného pro úlohu odstranění. Pokud se to udělá správně, měli byste být schopni vkládat je do jednotlivých úloh a spouštět je souběžně, protože – teoreticky – by DML mělo ovlivňovat úplně jiné sady stránek.

Závěr

To jsou zatím moje představy. Rád bych slyšel o vašich zkušenostech s ASPState:Jakého rozsahu jste dosáhli? Jaký druh úzkých hrdel jste zaznamenali? Co jste udělali pro jejich zmírnění?


  1. Jak nainstalovat phpMyAdmin

  2. Jak RTRIM_ORACLE() funguje v MariaDB

  3. Co je to porovnávací operátor?

  4. Jak získat odlišné záznamy z tabulky v SQL Server - SQL Server / TSQL Tutorial 112