難しいな。
(ノ・・)ン。。。。。。(((●コロコロッ
結論・・・というか落としどころは、
ストレージはデータベース
ってところか。
Yahoo!は正しいな。
セッションって言う概念はどのぐらい前から定着したのか。
今ほどセキュリティについて敏感になっていない頃は、
FormタグやURLにID、パスワードがゴンゴンかいてあった。
URLにPHPSESSIONIDというのを見かけるようになったのはPHP3のころ。
確かphplibというライブラリ集があって、
その中の一つだった記憶がある。
PHP4になって内部に組み込まれたんだったか。
クエリストリングからクッキー利用に仕様変更されていた。
そのころから今に至るまで、
PHPのセッションの仕組みって変わっていない。
CookieにIDを格納して、
それをキーにストレージからデータを取ってくる。
「そんなの[セッション]じゃねーよ」とJAVAユーザーに散々けなされた記憶もあるな。
(だったらjsessionってなんだよといいたい。)
確かに厳密な意味でのセッションではないが、
単発アクセスの集合体であるWebアプリではこの手法が主流になっていると思う。
さて、今回考えようと思ったのは、
セッションストレージ
私はセッションストレージにはmemcachedを使っていた。
これ自体にも議論は必要かもしれないが、
アプリケーションの規模からいうとこれで事足りる。
つまり、サーバが壊れたら「バックアップから復旧」状態で、
フェイルオーバーできるほど台数がないのだ。
(これで一日数百万ぐらいなら捌ける。)
が、そう言うわけにも行かないアプリケーションを作成することになり、
もう一度考えることにしてみた。
セッションを実装するとして、
そのストレージの選択肢として
ファイル(NFS)、データベース、memcachedとあると思う。
これらで最適なものってどれかちょっと考えて、
今後の指標にするか。
[More:]
まずはストレージの選択から。
●NFS
実装が一番楽で、
PHP側では特別設定不要。
お手軽さではNo.1だろう。
しかし、過去の経験では一番ナシの方法だ。
問題になるのはガベージコレクション。
セッションの仕組みからしてファイルの作成が大量発生する。
ファイルが溜まりすぎるとlsがなかなか返ってこなくなることがあると思うが、
それがネットワーク越しに発生する。
その間で待ちが発生して結果サーバを止めてしまうことになる。
つまりアクセス集中に極端に弱いのだ。
ファイルシステムの進化で解消されているかもしれないし、
cronでガーベージコレクションの対処も可能だが、
PHPのセッションをファイルでやった時の、
あの[セッションが切れてるのか切れてないのかはっきりしない]メリハリのない挙動はどうにもならない。
どっちにしてもファイルで管理はない。
●memcached
実装は楽な部類。
速度はダントツに速く、
安定動作する。
一見するとセッションには最適に見える。
問題はフェールオーバー。
セッションストレージが動かなくなった際、
データの復旧が出来ない。
複数のmemcachedサーバに同じデータを常に書き込んでおくという回避方法もあるようだが、
どうなんだろな。
サーバーってのは壊れるのが前提で考えなきゃいけないってのが、
何とも面倒くさい。
顧客に「壊れても復旧出来ませんよ(予算の都合で)」と言って納得して貰っても、
いざ壊れたらこちらのせいにされる理不尽さもなんとかして欲しい。
(だったら予算増やせっての!)
サーバ構成ってのは金をかけるべきだ。
●データベース
データストレージとしては定番。
ガーベージコレクションはデータ削除で対応、
フェールオーバーは標準装備(PostgreSQLは微妙だが)でデータ復旧も可能。
問題はPHPでの実装。
昔から思っている。
PHPのセッションハンドラ、
session_set_save_handlerの挙動はなんて怪しいのだろうかと。
( ̄-  ̄ ) ウーン
実装は別途考える方が良いか。
ということでデータストレージはデータベースということになるか。
オープンソースデータベースの代表的なものというと
MySQLとPostgreSQLか。
Firebirdは個人的に好きだがネットワーク越しに使えないと利用場面が全然無い。
PHPのモジュールには
session_mysql,
session_pgsqlというのが存在する。
これらを使えば「実装」は果てしなく楽になる。
が、これは問題だらけでどちらも使えない。
まず
session_mysqlだが、
これはトランザクションセーフではない。
つまりMyISAMで使うことを前提にしているのだ。
セッションの仕組みから考えてトランザクションは必須。
ならば、InnoDBで利用するのが当たり前なのだが、
これはそれを考慮されていない。
もう一つの問題がgc時の挙動。
deleteでデータを削除するのはいいのだが、
その後でoptimaze tableが走る。
設定にも依るが100回に1回の確率でoptimize tableが走ると、
アクセス集中時にサイトを止めてしまいかねない。
次に
session_pgsql。
これの問題はデータベースではなくモジュール側。
なぜかセッション発生時にWebサーバ側にファイルを作成する。
それを回避するために使っているのに、
本末転倒といったところか。
(検証が数年前なので解消されているかもしれない。)
さらに
session_pgsqlはunknown errorと格闘することになる。
初回アクセス時にunknown errorというwarningが表示されることがある。
unknown errorなので原因は結局わからなかったが、
一応動作はしていたのでwarningを消すなどとくだらないことをしたことがある。
(リストラクチャ時にsession_pgsqlは撤去した。)
データベースというと問題は別にもある。
それは「テーブル最適化」。
データベースといってもデータはファイルに格納しているに過ぎない。
となると、書き換えが大量に発生する仕組みでは、
大量に断片が発生する。
単にデータをdeleteすればいいという話ではなく、
MySQLならoptimize table, PostgreSQLならvacuum fullを打たなければならない。
どちらもテーブルロックが発生するので、
システムを停止する必要がある。
(PostgreSQLはデータベースの仕組みからしてセッションには向いてないと思う)
セッションをデータベースで作る時は定期メンテナンスが前提だ。
( ̄-  ̄ ) ウーン
となると落としどころはMySQL+InnoDBと言ったところか。
MySQLで実装するとなると、
session_mysqlが使えないので、
自前実装と言うことになる。
それは良いのだが、
session_set_save_handlerを使うと、
また別途問題が発生する。
上で軽く触れたが、
例えばMySQLでセッションを実装しようとして、
session_set_save_handlerに登録すると、
原因不明のセッション切れが起こる。
原因がわからないので別の方法を模索することにするみたいな展開。
今まで
session_set_save_handler+データベースでセッションが構築出来たことがない。
考え方を変えて、
セッションの仕組み自体も自前で作るというのはどうだろうか。
開発量は多少あるが挙動を安定させるには仕方がないだろう。
session.use_trans_sidが使えなくなるのはちょっと痛い。(DoCoMoに抗議したい)
MySQLへの接続はPDOを使えば速度の問題は気にならないほどだ。
(その内公開?いや・・・どうだろ。)
ちなみにsession_startって記述されているライブラリは全部使えなくなる。
PEAR::Authは卒業かな。
フレームワーク利用しているからもう使ってないけど。
--disable-sessionでコンパイルして関数を上書き定義するって方法でいくか。
ってことで、
session_set_save_handlerを使わない
ストレージはデータベース(MySQLがベスト)
というところか。
はき出しただけで、考察ですらないけど。。。
この方向でセッションを作ることにしよう。
追記:
2007-11-26発表の
PECL::memcacheの最新バージョン3.0で、
冗長化機能が搭載されていた。
http://pecl.php.net/package/memcache/3.0.0
See README for details on new API and INI directives.
- UDP support
- Binary protocol support
- Non-blocking IO using select()
- Pipelined multi-set/delete/increment/decrement
- CAS (Compare-And-Swap) support
- Append/prepend support
- Key and session redundancy (values are written to N mirrors)
- Improved error reporting and failover handling
- Added class "MemcachePool" implementing the new API
- New INI directives
memcache.protocol = {ascii, binary}
memcache.redundancy = 1
memcache.session_redundancy = 2
- Changed INI defaults
memcache.hash_strategy = consistent
memcache.chunk_size = 32768
これは試す価値はあるかもしれない。
一度検証してみよう。