仕事、先日から引き続いて、負荷分散やらベンチマークやら。
特にチューニングしないで平均 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の負荷はあまり上がらないから、パフォーマンスの向上が見込める。めでたい。
