kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

無駄な処理をしない事と、重い処理をバックグラウンドにする事


UniversalProviders の高速化合戦に参戦してみます。

無聊を 託つ: HighPerformanceSessionStateProvider

ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (3) « ブチザッキ

パフォーマンスの改善と言ったら、無駄処理の削減と重い処理のバックグラウウド化でしょーよと。

まず、無駄処理の削減、毎リクエストごとに Expire 処理するなんて無駄ジャン、精々毎秒、もっとルーズに行くなら 15秒に一回とかでもセッションの Expire 処理の精度なんて十分でしょーと。

private DateTime _lastSessionExpireInvoked = DateTime.MinValue;
private object _lockSessionExpire = new object();

で最後にセッションの Expire 処理が起動された日時を保存、これが1秒を超えてた時だけ Expire 処理を実行する様にします。

RemoveExpiredSessions(connectionStringSettings);

の一行を以下の様に書き換え

using System.Threading; (ソースの冒頭で)

if( _lastSessionExpireInvoked < DateTime.Now – TimeSpan.FromSeconds(1) )
{
    if( Monitor.TryEnter( _lockSessionExpire ) ) {
        // ロックに入れたスレッドでだけ処理する、入れなかったスレッドでは処理しない
        DateTime old = _lastSessionExpireInvoked;
        _lastSessionExpireInvoked = DateTime.Now;
        try
        {
            try
            {
                RemoveExpiredSessions(connectionStringSettings);
            }
            catch
            {
                // 失敗した時はタイマ値を戻すことで別のスレッドにやってもらう
                _lastSessionExpireInvoked = old;
                throw;
            }
        }
        finally
        {
            // 絶対にMonitorを出る
            Monitor.Exit( _lockSessionExpire );
        }
   }
}

これでリクエストが来ていれば1秒以上の間隔をもってRemoveExpiredSessions が呼ばれます。

このリクエストにとってはExpire処理をしなければならないなんてペナルティでしかありません。実行をバックグラウウドスレッドに投げるまでを責務として切り分けましょう。

まぁ、 ThreadPool.QueueUserWorkItem するだけで良いですよね。結果気にする必要あんまりないし。

bool _working = false;
private void RemoveExpiredSessions(
    ConnectionStringSettings connectionStringSettings)
{
    if( _working ) return; // やってる人が居るならやらんでいいでしょ
    _working = true;
    try {
        ThreadPool.QueueUserWorkItem( (object state)=>
        { 
            try
            {
                ExecuteSql(connectionStringSettings, (factory, command) =>
                { 
                    command.CommandText = "delete Sessions where Expires < @0";
                    var parameter = factory.CreateParameter();
                    parameter.ParameterName = "@0";
                    parameter.Value = DateTime.UtcNow;
                    command.Parameters.Add(parameter);
                });
            }
            finally { _working = false; }
        });
    } catch { _working = false; throw; }
}

_working 変数によって、すでに Queue されている処理が走っていれば実行しないって事が簡単にできますね。これで1秒以上の Expire 処理が走ってる時にかぶせて実行して RDBMS 側でのロック待ちに好き好んで飛び込む愚を避ける事ができます。

一番最後の catch は QueueUserWorkItem がキュー上限で OutOfMemoryException を投げる事に対する対策、_working を戻しとかないと二度と Expire 処理が走れなくなるという事に対する例外復旧ですね。

これで無駄に全リクエストで Expire 行くこともないし、無駄に RDBMS 上で Expire 総なめdeleteの為の tablock を奪い合い参加しないし、リクエスト処理にはぶっちゃけ WorkItemをQueueする為の負荷しかかからないはずで、もろもろ含めて使い物になる様になるんじゃないかな?

無駄な処理をしない事と、重い処理をバックグラウンドにする事」への1件のフィードバック

  1. ピンバック:ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (4) « ブチザッキ

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。