De nombreuses personnes ont implémenté ASPState dans leur environnement. Certaines personnes utilisent l'option en mémoire (InProc), mais je vois généralement l'option de base de données utilisée. Il existe certaines inefficacités potentielles ici que vous ne remarquerez peut-être pas sur les sites à faible volume, mais qui commenceront à affecter les performances à mesure que votre volume Web augmentera.
Modèle de récupération
Assurez-vous que ASPState est défini sur une récupération simple - cela réduira considérablement l'impact sur le journal qui peut être causé par le volume élevé d'écritures (transitoires et largement jetables) qui sont susceptibles d'aller ici :
ALTER DATABASE ASPState SET RECOVERY SIMPLE ;
Habituellement, cette base de données n'a pas besoin d'être en récupération complète, d'autant plus que si vous êtes en mode de récupération après sinistre et que vous restaurez votre base de données, la dernière chose dont vous devriez vous soucier est d'essayer de maintenir des sessions pour les utilisateurs de votre application Web - qui sont susceptibles de être parti depuis longtemps au moment où vous avez restauré. Je ne pense pas avoir déjà rencontré une situation où la récupération ponctuelle était une nécessité pour une base de données transitoire comme ASPState.
Minimiser/isoler les E/S
Lors de la configuration initiale d'ASPState, vous pouvez utiliser le -sstype c
et -d
arguments pour stocker l'état de la session dans une base de données personnalisée qui se trouve déjà sur un autre lecteur (comme vous le feriez avec tempdb). Ou, si votre base de données tempdb est déjà optimisée, vous pouvez utiliser le -sstype t
argument. Celles-ci sont expliquées en détail dans les documents Session-State Modes et ASP.NET SQL Server Registration Tool sur MSDN.
Si vous avez déjà installé ASPState et que vous avez déterminé qu'il serait avantageux de le déplacer vers son propre volume (ou au moins vers un volume différent), vous pouvez planifier ou attendre une brève période de maintenance et suivre ces étapes :
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');À ce stade, vous devrez déplacer manuellement les fichiers vers
<new path>
, puis vous pourrez remettre la base de données en ligne :ALTER DATABASE ASPState SET ONLINE ; ALTER DATABASE ASPState SET MULTI_USER ;Isoler les applications
Il est possible de faire pointer plusieurs applications vers la même base de données d'état de session. Je déconseille cela. Vous souhaiterez peut-être pointer des applications vers différentes bases de données, peut-être même sur différentes instances, afin de mieux isoler l'utilisation des ressources et d'offrir une flexibilité maximale pour toutes vos propriétés Web.
Si vous avez déjà plusieurs applications utilisant la même base de données, ce n'est pas grave, mais vous voudrez garder une trace de l'impact que chaque application pourrait avoir. Rex Tang de Microsoft a publié une requête utile pour voir l'espace consommé par chaque session; voici une modification qui résumera le nombre de sessions et la taille totale/moyenne des sessions par application :
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;Si vous trouvez que vous avez une distribution déséquilibrée ici, vous pouvez configurer une autre base de données ASPState ailleurs et pointer une ou plusieurs applications vers cette base de données à la place.
Effectuer des suppressions plus conviviales
Le code pour
dbo.DeleteExpiredSessions
utilise un curseur, remplaçant un seulDELETE
dans les implémentations antérieures. (Ceci, je pense, était basé en grande partie sur ce message de Greg Low.)À l'origine, le code était :
CREATE PROCEDURE DeleteExpiredSessionsAS DECLARE @now DATETIME SET @now =GETUTCDATE() DELETE ASPState..ASPStateTempSessions WHERE Expire <@now RETURN 0GO(Et cela peut encore l'être, selon l'endroit où vous avez téléchargé la source ou depuis combien de temps vous avez installé ASPState. Il existe de nombreux scripts obsolètes pour créer la base de données, bien que vous devriez vraiment utiliser aspnet_regsql.exe.)
Actuellement (à partir de .NET 4.5), le code ressemble à ceci (quelqu'un sait-il quand Microsoft commencera à utiliser des points-virgules ?).
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 Expire <@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.ASPStateTempSes sions WHERE SessionID =@SessionID AND Expires <@now FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID END CLOSE ExpiredSessionCursor DEALLOCATE ExpiredSessionCursor END DROP TABLE #tblExpiredSessions RETURN 0Mon idée est d'avoir un juste milieu ici - n'essayez pas de supprimer TOUTES les lignes d'un seul coup, mais ne jouez pas non plus une par une. Au lieu de cela, supprimez
n
lignes à la fois dans des transactions séparées, ce qui réduit la durée du blocage et minimise également l'impact sur le journal :ALTER PROCEDURE dbo.DeleteExpiredSessions @top INT =1000ASBEGIN SET NOCOUNT ON ; DECLARE @now DATETIME, @c INT ; SELECT @maintenant =GETUTCDATE(), @c =1 ; COMMENCER LA TRANSACTION ; WHILE @c <> 0 COMMENCER; AVEC x AS ( SELECT TOP (@top) SessionId FROM dbo.ASPStateTempSessions WHERE Expire <@now ORDER BY SessionId ) DELETE x; SET @c =@@ROWCOUNT ; SI @@TRANCOUNT =1 COMMENCEZ LA TRANSACTION DE COMMIT ; COMMENCER LA TRANSACTION ; END END IF @@TRANCOUNT =1 COMMIT COMMIT TRANSACTION ; ENDENDGOVous voudrez expérimenter avec
TOP
en fonction de l'occupation de votre serveur et de son impact sur la durée et le verrouillage. Vous pouvez également envisager d'implémenter l'isolement d'instantané - cela forcera un certain impact sur tempdb mais peut réduire ou éliminer le blocage vu depuis l'application.Aussi, par défaut, le travail
ASPState_Job_DeleteExpiredSessions
tourne toutes les minutes. Envisagez de revenir un peu en arrière - réduisez le calendrier à peut-être toutes les 5 minutes (et encore une fois, cela dépendra en grande partie de l'occupation de vos applications et du test de l'impact du changement). Et d'un autre côté, assurez-vous qu'il est activé - sinon votre tableau de sessions grandira et grandira sans contrôle.Les sessions tactiles sont moins fréquentes
Chaque fois qu'une page est chargée (et, si l'application Web n'a pas été créée correctement, éventuellement plusieurs fois par chargement de page), la procédure stockée
dbo.TempResetTimeout
est appelée, garantissant que le délai d'expiration de cette session particulière est prolongé tant qu'elle continue à générer de l'activité. Sur un site Web très fréquenté, cela peut entraîner un volume très élevé d'activités de mise à jour par rapport à la tabledbo.ASPStateTempSessions
. Voici le code actuel pourdbo.TempResetTimeout
:ALTER PROCEDURE [dbo].[TempResetTimeout] @id tSessionId AS UPDATE [ASPState].dbo.ASPStateTempSessions SET Expires =DATEADD(n, Timeout, GETUTCDATE()) WHERE SessionId =@id RETURN 0Maintenant, imaginez que vous avez un site Web avec 500 ou 5 000 utilisateurs, et qu'ils cliquent tous follement d'une page à l'autre. C'est probablement l'une des opérations les plus fréquemment appelées dans toute implémentation ASPState, et bien que la table soit saisie sur
SessionId
- l'impact de toute déclaration individuelle doit donc être minime - dans l'ensemble, cela peut être un gaspillage considérable, y compris sur le journal. Si le délai d'expiration de votre session est de 30 minutes et que vous mettez à jour le délai d'expiration d'une session toutes les 10 secondes en raison de la nature de l'application Web, quel est l'intérêt de recommencer 10 secondes plus tard ? Tant que cette session est mise à jour de manière asynchrone à un moment donné avant la fin des 30 minutes, il n'y a aucune différence nette pour l'utilisateur ou l'application. J'ai donc pensé que vous pourriez implémenter un moyen plus évolutif de "toucher" les sessions pour mettre à jour leurs valeurs de délai d'attente.Une idée que j'ai eue était d'implémenter une file d'attente de courtier de service afin que l'application n'ait pas à attendre que l'écriture réelle se produise - elle appelle le
dbo.TempResetTimeout
procédure stockée, puis la procédure d'activation prend le relais de manière asynchrone. Mais cela conduit toujours à beaucoup plus de mises à jour (et d'activité de journalisation) que ce qui est vraiment nécessaire.Une meilleure idée, à mon humble avis, consiste à implémenter une table de file d'attente dans laquelle vous n'insérez que, et selon un calendrier (de sorte que le processus termine un cycle complet en un temps plus court que le délai d'expiration), cela ne mettrait à jour que le délai d'expiration pour toute session il voit une fois , quel que soit le nombre de fois qu'ils ont *essayé* de mettre à jour leur délai d'attente au cours de cette période. Ainsi, un tableau simple pourrait ressembler à ceci :
CREATE TABLE dbo.SessionStack( SessionId tSessionId, -- nvarchar(88) - bien sûr, ils devaient utiliser les types d'alias EventTime DATETIME, Processed BIT NOT NULL DEFAULT 0); CREATE CLUSTERED INDEX et ON dbo.SessionStack(EventTime);GOEt puis nous changerions la procédure de stock pour pousser l'activité de session sur cette pile au lieu de toucher directement la table des sessions :
ALTER PROCEDURE dbo.TempResetTimeout @id tSessionIdASBEGIN SET NOCOUNT ON ; INSERT INTO dbo.SessionStack(SessionId, EventTime) SELECT @id, GETUTCDATE();ENDGOL'index clusterisé est sur le
smalldatetime
colonne pour éviter les fractionnements de page (au prix potentiel d'une page chaude), puisque l'heure de l'événement pour une session tactile augmentera toujours de manière monotone.Ensuite, nous aurons besoin d'un processus d'arrière-plan pour résumer périodiquement les nouvelles lignes dans
dbo.SessionStack
et mettre à jourdbo.ASPStateTempSessions
en conséquence.CREATE PROCEDURE dbo.SessionStack_ProcessASBEGIN SET NOCOUNT ON ; -- à moins que vous ne souhaitiez ajouter un tSessionId au modèle ou manuellement à tempdb -- après chaque redémarrage, nous devrons utiliser le type de base ici :CREATE TABLE #s(SessionId NVARCHAR(88), EventTime SMALLDATETIME); -- la pile est maintenant votre point d'accès, alors entrez et sortez rapidement :UPDATE dbo.SessionStack SET Processed =1 OUTPUT insert.SessionId, insert.EventTime INTO #s WHERE Processed IN (0,1) -- en cas d'échec en dernier heure AND EventTimeVous voudrez peut-être ajouter plus de contrôle transactionnel et de gestion des erreurs autour de cela - je présente juste une idée impromptue, et vous pouvez devenir aussi fou que vous le souhaitez. :-)
Vous pourriez penser que vous voudriez ajouter un index non clusterisé sur
dbo.SessionStack(SessionId, EventTime DESC)
pour faciliter le processus en arrière-plan, mais je pense qu'il est préférable de concentrer même les gains de performances les plus infimes sur le processus que les utilisateurs attendent (chaque chargement de page) plutôt que sur celui qu'ils n'attendent pas (le processus en arrière-plan). Je préfère donc payer le coût d'une analyse potentielle pendant le processus d'arrière-plan plutôt que de payer une maintenance d'index supplémentaire lors de chaque insertion. Comme avec l'index clusterisé sur la table #temp, il y a beaucoup de "ça dépend" ici, donc vous voudrez peut-être jouer avec ces options pour voir où votre tolérance fonctionne le mieux.À moins que la fréquence des deux opérations ne doive être radicalement différente, je planifierais cela dans le cadre des
ASPState_Job_DeleteExpiredSessions
travail (et envisagez de renommer ce travail si c'est le cas) afin que ces deux processus ne se piétinent pas.Une dernière idée ici, si vous trouvez que vous avez besoin d'évoluer encore plus, est de créer plusieurs
SessionStack
tables, où chacune est responsable d'un sous-ensemble de sessions (par exemple, hachées sur le premier caractère duSessionId
). Ensuite, vous pouvez traiter chaque table à tour de rôle et garder ces transactions beaucoup plus petites. En fait, vous pouvez également faire quelque chose de similaire pour le travail de suppression. Si cela est fait correctement, vous devriez pouvoir les placer dans des tâches individuelles et les exécuter simultanément, car - en théorie - le DML devrait affecter des ensembles de pages complètement différents.Conclusion
Ce sont mes idées jusqu'à présent. J'aimerais connaître vos expériences avec ASPState :quel type d'échelle avez-vous atteint ? Quels types de goulots d'étranglement avez-vous observés ? Qu'avez-vous fait pour les atténuer ?