まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2006/07/19(水)Perl CGIのキャッシュ環境

FastCGI対応は書き間違えだったのですが、どうせならということで対応してみました(対応版の配布はβ4以降になります)。

Perl CGIのキャッシュ動作比較

気づいたらあれこれ対応しすぎた感じがありますが、せっかくなのでどれが一番速いのか試してみしまた(笑) いずれの設定も全くチューニングも何もしていませんので、参考程度にお願いします

CPU  : Pentium3 800MHz (133*6/Coppermine)
OS   : FreeBSD 6.1-PRERELEASE
Web  : Apache 2.2 (prefork)
ETC  : DBキャッシュON
TEST : ab -n100 -c4 http://127.0.0.1/xxx/adiary.cgi/user/

テストしたページには適当な日記が数件表示されています。結果は数回実行して頻出値に近い値を取りました。

動作モードReq/secms/Req
cgi2.10477.270
speedycgi15.1965.825
mod_speedycgi26.4137.863
mod_fastcgi21.2846.990
mod_fcgid21.2147.157
mod_perl2*126.6937.473
text-file196.75.084

mod_fastcgiとmod_fcgidは差がなくて、mod_perl/mod_speedycgiが一歩前に出てるという感じですね。worker動作(スレッドモデル)となると、対応しているのは mod_perl2 vs mod_fcgid だけ。mod_perl2 はいかんせん導入が面倒くさいので、手軽さでは mod_fcgid の方がよいのかもしれません。

本格的にパフォーマンスを求めたり、高負荷時のメモリ消費量の少なさを考えると mod_perl2 on worker MPM に優る選択肢はないのですが個人では必要ないでしょう。*2

ただ、どれも Apache にモジュールを組み込まないとならないので、お手軽に高速化したい場合はSpeedyCGIソースコード)をオススメします。パフォーマンスも(個人で使うには)十分ですし、Apacheからは完全にcgiとして見えるので(プロセスが完全に分離するので)、精神的にもよいです。

誰か同じことをMTの管理画面で試さないかなぁ(笑)

*1 : Apache::Reloadなどのハンドラを外してやれば1ms程度改善しました

*2 : メモリ消費量を計測したわけではありませんが、mod_perl2/worker ではPerl自体もスレッド動作になるので、1リクエスト=1プロセスの mod_fcgid 系では勝負にならないと思います

各方式の特徴

せっかくなので、簡単に仕組みと特徴を説明。

Perl/cgi

通常のcgi動作。Apacheが、該当のcgiプログラムを fork し実行します。Perl/cgiでは、cgiファイル(使用ライブラリファイル群)のコンパイル時間がとても長く、度々問題となります。*3以下の方式は、どれもこのコンパイル時間を減らすことで大きな高速化を果たしています。

SpeedyCGI(=PersistentPerl)/mod_speedycgi

Apacheからみたら通常のcgiプログラムですが、スクリプト1行目にPerlではなくSpeedyCGIを起動するための

#!/usr/bin/speedy

を記述することでSpeedyCGIが動作します*4。SpeedyCGIは内部的にPerlを呼び出し、実行終了後もそのプロセスを常駐させ(バックエンドと言う)、続いてリクエストが来たときに空いたバックエンドがあればそれに処理を行わせます。

スクリプト側から見た場合、Apacheプロセスと分離されることを除けば*5、全体的な動作はほとんどmod_perlと同じです。

歴史が浅いのか何なのかいまいちマイナーだけど、導入も容易で、mod_perlでは動作しないスクリプトも動作し、Apacheとは別プロセスになるので使うのも気楽です。SpeedyCGIをインストールしてあるレンタルサーバもみかけます。

mod_speedycgiはSpeedyCGIの管理プログラム起動コストを押さえるためApacheにモジュールとして組み込んだものです。それによりかなり高速化されますが、worker MPMには非対応なので注意が必要です。

FastCGI/mod_fcgid

SpeedyCGI的な考え方をより推し進めて、Apache内部にバックエンドプロセスを管理する機能をモジュールとしてインストールしたものです。SpeedyCGIでは、いくらバックエンドがあるとはいえ結局は別プロセスである SpeedyCGI(/usr/bin/speedyそのもの)を fork して実行する必要がありましたが、FastCGIではバックエンドを直接操作するため fork のオーバーヘッドがありません。

mod_fastcgi(FastCGI)とmod_fcgidの差ですが、前者をスレッド動作のApacheに組み込むのはあまりよろしくないとのことです。また巷の噂によると後者の方が速いとかなんとか。なお、FastCGIをスレッド動作のApacheに組み込んだとしても、CGIプログラム(Perlなど)自体は1プロセス=1クライアントとなります。

cgiスクリプトはFastCGI向けに改造する必要があります。

use FCGI;
my $count = 0;
my $request = FCGI::Request();
while($request->Accept() >= 0) {
	print("Content-type: text/html\r\n\r\n", ++$count);
}

mod_perl

バックエンドどころか、「Perl自体をApacheに抱え込んでしまえばいい」という荒技的な解決策を提示するのが、mod_perl。荒技なだけに、もっともオーバーヘッドが少ない*6方式です。mod_perlの特徴として、Apacheの内部動作をスクリプト側から事細かに制御できますが、移植性(配布性)を考えたソフトではあまり使うことはありません*7

mod_perlには、Apache 1.x系向けのいわゆるmod_perl(以下mod_perl1と表記)と、Apache 2.0/2.2系向けのmod_perl2があり全く互換性がありません*8。この互換性が度々問題となって、mod_perl1向け書かれたcgiをmod_perl2で動かそうとすると誤動作して厄介な問題を引き起こします。その代表選手とも言えるものがMovable Type*9

スクリプト側から見た場合、mod_perl1やスレッド動作でないApache2ならば、およそSpeedyCGIと同じような感じです。ただ、Apache2が真価を発揮する worker(スレッド)動作でのmod_perl2は、Perlスクリプト自体もスレッド動作することで高い効率が得られる一方、カレントディレクトリを指定出来ない(chdir()できない)こと*10、あらゆるスクリプトがマルチスレッドでライブラリ空間(メモリ空間)を共有すること*11が大きなネックになります。後からこの対応を行うことは困難であり、これがmod_perl2向けスクリプトがほとんど存在しない原因になっていると思われます。

余談

Apache2(Prefock) + mod_perl2 のときは、次のようにすると自動で chdir されます(いずれか1つ選択)。

PerlResponseHandler ModPerl::RegistryPrefork
PerlResponseHandler ModPerl::PerlRunPrefork

*3 : 不必要なライブラリを極力ロードしないように気を付けているadiaryですら、cgi実行時間の約9割がPerlのコンパイル時間です。

*4 : この仕組みから分かるとおり、SpeedyCGIは何もcgiだけではなく、通常コンソールから実行するPerlスクリプトに対しても高速化が可能

*5 : Apache2のworkerであっても、chdirやumaskなどプロセス環境を変更することができる

*6 : 原理上もっとも速く動作する

*7 : Apache自体の動作を変えてしまうような、特殊なことをする専用アプリ開発には向いている

*8 : Apache2では、スレッド動作による効率化を求めたため、Apacheと一体となる mod_perl はApache 2系向けに大幅な改変が施されている

*9 : パッチを充てたり、mod_perl2の設定をあれこれ変更したりと涙ぐましい努力がたくさん見られます

*10 : サーバ全体で1つディレクトリにあるmod_perl2スクリプトしか実行しないとすれば、なんとかなるかも知れませんが……。きわどい。

*11 : 特定関数内で一時的であってもグローバル変数にデバッグフラグを立てるモードを設定するなどということが出来ない。