Database
 sql >> Base de Dados >  >> RDS >> Database

Aprimoramentos potenciais para ASPState


Muitas pessoas implementaram o ASPState em seu ambiente. Algumas pessoas usam a opção na memória (InProc), mas geralmente vejo a opção de banco de dados sendo usada. Existem algumas ineficiências potenciais aqui que você pode não notar em sites de baixo volume, mas isso começará a afetar o desempenho à medida que o volume da Web aumenta.

Modelo de recuperação


Certifique-se de que o ASPState esteja definido para recuperação simples – isso reduzirá drasticamente o impacto no log que pode ser causado pelo alto volume de gravações (transitórias e amplamente descartáveis) que provavelmente ocorrerão aqui:
ALTER DATABASE ASPState SET RECOVERY SIMPLE;

Normalmente, esse banco de dados não precisa estar em recuperação total, especialmente porque se você estiver no modo de recuperação de desastres e restaurando seu banco de dados, a última coisa com que você deve se preocupar é tentar manter sessões para usuários em seu aplicativo da web - que provavelmente estar muito longe no momento em que você restaurou. Acho que nunca me deparei com uma situação em que a recuperação pontual fosse uma necessidade para um banco de dados transitório como o ASPState.

Minimizar/isolar E/S


Ao configurar o ASPState inicialmente, você pode usar o -sstype c e -d argumentos para armazenar o estado da sessão em um banco de dados personalizado que já está em uma unidade diferente (assim como você faria com tempdb). Ou, se seu banco de dados tempdb já estiver otimizado, você pode usar o -sstype t argumento. Eles são explicados em detalhes nos documentos Modos de estado de sessão e ASP.NET SQL Server Registration Tool no MSDN.

Se você já instalou o ASPState e determinou que se beneficiaria de movê-lo para seu próprio volume (ou pelo menos para um volume diferente), poderá agendar ou aguardar um breve período de manutenção e seguir estas etapas:
ALTER DATABASE ASPState SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ALTER DATABASE ASPState SET OFFLINE; ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState, FILENAME ='{novo caminho}\ASPState.mdf');ALTER DATABASE ASPState MODIFY FILE (NAME =ASPState_log, FILENAME ='{novo caminho}\ASPState_log.ldf'); 
Neste ponto, você precisará mover manualmente os arquivos para <new path> , e então você pode colocar o banco de dados online novamente:
ALTER DATABASE ASPState SET ONLINE; ALTER DATABASE ASPState SET MULTI_USER;

Isolar aplicativos


É possível apontar mais de um aplicativo para o mesmo banco de dados de estado de sessão. Eu recomendo contra isso. Você pode querer apontar aplicativos em bancos de dados diferentes, talvez até em instâncias diferentes, para isolar melhor o uso de recursos e fornecer flexibilidade máxima para todas as suas propriedades da web.

Se você já tem vários aplicativos usando o mesmo banco de dados, tudo bem, mas você deve acompanhar o impacto que cada aplicativo pode estar causando. Rex Tang, da Microsoft, publicou uma consulta útil para ver o espaço consumido por cada sessão; aqui está uma modificação que resumirá o número de sessões e o tamanho total/médio da sessão por aplicativo:
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;

Se você achar que tem uma distribuição desigual aqui, poderá configurar outro banco de dados ASPState em outro lugar e apontar um ou mais aplicativos para esse banco de dados.

Faça exclusões mais amigáveis


O código para dbo.DeleteExpiredSessions usa um cursor, substituindo um único DELETE em implementações anteriores. (Isso, eu acho, foi baseado em grande parte neste post de Greg Low.)

Originalmente o código era:
CREATE PROCEDURE DeleteExpiredSessionsAS DECLARE @now DATETIME SET @now =GETUTCDATE() DELETE ASPState..ASPStateTempSessions WHERE Expires <@now RETURN 0GO

(E ainda pode ser, dependendo de onde você baixou a fonte ou há quanto tempo você instalou o ASPState. Existem muitos scripts desatualizados para criar o banco de dados, embora você realmente deva usar aspnet_regsql.exe.)

Atualmente (a partir do .NET 4.5), o código se parece com isso (alguém sabe quando a Microsoft começará a usar ponto e vírgula?).
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(8Se) OPEN ExpiredSessions 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 0

Minha idéia é ter um meio termo aqui – não tente deletar TODAS as linhas de uma só vez, mas também não jogue um por um no golpe de toupeira. Em vez disso, exclua n linhas por vez em transações separadas – reduzindo a duração do bloqueio e também minimizando o impacto no log:
ALTER PROCEDURE dbo.DeleteExpiredSessions @top INT =1000ASBEGIN SET NOCOUNT ON; DECLARE @agora DATETIME, @c INT; SELECT @agora =GETUTCDATE(), @c =1; COMEÇAR A TRANSAÇÃO; 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; SE @@TRANCOUNT =1 BEGIN COMMIT TRANSACTION; COMEÇAR A TRANSAÇÃO; END END IF @@TRANCOUNT =1 BEGIN COMMIT TRANSACTION; ENDENDGO

Você vai querer experimentar com TOP dependendo de quão ocupado seu servidor está e qual o impacto que ele tem na duração e no bloqueio. Você também pode considerar a implementação do isolamento de instantâneo – isso forçará algum impacto no tempdb, mas poderá reduzir ou eliminar o bloqueio visto no aplicativo.

Além disso, por padrão, o trabalho ASPState_Job_DeleteExpiredSessions corre a cada minuto. Considere diminuir um pouco isso – reduza a programação para talvez a cada 5 minutos (e, novamente, muito disso se resume a quão ocupados seus aplicativos estão e testando o impacto da mudança). Por outro lado, certifique-se de que está ativado – caso contrário, sua tabela de sessões crescerá e crescerá desmarcada.

Toque nas sessões com menos frequência


Sempre que uma página é carregada (e, se o aplicativo Web não tiver sido criado corretamente, possivelmente várias vezes por carregamento de página), o procedimento armazenado dbo.TempResetTimeout é chamado, garantindo que o tempo limite para essa sessão em particular seja estendido enquanto continuarem a gerar atividade. Em um site ocupado, isso pode causar um volume muito alto de atividade de atualização na tabela dbo.ASPStateTempSessions . Aqui está o código atual para dbo.TempResetTimeout :
ALTER PROCEDURE [dbo].[TempResetTimeout] @id tSessionId AS UPDATE [ASPState].dbo.ASPStateTempSessions SET Expires =DATEADD(n, Timeout, GETUTCDATE()) WHERE SessionId =@id RETURN 0

Agora, imagine que você tem um site com 500 ou 5.000 usuários, e todos eles estão clicando loucamente de página em página. Esta é provavelmente uma das operações mais frequentemente chamadas em qualquer implementação ASPState, e enquanto a tabela é digitada em SessionId – portanto, o impacto de qualquer declaração individual deve ser mínimo – em conjunto, isso pode ser substancialmente um desperdício, inclusive no log. Se o tempo limite de sua sessão for de 30 minutos e você atualizar o tempo limite de uma sessão a cada 10 segundos devido à natureza do aplicativo da Web, qual é o sentido de fazer isso novamente 10 segundos depois? Contanto que essa sessão seja atualizada de forma assíncrona em algum momento antes dos 30 minutos, não há diferença líquida para o usuário ou o aplicativo. Então pensei que você poderia implementar uma maneira mais escalável de "tocar" sessões para atualizar seus valores de tempo limite.

Uma ideia que tive foi implementar uma fila de agente de serviço para que o aplicativo não precisasse esperar a gravação real acontecer – ele chama o dbo.TempResetTimeout procedimento armazenado e, em seguida, o procedimento de ativação assume de forma assíncrona. Mas isso ainda leva a muito mais atualizações (e atividades de log) do que é realmente necessário.

Uma ideia melhor, IMHO, é implementar uma tabela de filas na qual você só insere e em um agendamento (de modo que o processo conclua um ciclo completo em algum tempo menor que o tempo limite), ele atualizaria apenas o tempo limite para qualquer sessão que vê uma vez , não importa quantas vezes eles *tentem* atualizar seu tempo limite dentro desse período. Assim, uma tabela simples pode ficar assim:
CREATE TABLE dbo.SessionStack( SessionId tSessionId, -- nvarchar(88) - é claro que eles tiveram que usar tipos de alias EventTime DATETIME, Processed BIT NOT NULL DEFAULT 0); CREATE CLUSTERED INDEX et ON dbo.SessionStack(EventTime);GO

E então mudaríamos o procedimento de estoque para empurrar a atividade da sessão para essa pilha em vez de tocar diretamente na tabela de sessões:
ALTER PROCEDURE dbo.TempResetTimeout @id tSessionIdASBEGIN SET NOCOUNT ON; INSERT INTO dbo.SessionStack(SessionId, EventTime) SELECT @id, GETUTCDATE();ENDGO

O índice clusterizado está no smalldatetime coluna para evitar divisões de página (ao custo potencial de uma página quente), já que o tempo do evento para um toque de sessão sempre aumentará monotonicamente.

Em seguida, precisaremos de um processo em segundo plano para resumir periodicamente novas linhas em dbo.SessionStack e atualize dbo.ASPStateTempSessions adequadamente.
CREATE PROCEDURE dbo.SessionStack_ProcessASBEGIN SET NOCOUNT ON; -- a menos que você queira adicionar tSessionId ao modelo ou manualmente ao tempdb -- após cada reinicialização, teremos que usar o tipo base aqui:CREATE TABLE #s(SessionId NVARCHAR(88), EventTime SMALLDATETIME); -- a pilha agora é seu hotspot, então entre e saia rapidamente:UPDATE dbo.SessionStack SET Processed =1 OUTPUT inserido.SessionId, inserido.EventTime INTO #s WHERE Processed IN (0,1) -- caso algum tenha falhado por último hora AND EventTime  
Você pode querer adicionar mais controle transacional e tratamento de erros em torno disso – estou apenas apresentando uma ideia improvisada, e você pode ficar tão louco quanto quiser. :-)

Você pode pensar que gostaria de adicionar um índice não clusterizado em dbo.SessionStack(SessionId, EventTime DESC) para facilitar o processo em segundo plano, mas acho que é melhor focar até mesmo os ganhos de desempenho mais minúsculos no processo que os usuários esperam (cada carregamento de página) em vez de um que eles não esperam (o processo em segundo plano). Portanto, prefiro pagar o custo de uma possível varredura durante o processo em segundo plano do que pagar pela manutenção adicional do índice durante cada inserção. Assim como acontece com o índice clusterizado na tabela #temp, há muito "depende" aqui, então você pode querer brincar com essas opções para ver onde sua tolerância funciona melhor.

A menos que a frequência das duas operações precise ser drasticamente diferente, eu agendaria isso como parte do ASPState_Job_DeleteExpiredSessions job (e considere renomear esse job, se for o caso) para que esses dois processos não atropelem um ao outro.

Uma ideia final aqui, se você achar que precisa expandir ainda mais, é criar vários SessionStack tabelas, onde cada uma é responsável por um subconjunto de sessões (digamos, hash no primeiro caractere do SessionId ). Então você pode processar cada tabela por sua vez e manter essas transações muito menores. Na verdade, você também pode fazer algo semelhante para o trabalho de exclusão. Se feito corretamente, você poderá colocá-los em trabalhos individuais e executá-los simultaneamente, pois – em teoria – o DML deve afetar conjuntos de páginas completamente diferentes.

Conclusão


Essas são minhas ideias até agora. Eu adoraria ouvir sobre suas experiências com ASPState:Que tipo de escala você alcançou? Que tipo de gargalos você observou? O que você tem feito para atenuá-los?