Sessionの生成・破棄を任意のタイミングで制御可能にするScope::Session

@kazeburo先生から解説記事の希望とあれば、まじめにブログ書かないと@kzysにDISられようとも気にも留めない僕ですら、記事を書きます。

前段のところを丁寧にまとめると、こりゃ厄介だなと思っていたところ@kazeburo先生の記事が出てきたので、引用しながらScope::Sessionなる昔書いたCPANモジュールの話をします。

mod_perl のアプリケーションでは、Apacheモジュールの提供するpnotesを使うとリクエスト毎のデータを簡単に持つことができます。pnotesに入れたデータはリクエストの処理が終了したところで自動的にクリーンアップされます。これを利用したのがリクエストごとにインスタンスを作成破棄できる、Apache::Singleton(::Request)です。


通常Singletonというと「常にインスタンスが一意」という性質をもったクラスなんですが、cgi時代ならいざ知らず、Webアプリケーションのプロセスの寿命は長いので、適当なところ(リクエスト単位)で一度リソースを手放してくれるとうれしいことが多いです。


とくに外部リソースに問い合わせたりするような処理を最適化していこうとおもったら、多少のコヒーランシーを無視しても、キャッシュできたほうがうれしかったりします。

また、pnotesはデータベースの接続の管理にもしばしば使われます。1リクエストを裁いている間だけデータベースとの接続を維持し、リクエストが完了したところで接続を閉じるような処理に利用されています。このようにすることでmod_perlのプロセス数分(数百)の接続がMySQLに常に張られることもなく、また1度のリクエストの中で何度も接続を行うコストを押さえる事ができます。


このようにDBConnectionというリソースに関しても同様で、リクエスト単位の寿命というやつが適切だったりします。


ところで、
DBコネクション管理やSingleton/Flyweightといったものが必要なモジュールとはアプリケーションにとって、どういった部分でしょうか?


MVCで言えば、間違いなくM。Model系モジュールになります。


こういったものが、ApacheというWebServerやPlack/PSGICatalystやその他WAFに依存した実装になっていると、WebServerプロダクトの変更、WAFの刷新といった楽しげなことに禍根を残すこと請け合いです。


また、まともなWebアプリケーションにはWebサーバへのリクエストを捌く機能の他にも
次のようなサブシステムが存在しているはずです。

  • JobQueue Worker用framework
  • 内部RPCサーバ用framework
  • cronのbatch用framework
  • オペレーション用script


こういったサブシステム全般で、このぐらいの粒度で寿命を持ってほしいという理想があると思います。
たとえば、JobQueueなら一つのジョブを処理し終えるまでとか、cronであれば、イテレーション対象のデータを一つ処理し終えるまで。といった風に。

# ダミーコード
# リクエストを取得して
while( my $req = $itr->() ){
# リクエストを処理
    handle_req( $req ); 
# handle_reqの間だけ、リソースの保持をしていてほしい。
}

そうなったときにScope::Sessionを使います。

use Scope::Session;
while( my $req = $itr->() ){
    Scope::Session::start {
        handle_req( $req );
    };
}

こんな感じに。これで、handle_req内でScope::Sessionのkey-valueを利用すると、それが維持されて抜けると破棄されます。

@kazeburoさんのDBコネクションのサンプルをそのまま置き換えると

sub connect {
    my $class = shift;
    my @dsn = @_;
    my $dbh;

    my $dsn_key = Data::MessagePack->pack(\@dsn);
-  my $connector = scope_container($dsn_key);
+  my $connector = Scope::Session->notes($dsn_key);
    eval {
        $dbh = $connector->_dbh;
    };
    return $dbh if $dbh;

    my $connector = DBIx::Connector->new(@dsn);
    $dbh = $connector->dbh;
-    scope_container($dsn_key, $connector);
+    Scope::Session->notes($dsn_key => $connector);
    return wantarray ? ( $dbh, $connector ) : $dbh;
}

動かしてないけど。

あと、Plack製品で利用したい場合はMiddlewareもcpanにあります。

Scope::Sessionには同梱で、Scope::Session::Singleton / Scope::Session::Flyweightがついてきます。
これは、Scope::Sessionをつかった場合によく使うであろうデザインパターンのクラスを簡単につくるためのユーティリティです。
Singletonはひとつのインスタンスしか作れないクラスをつくり、
Flyweightは任意の生成済みインスタンスを再利用可能なクラスをつくるのを手伝います。


Scope::SessionとScope::Containerの違いは、
Scope::Sessionはlocalでダイナミックスコープを利用していて、
Scope::ContainerはRAIIでDESTORYで削除。


どっちも普通に使うにはたいした違いはないと思いますが、実装を比べてみると楽しいです。

http://ja.wikipedia.org/wiki/動的スコープ
http://ja.wikipedia.org/wiki/RAII

ちなみにRAIIはref count方式や自動変数の破棄がある言語処理系を前提としているところが
あるので、CPythonとJythonだと同じモジュールが動かなかったりするよ:
http://d.hatena.ne.jp/hirokidaichi/20101017


enjoy!