仕事、先日から引き続いて、負荷分散やらベンチマークやら。
特にチューニングしないで平均 30 requests/sec 程度を捌ける Web アプリケーション (mod_perl + DBI) があるのだが、 こいつを最大 100req/sec まで持っていきたい。
バックエンドの DB サーバ(PostgreSQL) に負荷があまり掛からない条件なら、Webサーバを並列にして3台も並べれば100程度行きそう。 が、30/sec の時点で DB サーバが限界近くに達する場合もあって、この場合は Webサーバを増やしても DB サーバがボトルネックになるので処理能力は上がらない。ここをなんとかしたい。
単純に考えれば、DBサーバのスペックを上げれば解決できる。 現状 Pentium III のシングルだから、Xeon の 4Way ぐらいにすれば、多分大丈夫。 ただし、最低200万円コース。
まあ、ハードウェアに物を言わせるのも芸がないので、ソフトウェア的になんとかしてみようと、 mod_perl の永続化環境を活かしてキャッシュを実装してみる。
具体的には、DBに問い合わせて得たオブジェクト (単なるハッシュリファレンスだったりする) を、パッケージグローバルな変数にぶち込んで、以後一定時間は DB にアクセスせずにそれを参照する、という方針で。 ユーザ情報のオブジェクトみたいに、更新をかける必要があるものはキャッシュせず、静的なテーブル(メンテナンス時に内容を更新するのみ) から得られたオブジェクトをキャッシュする。
以下はメモというか、動作検証してない殴り書き。
package CacheOjecct; use vars (%CACHE); sub get_cache { my $self = shift; my $id = shift; # キャッシュを特定する名前 my $expire = shift; # 有効時間 (秒) if(defined $CACHE{$id}{object}){ if($CACHE{$id}{time} + $expire > time){ # キャッシュに格納してから有効時間以上経ったので消去 delete $CACHE{$id}; return undef; } return $CACHE{$id}{object}; }else{ return undef; } } sub set_cache { my $self = shift; my $id = shift; # キャッシュを特定する名前 my $object = shift; # 格納するオブジェクト(内容不問) $CACHE{$id}{object} = $object; $CACHE{$id}{time} = time; # 格納時刻を記録 1; }
これを使うスクリプトの方では、
use CacheObject; use vars qw ($CACHE_OBJ); unless(defined $CACHE_OBJ){ $CACHE_OBJ = CachedOjecct->new(); } my $object; unless($object = $cache->get_cache('hoge', 60)){ # DB に問い合わせて $object を取得。 # $object = *************; $cache->set_cache('hoge', $object); # キャッシュに保存 }
こんな感じかな。安直だけど。
ただし、この実装だと httpd のプロセス毎に別々のキャッシュを(それぞれのメモリ空間に)
保持することになるので、少々メモリを無駄に使ってる気もする。
IPC::SharedCache モジュールでも使って共有メモリに保持すれば、消費メモリを節約できるんだろうけど。
# 実は CPAN には Apache::Cache とか、Cache::Cache とか、それらしいモジュールもあるので、そっちを使うのが利口だとは思う。
....
で、上記のように実装をしてベンチを取ってみると、劇的に DB への負荷が減少。 (キャッシュに溜まってるうちは DB を使わないのだから、当たり前といえば当たり前だが。) これなら Web サーバを単純に増やしても DBの負荷はあまり上がらないから、パフォーマンスの向上が見込める。めでたい。