多くの人が自分の環境にASPStateを実装しています。インメモリオプション(InProc)を使用する人もいますが、通常はデータベースオプションが使用されています。ここには、ボリュームの少ないサイトでは気付かない可能性のある非効率性がいくつかありますが、Webボリュームが増加するにつれて、パフォーマンスに影響を及ぼし始めます。
リカバリーモデル
ASPStateが単純なリカバリに設定されていることを確認してください。これにより、ここに送信される可能性のある大量の(一時的で大部分が使い捨ての)書き込みによって引き起こされる可能性のあるログへの影響が大幅に減少します。
ALTER DATABASE ASPState SET RECOVERY SIMPLE;
通常、このデータベースは完全に復旧している必要はありません。特に、障害復旧モードでデータベースを復元している場合、最後に心配する必要があるのは、Webアプリのユーザーのセッションを維持しようとすることです。あなたが回復した時までに長い間去っていきます。 ASPStateのような一時的なデータベースでポイントインタイムリカバリが必要だったという状況に遭遇したことはないと思います。
I/Oの最小化/分離
ASPStateを最初に設定するときは、-sstype cを使用できます。 および-d すでに別のドライブにあるカスタムデータベースにセッション状態を格納するための引数(tempdbの場合と同じように)。または、tempdbデータベースがすでに最適化されている場合は、-sstype tを使用できます。 口論。これらについては、MSDNのセッション状態モードとASP.NETSQLServer登録ツールのドキュメントで詳しく説明されています。
ASPStateをすでにインストールしていて、ASPStateを独自の(または少なくとも別の)ボリュームに移動することでメリットが得られると判断した場合は、短いメンテナンス期間をスケジュールまたは待機して、次の手順を実行できます。
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');
この時点で、ファイルを手動で<new path>に移動する必要があります。 、その後、データベースをオンラインに戻すことができます:
ALTER DATABASE ASPState SET ONLINE; ALTER DATABASE ASPState SET MULTI_USER;
アプリケーションを分離する
同じセッション状態データベースで複数のアプリケーションを指すことができます。私はこれに反対することをお勧めします。リソースの使用をより適切に分離し、すべてのWebプロパティに最大限の柔軟性を提供するために、アプリケーションをさまざまなデータベース、場合によってはさまざまなインスタンスに向けることができます。
同じデータベースを使用する複数のアプリケーションがすでにある場合は問題ありませんが、各アプリケーションが与える可能性のある影響を追跡する必要があります。 MicrosoftのRexTangは、各セッションで消費されたスペースを確認するための便利なクエリを公開しました。これは、セッション数とアプリケーションごとの合計/平均セッションサイズを要約する変更です。
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;>
ここに偏った分布があることがわかった場合は、別のASPStateデータベースを別の場所にセットアップし、代わりにそのデータベースに1つ以上のアプリケーションを指定できます。
よりわかりやすい削除を行う
dbo.DeleteExpiredSessionsのコード カーソルを使用して、単一のDELETEを置き換えます 以前の実装では。 (これは、主にGreg Lowによるこの投稿に基づいていると思います。)
元々のコードは次のとおりでした:
CREATE PROCEDURE DeleteExpiredSessions AS DECLARE @now DATETIME SET @now = GETUTCDATE() DELETE ASPState..ASPStateTempSessions WHERE Expires < @now RETURN 0 GO
(ソースをダウンロードした場所や、ASPStateをインストールした期間によっては、まだ存在する可能性があります。データベースを作成するための古いスクリプトがたくさんありますが、実際にはaspnet_regsql.exeを使用する必要があります。)
現在(.NET 4.5以降)、コードは次のようになっています(Microsoftがセミコロンの使用を開始する時期を知っている人はいますか?)
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
私の考えは、ここに幸せな媒体を用意することです。一挙にすべての行を削除しようとしないでください。また、1つずつモグラを叩いてプレイしないでください。代わりに、nを削除してください 個別のトランザクションで一度に1行ずつ–ブロッキングの長さを短縮し、ログへの影響を最小限に抑えます:
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
TOPを試してみてください サーバーがどれだけビジーであるか、およびサーバーが期間とロックにどのような影響を与えるかによって異なります。スナップショットアイソレーションの実装を検討することもできます。これにより、tempdbにある程度の影響がありますが、アプリから見たブロッキングが減少または排除される可能性があります。
また、デフォルトでは、ジョブASPState_Job_DeleteExpiredSessions 毎分実行されます。少し戻ってダイヤルすることを検討してください–スケジュールをおそらく5分ごとに減らしてください(そして、これの多くは、アプリケーションがどれだけ忙しく、変更の影響をテストするかにかかっています)。反対に、有効になっていることを確認してください –そうしないと、セッションテーブルが大きくなり、チェックされていない状態で大きくなります。
タッチセッションの頻度を減らす
ページが読み込まれるたびに(また、Webアプリが正しく作成されていない場合は、ページの読み込みごとに複数回)、ストアドプロシージャdbo.TempResetTimeout が呼び出され、アクティビティを生成し続ける限り、その特定のセッションのタイムアウトが延長されるようにします。忙しいWebサイトでは、これにより、テーブルdbo.ASPStateTempSessionsに対して非常に大量の更新アクティビティが発生する可能性があります。 。 dbo.TempResetTimeoutの現在のコードは次のとおりです :
ALTER PROCEDURE [dbo].[TempResetTimeout]
@id tSessionId
AS
UPDATE [ASPState].dbo.ASPStateTempSessions
SET Expires = DATEADD(n, Timeout, GETUTCDATE())
WHERE SessionId = @id
RETURN 0
ここで、500人または5,000人のユーザーがいるWebサイトがあり、それらがすべてページからページへと狂ったようにクリックしていると想像してください。これはおそらく、ASPStateの実装で最も頻繁に呼び出される操作の1つであり、テーブルはSessionIdにキー設定されています。 –したがって、個々のステートメントの影響は最小限に抑える必要があります–全体として、これはログを含めて実質的に無駄になる可能性があります。セッションのタイムアウトが30分で、Webアプリの性質上、セッションのタイムアウトを10秒ごとに更新する場合、10秒後に再度更新する意味は何ですか。 30分が経過する前のある時点でそのセッションが非同期的に更新される限り、ユーザーまたはアプリケーションに正味の違いはありません。したがって、セッションを「タッチ」してタイムアウト値を更新する、よりスケーラブルな方法を実装できると思いました。
私が持っていたアイデアの1つは、アプリケーションが実際の書き込みが発生するのを待つ必要がないように、サービスブローカーキューを実装することでした。これにより、dbo.TempResetTimeoutが呼び出されます。 ストアドプロシージャ、そしてアクティベーションプロシージャが非同期で引き継ぎます。しかし、これでも、本当に必要な数よりもはるかに多くの更新(およびログアクティビティ)が発生します。
より良いアイデア、IMHOは、挿入するだけのキューテーブルを実装することです。スケジュールに従って(プロセスがタイムアウトよりも短い時間で完全なサイクルを完了するように)、セッションのタイムアウトのみを更新します。 一度を見る 、そのスパン内でタイムアウトを更新しようと*試行*した回数に関係なく。したがって、単純なテーブルは次のようになります。
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
次に、ストックプロシージャを変更して、セッションテーブルに直接アクセスするのではなく、セッションアクティビティをこのスタックにプッシュします。
ALTER PROCEDURE dbo.TempResetTimeout
@id tSessionId
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.SessionStack(SessionId, EventTime)
SELECT @id, GETUTCDATE();
END
GO
クラスター化されたインデックスはsmalldatetimeにあります セッションタッチのイベント時間は常に単調に増加するため、ページ分割を防ぐための列(ホットページの潜在的なコストで)。
次に、dbo.SessionStackの新しい行を定期的に要約するバックグラウンドプロセスが必要になります。 dbo.ASPStateTempSessionsを更新します それに応じて。
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 これにトランザクション制御とエラー処理を追加することをお勧めします。私はただのアイデアを提示しているだけで、これに夢中になることができます。 :-)
dbo.SessionStack(SessionId, EventTime DESC)に非クラスター化インデックスを追加したいと思うかもしれません。 バックグラウンドプロセスを容易にするためですが、ユーザーが待機しないプロセス(バックグラウンドプロセス)ではなく、ユーザーが待機するプロセス(ページの読み込みごと)に、ごくわずかなパフォーマンスの向上に焦点を当てた方がよいと思います。したがって、挿入ごとに追加のインデックスメンテナンスを支払うよりも、バックグラウンドプロセス中にスキャンの可能性のあるコストを支払うほうがよいでしょう。 #tempテーブルのクラスター化インデックスと同様に、ここには多くの「依存」があるため、これらのオプションを試して、許容範囲が最適に機能する場所を確認することをお勧めします。
2つの操作の頻度を大幅に変える必要がない限り、これをASPState_Job_DeleteExpiredSessionsの一部としてスケジュールします。 これらの2つのプロセスが互いに踏みにじらないように、ジョブ(および、その場合はそのジョブの名前を変更することを検討してください)。
ここでの最後のアイデアの1つは、さらにスケールアウトする必要がある場合は、複数のSessionStackを作成することです。 テーブル。各テーブルがセッションのサブセットを担当します(たとえば、SessionIdの最初の文字でハッシュされます)。 )。次に、各テーブルを順番に処理し、それらのトランザクションをはるかに小さく保つことができます。実際、削除ジョブについても同様のことを行うことができます。正しく実行されれば、これらを個々のジョブに入れて同時に実行できるはずです。理論的には、DMLは完全に異なるページのセットに影響を与えるはずだからです。
結論
これらはこれまでの私の考えです。 ASPStateでのあなたの経験について聞いてみたいです:あなたはどのような規模を達成しましたか?どのようなボトルネックを観察しましたか?それらを軽減するために何をしましたか?