phpのセッションのガーベッジコレクションとかあれこれ

投稿日:

php で利用するセッション情報を MySQL に保存する方式に変更しようとしていたら、結構いろいろな設定があることに気が付いたので備忘録的な意味も込めてメモ。

今回のネタはだいたい以下の3つ。

// セッションで利用するクッキーの有効期間を1日に
ini_set("session.cookie_lifetime", 86400);

// 1日以上たったセッション情報を消去
ini_set("session.gc_maxlifetime", 86400);

// ガーベッジコレクションを毎回発生させる
ini_set('session.gc_probability', 100);

まず、session.cookie_lifetime から。

ini_set("session.cookie_lifetime", 86400);

この値は、マニュアルに書かれている内容を引用すると以下のようになります。

session.cookie_lifetime は、 ブラウザに送信するクッキーの有効期間を秒単位で指定します。 0 を指定すると "ブラウザを閉じるまで" という意味になります。 デフォルトは、0 です。

初期値は 0 が設定されているため、通常はブラウザを閉じるとセッションは無効になりますが、0 以外の値を設定することでブラウザを閉じてもセッションが無効にならないようにすることが出来ます。

例では 86400 を指定してますが、これは 1日間有効にしていると言う事です。

つまりログインするようなページで一旦ブラウザを閉じたとしても、一日の間ならログインした状態で再開できるような作りに出来ます。

例えば、下記の例の用に 2592000(30日) を指定した場合には、一旦ログインした後にブラウザを閉じたとしても、30日間はログイン状態を保持できるようにすることも可能です。

ini_set("session.cookie_lifetime", 2592000);

で次は session.gc_maxlifetime 。

ini_set("session.gc_maxlifetime", 86400);

マニュアルを引用すると以下のような感じ。

session.gc_maxlifetime は、データが 'ごみ' とみなされ、消去されるまでの秒数を指定します。 ガベージコレクション (ごみの収集) は、 セッションの開始時に行われます (session.gc_probability と session.gc_divisor に依存します)。

つまり、セッションの有効期限。
発行されてから上記の時間を経過したセッションは破棄されて、利用できなくなります。

上記の例では 86400秒(1日)で期限が切れて、セッションが削除されます。

が、マニュアルにも書いてありますが、この動作は session.gc_probability と session.gc_divisor の値に依存します。


ということで、session.gc_probability 。

ini_set('session.gc_probability', 100);

これは、session.gc_probability 単体だとよく意味が分からないと思うので、session.gc_divisor の説明も引用します。

session.gc_probability
session.gc_probabilityと session.gc_divisorの組み合わせでgc (ガーベッジコレクション)ルーチンの始動を制御します。 デフォルトは、1 です。 詳細はsession.gc_divisor をご覧ください

session.gc_divisor
session.gc_divisorと session.gc_probabilityの組み合わせで すべてのセッションの初期化過程でgc(ガーベッジコネクション)プロセス も始動する確率を制御します。確率は gc_probability/gc_divisor で計算されます。例えば、1/100は各リクエスト毎に1%の確率でGCプロセスが 始動します。 session.gc_divisorのデフォルトは100です。

簡単に言うと、php はガーベッジコレクションの実行を session_start() がコールされる度に毎回実行するのではなくて一定の確率で実行していて、その確率を制御するのが session.gc_probability と session.gc_divisor ということです。

ガーベッジコレクションが発生する確率は
session.gc_probability/session.gc_divisor
で表すことが出来、デフォルト値は
session.gc_probability = 1
session.gc_divisor = 100
となっているため、1%の確率で実行されることになります。

テストをしている際に、明らかにセッションの有効期限を過ぎているのに破棄されれずに残ったりするのはこれが原因で、単純にガーベッジコレクションが実行されていない可能性が高いです。

まぁ、この設定自体は問題ないと思いますが、1% の確率で実行される状態だとテストなどでは面倒なので、以下のように設定して毎回ガーベッジコレクションが行われるようにすると良いと思います。

ini_set('session.gc_probability', 100);

session.gc_probability を 100 に設定するのではなく session.gc_divisor を 1 に設定しても結果は同じです。

ini_set('session.gc_divisor', 1);

ちなみに、これらの設定は session_start() よりも前に実行されるように書く必要があります。


で、今回は MySQL にセッションを保存するような形で実装していたのですが、ガーベッジコレクションで削除対象のレコードを絞り込む際の計算は注意が必要です。

たまに、以下のような感じで期限切れの日時を求めているものがありますが、MySQL では期待通りの動作になりません。

DELETE
FROM sessions
WHERE updated < CURRENT_TIMESTAMP + '-". $maxlifetime . " secs'

上記の期待している動作としては現在の時間から $maxlifetime秒前の時間を作り、それよりも前に作成されたセッションを削除する感じですが、これだと期待通りにはなりません。

何故か?

CURRENT_TIMESTAMP + '-". $maxlifetime . " secs'

上記のSQL文では 現在の時間から $maxlifetime 秒を引いた値を返してこないからです。

ではどう書くかというと、以下のような感じです。

CURRENT_TIMESTAMP - interval " . $maxlifetime . " SECOND

まぁ、これだけ書いてもよく分からないと思うので、結果を比較する意味で以下の SQL を実行したいと思います。

SELECT CURRENT_TIMESTAMP CT, 
	CURRENT_TIMESTAMP +  '-86400 secs' CTsecs, 
	CURRENT_TIMESTAMP - interval 86400 SECOND CTinterval

結果は以下のように感じになります。

+---------------------+----------------+---------------------+
| CT                  | CTsecs         | CTinterval          |
+---------------------+----------------+---------------------+
| 2012-02-02 15:25:26 | 20120202066126 | 2012-02-01 15:25:26 |
+---------------------+----------------+---------------------+

CT はそのまま現在の時間で、CTinterval は 現在の時間から一日前の日時を返します。

が、CTsecs を見ると 20120202066126 となっており、そもそも日付型のデータですらない形式のように思えますが、一応桁数だけは合っていて、2012-02-02 06:61:26 と読むことも出来ます。

で、こうすると何となく分かるのですが、要はこれって CT のデータを単純に数値として考えて 86400 を引いた数なんですよね。

つまり、こういう感じです。

2012-02-02 15:25:26 '-86400 secs'
	↓
20120202152526 - 86400
	=
20120202066126

しかも、MySQL は 20120202066126 を渡しても結構普通に日付型として見た時に一番近い値として処理してくれる様なので、$maxlifetime に比較的小さな値の時が入る場合には気が付きにくいのかも知れません。

例えば、1800秒 は通常であれば 30分ですが、上記の様な計算方法だと 18分を指定した場合に近い動作になります。
が、これを普通にテストしていて気がつくのは難しいかなぁっと思いました。

私の場合は 2592000秒(30日)を指定していたのに、約3日でセッションが削除されているという現象が発生して気が付きました。

今回はセッション破棄のタイミングなので運用上問題がなければ良いのですが、結構危険なので注意が必要かも知れません。

更新日: