mod_perlについて
mod_perlはPerlスクリプトを高速化する仕組みで、「ModPerl::PerlRun」モードではインタプリタ(Perlの実行エンジン的なもの)をメモリに保持し、「ModPerl::Registry」モードではインタプリタに加えてコード全体もメモリに保持するのでグローバル変数なんかは次回呼び出し時にも残ります。
実行速度よりもオーバーヘッドが減る感じですが、比較として以下の適当なスクリプトを実行してみます。
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "<html><body><pre>"; print "test"; print "</pre></body></html>";
テストは「Apache Bench」を使います。
ab -c 10 -n 10000 http://localhost/test.cgi
テスト用の古い小型PCなので性能的にアレですが、以下のような感じで結果が表示されます。
Server Software: Apache Server Hostname: localhost Server Port: 80 Document Path: /test.cgi Document Length: 41 bytes Concurrency Level: 10 Time taken for tests: 2.5275 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 176000 bytes HTML transferred: 41000 bytes Requests per second: 498.68 [#/sec] (mean) Time per request: 20.053 [ms] (mean) Time per request: 2.005 [ms] (mean, across all concurrent requests) Transfer rate: 85.28 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: -1 0 1.3 0 42 Processing: -13 18 31.6 17 945 Waiting: -17 9 31.1 8 932 Total: -13 18 31.6 17 945 Percentage of the requests served within a certain time (ms) 50% 17 66% 18 75% 19 80% 19 90% 21 95% 24 98% 60 99% 69 100% 945 (longest request)
このなかの「Requests per second:」が1秒間に処理可能なリクエスト数を表示しており、CGIモード(非mod_perl)では「1秒間に101.42リクエスト」ですが、
Requests per second: 101.42 [#/sec] (mean)
「ModPerl::Registry」モードで実行してみると「1秒間に498.68リクエスト」と5倍くらいの速度になりました。
Requests per second: 498.68 [#/sec] (mean)
レスポンスの良さはFirefoxなどの開発者ツールの「ネットワーク」タブでも確認できます。
F5でページ更新して4回表示した例ですが、CGIモード(非mod_perl)では10ms~11msでレスポンスが返ってきているのに対し、
「ModPerl::Registry」では2ms~4msでレスポンスが返ってきています。
上記のテストスクリプトはサイズが小さいため「ModPerl::PerlRun」で実行してもレスポンスは2ms~5msと違いは出ませんでしたが、もっと行数の多いスクリプトだとコードをキャッシュする「ModPerl::Registry」との違いが出てくると思います(大きなスクリプトではModPerl::Registryの方が有利)
「ModPerl::Registry」モードではグローバル変数はメモリに保持したままなので、値が問題ないか等のチェックをする必要はありますが、RAMディスクみたいな使い方でディスクI/Oを減らす事も可能です。
ここではCentOS7上で動作するApache2.4へmod_perlを導入する手順を説明します。
mod_perlの導入
導入済みか確認
インストールしたapacheによっては既に導入されている事もありますので確認しておきます。
httpdに「-M」オプションでモジュール一覧が表示できます。
httpd -M | grep perl
導入されていない場合は何も表示されませんが、導入済みの場合は以下のように「perl_module」が表示されます。
[root@aimix]# httpd -M | grep perl perl_module (shared)
mod_perlの導入
今どきのapacheなら対応していますが、DSO(Dynamic Shared Object)に対応しているか確認しておきます。
DSOはapacheのコンパイル時に導入していなかったモジュールを後から導入できる仕組みです。
「httpd -l」と実行して「mod_so.c」があればOKです。
[root@aimix]# httpd -l Compiled in modules: core.c mod_so.c http_core.c
今回はyumを使いますが、導入済みのパッケージ管理ツールで行えばOKです。
yum --showduplicates list | grep mod_perl
EPELレポジトリにありましたのでこれをインストールします。
一般的なレポジトリに無い新しいバージョンのmod_perlを使いたい場合でも、apacheがDSO対応であればmod_perlをコンパイルして出来た「mod_perl.so」を読み込ませれば良いだけなので結構簡単に導入できますが、組み合わせによっては不具合が出るかもしれません。
[root@aimix ~]# yum --showduplicates list | grep mod_perl mod_perl.x86_64 2.0.11-1.el7 epel mod_perl-devel.x86_64 2.0.11-1.el7 epel
インストールします。
yum install mod_perl
依存で2つくっついてきました。
==================================================================================================================================== Package アーキテクチャー バージョン リポジトリー 容量 ==================================================================================================================================== インストール中: mod_perl x86_64 2.0.11-1.el7 epel 3.0 M 依存性関連でのインストールをします: perl-BSD-Resource x86_64 1.29.07-1.el7 epel 38 k perl-Linux-Pid x86_64 0.04-18.el7 epel 14 k トランザクションの要約 ==================================================================================================================================== インストール 1 パッケージ (+2 個の依存関係のパッケージ)
インストール完了したら再びモジュール一覧を表示してみます。
httpd -M | grep perl
以下のように表示されればOKです。
[root@aimix ~]# httpd -M | grep perl perl_module (shared)
導入したモジュールは以下のディレクトリに配置されます。
/usr/lib64/httpd/modules/
mod_perlの導入では以下のファイルが追加されていると思います。
mod_perl.so
Apacheの設定
設定ファイルと基本的な設定
最近のapacheだとPerl関連は以下のファイルに分けられていますが、httpd.confに記述しても問題ありません。
/etc/httpd/conf.d/perl.conf
先頭あたりに以下の行がありますが、コメントアウトを解除すると警告をエラーログへ出力します。
既存のスクリプトをmod_perl化していくにあたり、こういった警告は重要なヒントになるのでONにしておいた方が良いですが、この設定のコメントにあるように警告をエラーログへ出力するのはパフォーマンス低下の原因になるため、テスト環境でONにし、本番環境ではコメントアウトするのも良いと思います。
#PerlSwitches -w
以下は汚染検出モードのスイッチなので、アンコメントで有効にしておきます。
汚染検出というのは、フォームなどから値にパイプでつなげてコマンドを送ってサーバ上で実行させるような攻撃がありますが、こういった値をチェックしてくれる機能です。
完璧ではありませんが、保険のために有効にしておくと良いです。
PerlSwitches -T
特定のCGIだけmod_perlで動作させる
mod_perl対応化しないスクリプトもあると思いますので、例えば「test.cgi」だけmod_perlで動作させる場合は以下のようにします。
<Files test.cgi> SetHandler perl-script PerlHandler ModPerl::Registry PerlSendHeader On </Files>
拡張子.cgiすべてをmod_perlで動作させる場合。
<Files *.cgi> SetHandler perl-script PerlHandler ModPerl::Registry PerlSendHeader On </Files>
mod_perlで動作しているか確認する
mod_perlで動作しているスクリプトは環境変数の $ENV{‘MOD_PERL’} と $ENV{‘MOD_PERL_API_VERSION’} へ値がセットされますので確認します。
以下を test.cgi で保存して、先程のFilesディレクティブで指定し、httpdを再起動してからアクセスします。
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "<html><body>"; print "$ENV{'MOD_PERL'}: $ENV{'MOD_PERL'}<br>"; print "$ENV{'MOD_PERL_API_VERSION'}: $ENV{'MOD_PERL_API_VERSION'}"; print "</body></html>";
CGIモードの場合は以下のように値がセットされていませんが、
$ENV{'MOD_PERL'}: $ENV{'MOD_PERL_API_VERSION'}:
mod_perlで動作させたスクリプト内では以下のように環境変数が表示されます。
$ENV{'MOD_PERL'}: mod_perl/2.0.11 $ENV{'MOD_PERL_API_VERSION'}: 2
格納されている環境変数を全部見たい場合。
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "<html><body><table>"; foreach my $key (sort keys %ENV) { print "<tr>"; print "<td>$key</td><td>$ENV{$key}</td>"; print "</tr>"; } print "</table></body></html>"; exit;
キャッシュ
Perlスクリプトとmod_perlのキャッシュ
例えば以下のスクリプトを見てみます。
「my $count = 1」と初期化しているように見え、確かにCGIモードだと常に「1」ですが、mod_perl環境で動作させて「F5」キーで更新してみると「1.2.3.4…」とカウントアップします。
#!/usr/bin/perl print "Content-type: text/plain\n\n"; my $count = 1; &counter(); exit; sub counter { print $count; $count++; }
このような場合はサブルーチンにちゃんと値を渡してあげます。
#!/usr/bin/perl print "Content-type: text/plain\n\n"; my $count = 1; &counter($count); exit; sub counter { $count = $_[0]; print $count; $count++; }
スクリプトの更新
Perlスクリプトが更新されればキャッシュは破棄され新しく生成してくれるので、通常どおりFTPクライアントなどでアップロードするだけでOKです。
メモリリーク
CGIモードの場合は毎回破棄されるので変数や配列は破棄しなくても問題ありませんでしたが、mod_perlの場合はキャッシュしてしまうので、書き方によってはどんどこメモリ使用量が増加する可能性があるため、初期化したり必要がなくなったら開放するよう注意する必要があります。
逆にキャッシュされるのを利用して、ディスクから読み出さずに変数や配列の値をそのまま使う事もできますが、破棄のタイミングを自分でコントロールしているわけではないので注意が必要です(apacheのプロセス再生成などでも破棄されるため)
値をチェックして必要な場合のみディスクから読み出すようにすれば、上手くやれば大きなデータの場合はディスクI/Oが激減し、サーバ全体の速度やロードアベレージの軽減など大幅に改善する場合もあります。
Comment