かなり前からApache 2.2.xを使っているのですが、mod_cache/mod_disk_cacheなんていうモジュールが存在することに全然気がついていませんでした。このモジュールはサーバサイドでコンテンツキャッシングを実現するもので、CGIなどを使って生成される動的なコンテンツのレンダリング結果の再利用を可能にします(静的なコンテンツもキャッシュできますが、応答時間が問題になることはありませんし、一般的にはクライアントサイドでキャッシングされます)。キャッシュするコンテンツは時間制約の強くないものである必要があります。例えば、コメンティングシステムなどではユーザが行った操作をコンテンツに即座に反映する必要があるために適していませんが、検索システムやCMSのように対象となるデータセットが一定以上の間隔で更新されると期待され、レンダリング結果が変化しないような場合にはとても有効です。
Movable Type 3.3以降ではタグアーカイブの生成にmt-search.cgiが使われていてまたえらく遅いのですが、これを超高速化できるのではないかと思って試してみました。
httpd.confの記述
まず、何はなくともmod_cache, mod_disk_cacheをロードしましょう。
LoadModule cache_module libexec/apache22/mod_cache.so LoadModule disk_cache_module libexec/apache22/mod_disk_cache.so
次にmod_disk_cache - Apache HTTP サーバやmod_cache - Apache HTTP サーバを読んで必要な設定を加えます。最小限の設定は以下のようになります。
<IfModule mod_cache.c> <IfModule mod_disk_cache.c> CacheRoot /var/www/cache CacheEnable disk /MTDIR/mt-search.cgi </IfModule> </IfModule>
httpd.confの編集が済んだらapachectl restartします。
MT::Bootstrapの修正
HTTP/1.1: Caching in HTTPあたりを読むと、クエリ文字列付きのHEAD/GETリクエストでは明示的に有効期限をサーバが返さない限り、キャッシュ機構はレスポンスをfreshなものとして取り扱ってはならない旨が書かれています。
一方、mod_cacheにはCacheIgnoreNoLastModという、上記のRFC2616の項をいい感じに無視してくれそうなディレクティブが用意されています。しかし、これを有効にした場合には、通常の「ETag、Last-Modfied、Expiresヘッダのいずれか一つを持ち、クエリ文字列のない」URLに加え、「ETag、Last-Modfied、Expiresヘッダのいずれもないが、クエリ文字列のない」URLのコンテンツもキャッシュされるようになりますが、「ETag、Last-Modfied、Expiresヘッダのいずれもなく、クエリ文字列がある」URLは依然としてキャッシュされません。
結局のところ、mod_cacheにキャッシュしてもらうには、mt-search.cgiが「ETag、Last-Modfied、Expiresヘッダのいずれか」を返答するように変更する必要があるということです。と言っても難しい変更ではなく、MT 3.34を例にとると以下の小変更で済みます。
--- lib/MT/Bootstrap.pm.bak Wed Jan 10 12:11:30 2007 +++ lib/MT/Bootstrap.pm Mon Feb 12 02:12:43 2007 @@ -63,11 +63,15 @@ local $SIG{__WARN__} = sub { $app->trace($_[0]) }; MT->set_instance($app); $app->init_request(CGIObject => $cgi); + $app->set_header('Expires' => '+1h') + if $class eq 'MT::App::Search'; $app->run; } } else { $app = $class->new( %param ) or die $class->errstr; local $SIG{__WARN__} = sub { $app->trace($_[0]) }; + $app->set_header('Expires' => '+1h') + if $class eq 'MT::App::Search'; $app->run; } };
この例ではキャッシュの有効期間を1時間に設定していますが、お好みで変更するとよいでしょう。
性能は?
試しに100回ほど問い合わせてみました。
キャッシュなしの場合:
$ ab -n 100 'http://example.org/MTDIR/mt-search.cgi?tag=x&blog_id=1' (snipped) Server Software: Apache/2.2.4 Server Hostname: example.org Server Port: 80 Document Path: /MTDIR/mt-search.cgi?tag=x&blog_id=1 Document Length: 3877 bytes Concurrency Level: 1 Time taken for tests: 41.432825 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 423600 bytes HTML transferred: 387700 bytes Requests per second: 2.41 [#/sec] (mean) Time per request: 414.328 [ms] (mean) Time per request: 414.328 [ms] (mean, across all concurrent requests) Transfer rate: 9.97 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 404 413 22.5 410 598 Waiting: 399 408 22.5 404 592 Total: 404 413 22.5 410 598 Percentage of the requests served within a certain time (ms) 50% 410 66% 410 75% 410 80% 410 90% 414 95% 415 98% 516 99% 598 100% 598 (longest request)
キャッシュありの場合:
$ ab -n 100 'http://example.org/MTDIR/mt-search.cgi?tag=x&blog_id=1' (snipped) Server Software: Apache/2.2.4 Server Hostname: example.org Server Port: 80 Document Path: /MTDIR/mt-search.cgi?tag=x&blog_id=1 Document Length: 3877 bytes Concurrency Level: 1 Time taken for tests: 0.487676 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 426592 bytes HTML transferred: 387700 bytes Requests per second: 205.05 [#/sec] (mean) Time per request: 4.877 [ms] (mean) Time per request: 4.877 [ms] (mean, across all concurrent requests) Transfer rate: 853.03 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 0 4 41.1 0 411 Waiting: 0 4 41.0 0 410 Total: 0 4 41.1 0 411 Percentage of the requests served within a certain time (ms) 50% 0 66% 0 75% 0 80% 0 90% 0 95% 0 98% 0 99% 411 100% 411 (longest request)
41.43秒から0.49秒に高速化!!!
もう少しちゃんと見ると、キャッシュありの方は最初の一回目のリクエストに411msecかかっていて、残りの99回分のリクエストには77msecしか要していません。したがって、キャッシュヒット時には500倍以上速くなっているということです。条件によって結果はいろいろ変わってきますけどね。
ついでなので、FastCGI (mod_fcgid)でキャッシュあり・なしのデータも追加しておきます。
FastCGI (mod_fcgid) + キャッシュなしの場合:
$ ab -n 100 'http://example.org/MTDIR/mt-search.fcgi?tag=x&blog_id=1' (snipped) Server Software: Apache/2.2.4 Server Hostname: example.org Server Port: 80 Document Path: /MTDIR/mt-search.fcgi?tag=x&blog_id=1 Document Length: 3877 bytes Concurrency Level: 1 Time taken for tests: 2.945091 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 419600 bytes HTML transferred: 387700 bytes Requests per second: 33.95 [#/sec] (mean) Time per request: 29.451 [ms] (mean) Time per request: 29.451 [ms] (mean, across all concurrent requests) Transfer rate: 138.88 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 29 29 1.3 29 42 Waiting: 28 28 1.4 28 42 Total: 29 29 1.3 29 42 Percentage of the requests served within a certain time (ms) 50% 29 66% 29 75% 29 80% 29 90% 29 95% 29 98% 29 99% 42 100% 42 (longest request)
FastCGI (mod_fcgid) + キャッシュありの場合:
$ ab -n 100 'http://example.org/MTDIR/mt-search.fcgi?tag=x&blog_id=1' (snipped) Server Software: Apache/2.2.4 Server Hostname: example.org Server Port: 80 Document Path: /MTDIR/mt-search.fcgi?tag=x&blog_id=1 Document Length: 3877 bytes Concurrency Level: 1 Time taken for tests: 0.107752 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 426570 bytes HTML transferred: 387700 bytes Requests per second: 928.06 [#/sec] (mean) Time per request: 1.078 [ms] (mean) Time per request: 1.078 [ms] (mean, across all concurrent requests) Transfer rate: 3860.72 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 0 0 3.0 0 30 Waiting: 0 0 2.9 0 29 Total: 0 0 3.0 0 30 Percentage of the requests served within a certain time (ms) 50% 0 66% 0 75% 0 80% 0 90% 0 95% 0 98% 0 99% 30 100% 30 (longest request)
まとめると私の環境でのmt-search.cgiの応答時間は、キャッシュヒット時には約0.8msec、キャッシュミス時にはCGI版で約400msec、FastCGI版で約30msec、ということになります。念のため、キャッシュミス時の応答時間はアプリケーションにも依存しますし、キャッシュヒット時の応答時間はキャッシュしているデータのサイズに依存します。
また、このエントリーで書いたmod_cacheを使った高速化手法は、mt-search.cgi以外の任意のアプリケーションに適用可能です。冒頭でも触れましたが、比較的ルーズなコンシステンシを実現すればいいようなコンテンツ配信の高速化には絶大な効果がありますね。
0 コメント:
コメントを投稿