セッションについて書いてみたらどうでもいいネタばかりになっちゃったけど、まあいいや。誰かのお役に立てれば。
セッションは、基本的に
session.gc_maxlifetime
session.gc_probability
session.gc_divisor
の3つをこねくりまわせば有効期限を制御することができる。この中で一番大切なのはsession.gc_maxlifetimeで、ここにセッションの有効期限を秒数で設定すればいい。
通常は(session.save_handlerのデフォルトはfileなので)セッション情報がサーバ上にぼこぼこファイル保存されることになるので、適当な値(a='aaa'とか)を$_SESSIONに格納した後、/tmp以下(session.save_path)にできたsess_1ccd30f49819fa3a461b3e7aacad8656などというファイルの中身を見てみるとこんな内容になっている。
a|s:3:"aaa";
セッションデータがどのように格納されてるかが具体的にわかるので、ちょっと面白い。ちなみにsession_encode()を使えばPHPソース内でも上記の(エンコードされた)文字列を取得することができる。
で、当然別の人がアクセスしてきたら別のセッションファイルができて、また別の人がアクセスしてきたらさらに別のセッションファイルができて...ということを延々と繰り返していくのだけれど、そうなるとセッションファイルがどんどんたまっていって、しまいにはサーバのディスク容量を圧迫してしまう。つまり、有効期限の切れた(=古くて必要のない)セッションファイルはどんどん削除する必要がある。
この「有効期限の切れたセッションファイルの削除機能」はPHPのガーベッジコレクタ(以下GC)が担当していて、GCが起動すると先のsession.gc_maxlifetimeで指定した有効期限の切れたセッションファイルを根こそぎ削除してくれる。おお、なんとも賢い。
それじゃどんなタイミングでGCが起動するかというと、cronとかではなく、session_start()が呼ばれた時。つまり誰かがアクセスしてきたタイミングで、ということになる。
「でもsession_start()が呼ばれる度にGCが起動するというのは、いくらなんでもムダな負荷が多いだろう」とエライ人が言ったのかどうかは定かじゃないけれど、「session_start()が呼ばれたら、一定の確率でGCを起動しよう」ということになっていて、その確立を設定するのがsession.gc_probabilityとsession.gc_divisorの役目だ。
これは単純に確立の分子と分母の値になるので、
session.gc_probability / session.gc_divisorがそのままGCの起動する確立になる。
例えばsession.gc_probabilityが1でsession.gc_divisorが1000だったら、
1 / 1000 = 0.001
となるから、1000アクセス中1回くらいの割合でGCが起動されてセッションファイルが削除されることになる。つまり、残り999回のアクセスではGCが起動されず、有効期限の切れたセッションでも削除されない。だからアクセスが少ないサイトであれば、セッションデータはそれなりに長い間残り続けることになる。
逆にsession.gc_probabilityが1でsession.gc_divisorが1だったら、
1 / 1 = 1
となるので、毎アクセスごとにGCが確実に起動される。
普通は有効期限を30分、GC起動は1/100とかに設定しとけば問題ないと思うので、僕は.htaccessにこんな感じで書いちゃう(当然httpd.confのがいいんだけど、めんどくさくて)。
php_value session.gc_maxlifetime 1800
php_value session.gc_probability 1
php_value session.gc_divisor 100
で、ちょっと気になっていたのが「バーチャルホストでexample.comとexample.netを運営していて、example.comのgc_maxlifetimeは10に、example.netのgc_maxlifetimeは1000に設定されているとき、example.comへのアクセスで起動したGCはexample.netのセッションファイルも削除しちゃうのかどうか」ということ。例えば
1. 00:00:00 example.comにAさんがアクセス
2. 00:00:30 example.netにBさんがアクセス
3. 00:02:00 example.comにAさんがアクセス
4. 00:02:30 example.netにBさんがアクセス
というような流れでアクセスがあったとしたら、3.の時点でAさんのセッションファイルが削除されるのは正しい動きだけど、この時点でBさんのセッションファイルも削除されてしまうとしたら、example.netの運用に支障をきたしてしまう。
で、ものは試しと以下の設定で上記のようなアクセスをしてみた。
example.com:
php_value session.gc_maxlifetime 10
php_value session.gc_probability 1
php_value session.gc_divisor 1
example.net
php_value session.gc_maxlifetime 1000
php_value session.gc_probability 1
php_value session.gc_divisor 1
結果:見事に消された。
セッションファイルには「どのサイトの管理下にあるセッションなのか」という情報は格納されておらず、純粋に$_SESSIONに設定された値だけが格納されているため、GCはセッションファイルの更新日時だけを見て、古いと判断したものを根こそぎ削除してしまう仕様らしい。
じゃあ、どうやったらGC起動時にサイトごとのセッションファイルだけを消せるかな?と考えた。そしたらこうなった。
↓
example.com:
php_value session.save_path /var/tmp/example.com
php_value session.gc_maxlifetime 10
php_value session.gc_probability 1
php_value session.gc_divisor 1
example.net
php_value session.save_path /var/tmp/example.net
php_value session.gc_maxlifetime 1000
php_value session.gc_probability 1
php_value session.gc_divisor 1
つまりサイトごとにセッションファイル格納ディレクトリを個別に設定しておけば、example.comへのアクセスをトリガにGCが起動したとしてもexample.netのセッションファイルは削除されなくなる。この手法を使えば、特定ディレクトリへのアクセス時のセッションファイルを通常とは別のところに格納しておくといった使い方もできると思う。ここら辺はセッション処理関数(session)に詳しく載ってるので、まあよく見とくに越したことはない。
ちなみにsession.save_pathの説明文にある「session.save_pathのパスの深さが2より大きい場合、ガーベッジコレクションは行われません。」の2っていうのは、どうやらオプションの引数として指定できるNのことらしい。Nを使わないのであれば/var/tmp/php/session/example.comなどと設定しても、GCは起動されていた。
2005/10/25 追記
※セッションの保存先を/var/tmpではなく/tmpにしてしまうと、サーバ再起動時に/tmp以下が全て削除されてしまうため、example.comなどのサブディレクトリが全て消えてしまい、セッションが保存できなくなってしまいます。なので/var/tmpに格納するよう修正しました。
session.gc_probabilityとsession.gc_divisorについてですが、session.gc_probability/session.gc_divisorの値がPHPによりランダムで0~1で生成された数字より小さいときにGCが実行されるそうです。
>> shinriyoさん
どもです。
ext\session\session.c にあるphp_session_start()のコレですね。
~略~
nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
if (nrand < PS(gc_probability)) { ←ココ
PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC);
#ifdef SESSION_DEBUG
if (nrdels != -1) {
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "purged %d expired session objects", nrdels);
}
#endif
}
~略~
php_combined_lcg()が擬似乱数を生成する関数であるからして、この乱数と gc_divisor との積が gc_probability より小さい場合はGCが起動するみたいっすね。