PHPのセッション管理に使う箱選び 1

PHPには元々セッション管理の機能が用意されているので、一般的な環境であれば組み込みの関数を呼ぶだけでセッション変数を介して値の保存と取得ができるようになる。
この機能は、内部的にはセッション変数の内容をシリアライズした文字列をファイルに保存し、次のリクエスト時にはファイルの内容をアンシリアライズし変数に展開する方法で実現されている。ファイルはsession.save_pathで設定されたディレクト*1下にセッションIDを元にした名前で作られる。
また、セッションが開始される時、session.gc_probability / session.gc_divisorの確率でGCが起動しsession.gc_maxlifetime秒前から更新されていないセッションのファイルが削除され、古いセッションは無効になる。

以上は初期設定でPHPのセッション管理機能を使った場合の動作なのだけど、この方式にはいくつか問題がある。

  1. 複数のWebサーバにリクエストを振り分けているとセッションの内容が共有されない*2
  2. セッションの数が増えると特にGCの負荷がとんでもない事になる

そのため、ある程度規模が大きいWebアプリケーションでは別の方法を採る事になる。
セッションが共有されない点についてはローカルディスク上にデータを保存している以上解決が難しいので、まずは共有できるセッションデータの保存先を探さねばならない。

ネット上を探してみたところ

あたりが一般的に使われているらしい。これらのうち、DB(MySQL),memcachedについてベンチマークを取ってみた。

ベンチマーク方法

クライアントからサーバに対してabでベンチマークを実行。リクエスト数は500,1000,5000,10000,50000,100000,500000に設定し、50万件以外はそれぞれ3回実行した平均値を結果として採用。同時接続数は10。
DBとかのサーバはlocalhostで動かしているので、数字などはその程度の信頼性という事で。
詳しい環境は↓に。

サーバ

php.iniのセッション関連設定

session.save_handler = files
session.serialize_handler = php
session.save_path = "/tmp"
session.gc_probability = 1
session.gc_divisor     = 100
session.gc_maxlifetime = 1440
session.entropy_length = 16
session.entropy_file = /dev/urandom
session.hash_function = 0
session.hash_bits_per_character = 4
クライアント

標準ファイル方式

比較対象として、PHP標準のファイルを使った方式のベンチマーク結果。

Y軸が1秒間に処理できたリクエスト数で、X軸が総リクエスト数。数字が大きいほど良い。
abはCookieを設定せず動かしたので、リクエスト度にセッションファイルが生成され、GCが起動する度に全ファイルを調査するのでどんどん遅くなっている。GCを切った状態でもベンチマークを採ってみたけど、傾向としては同じ感じ。


Y軸が最も遅かったレスポンスの待ち時間(ミリ秒)。GCありの場合は50万リクエスト時で500秒近く待つ場合があるみたいだ。

標準ファイル方式の場合は5000セッションくらいが限界といった結果。ファイル保存先の階層を増やし*3GCをcronとかで動かせばもう少しがんばれそうだけど、がんばらずにサーバを追加した方が良いと思う。

*1:初期設定は/tmp

*2:ロードバランサによっては「同じセッションIDは常に同じサーバへ振り分ける」という機能を持つ物もあるが特定のサーバをメンテナンスで落としたい時などに困る

*3:設定で可能