読者です 読者をやめる 読者になる 読者になる

人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

ApacheでCGIを使う場合にpreforkを使った方が良い状況とそのチューニングについて

運用 研究 Webサーバ

かなり今更感の漂う内容ではありますが、意外と情報が分散していたり、Apache2.4系を考慮した場合に足りていない内容があったのでこのエントリで一度まとめてみようと思います。

CGIを使うようなシステムでそれなりにアクセスが集中するサーバ、例えば日々のピーク時のApacheのbusyワーカー数が1000になるようなサーバで、かつ、それを処理可能なマシンスペックのサーバであることを前提にしています。

ApacheのMPMとCGI実行アーキテクチャの復習

ApacheでCGIを使う場合には、MPMとCGI実行アーキテクチャの組み合わせは大きく分けて以下の2つに分ける事ができます。

  • worker(event) + mod_cgid
  • prefork + mod_cgi

Apacheの2.4系から特にworker(event) + mod_cgidのモデルが推奨されているようです。また、2.4系ではpreforkであってもデフォルトではmod_cgidが動作するようになっています。ですが、このまま使用すると状況によってはマシンの性能を十分に生かせない事があります。それらについて、まずはCGIを実行するためのアーキテクチャの違いからお話します。

mod_cgiとmod_cgidのアーキテクチャの違い

まずは簡単に復習ですが、mod_cgiはリクエストのあったCGIをApacheでプールしている複数のプロセスで分担してCGIを実行します。それに対して、mod_cgidはApache起動時にシングルスレッドのhttpdプロセスを起動させておき、CGIにリクエストがあった場合はリクエストを受けたhttpdプロセスとmod_cgid専用のシングルスレッドのhttpdプロセスがプロセス間通信を行い、実際のCGI実行はmod_cgid専用のプロセスで実行されます。通常このプロセスは一つです。

workerやeventといったマルチスレッドかつマルチプロセスのハイブリッド型MPMを使う場合はmod_cgidを使う事が推奨されています。その理由は以下のエントリやApacheのマニュアルに書かれています。

Unix オペレーティングシステムの中には、マルチスレッドのサーバから プロセスを fork するのが非常にコストの高い動作になっているものがあります。 理由は、新しいプロセスが親プロセスのスレッドすべてを複製するからです。 各 CGI 起動時にこのコストがかかるのを防ぐために、mod_cgid は子プロセスを fork して CGI スクリプトを実行するための 外部デーモンを実行します。 主サーバは unix ドメインソケットを使ってこのデーモンと通信します。

マルチスレッドのプログラムで、「自スレッド以外のスレッドが存在している状態」でfork*1を行うと、さまざまな問題を引き起こす可能性があります。「問題」の典型例としては、子プロセスのデッドロックが挙げられます。問題の詳細を把握しないまま、マルチスレッドのプログラムで不用意にforkするのはやめましょう!

上記のエントリのように、マルチスレッドのプロセスをガンガンfork()するのは安全でなかったり、コストの問題があったりするので、Apacheでは基本的にworkerやeventをMPMで使う場合にmod_cgidとの組み合わせで使用する事を推奨しているわけです。

マシンの性能向上に伴いmod_cgidプロセスがボトルネックに

しかし、実は状況によってmod_cgidにはスケールの面で問題があります。というのも、上記の仕様上どれだけhttpdのスレッドやプロセスをプールしていたとしても、CGIへのアクセスはシングルスレッドのmod_cgidプロセスで処理が一旦行われます。アクセス数の増加に合わせてマシンの性能を向上させ、CPUのコアの数も10以上になる事もざらにあるようなこの時代にも関わらず、です。

その状況で、メモリ効率や性能のためと思ってworkerやeventモデルとmod_cgidを合わせて使っていると、マシンスペックが向上してCPUコアが増え、大量のリクエストを受け付けられるようになっていくほどに、CGIの処理をシングルスレッドで担当しているmod_cgidプロセスが忙しくなります。そして、ついには一つのコアをCPU100%で占有してしまいます。

その結果、他に沢山コアがあるにも関わらず、mod_cgidのプロセスがボトルネックとなってその他のhttpdプロセスが待たされる状況が発生するのです。

つまり、CGIを使う状況によってはマシンスペックやアクセスパターンを考慮した場合、メモリ効率や静的コンテンツ配信の性能向上に効いてくるだろうという考えから、むやみにworkerやeventといったMPMを使わずに、あえて従来のprefork + mod_cgiを使う事も考えられるという話になります。

prefork+mod_cgiをチューニング

ではそういう状況ではどういうチューニングをするべきかを述べます。この組み合わせだと、CGIを沢山使う状況では、マシンの性能が向上しマルチコアの数が増えても、上述した通りmod_cgiは複数のマルチプロセスそれぞれがfork()してCGI実行を担当するので、効率良くマルチコアを使う事ができると考えられます。

この状況でのチューニングとしては、僕は以下の戦略でチューニングするようにしています。以降はApache2.4系でのディレクティブ名で記述します。

  • 日々のピーク時のbusyワーカーの2倍をMaxRequestWorkers(旧MaxClients)に設定する
  • StartServersMinSpareServersMaxSpareServersMaxRequestWorkersの数を同じにする
  • mod_bumpy_lifeを使ってサーバプロセスの破棄のタイミングを分散する

SpareServersやMaxRequestWorkers(旧MaxClients)を同じにする話はこれまでも散々検討されてきた内容で、この点については以下の記事で詳細に解説されています。

また、MaxRequestWorkersをピーク時のbusyワーカーの約2倍に設定しておけば、大抵の場合は効率良くリクエストを処理できるはずです。そのためには当然、そのプールしておくhttpdプロセスの数とSharedメモリを考慮した物理メモリ、さらには十分なCPUのコア数を載せておく必要があります。

さらに、上記の設定を同じ値にしておくと、MaxRequestsPerChildで設定した値のタイミングでhttpdプロセスがほとんど同時に全て破棄されてしまうので、その瞬間の性能が大きく低下してしまう可能性があります。その対策として、mod_bumpy_lifeを使う事で、httpdプロセスが破棄されるタイミングをばらしておいて、大きな性能低下が発生しないようにしておきましょう。

実際に、CGIプロセスの生成・破棄と、httpdの小サーバプロセスの生成・破棄のコストは大きく違っています。実験的に1リクエストに必ずCGIプロセスの生成・破棄を発生した場合と、httpdの子サーバプロセスの生成・破棄を発生させた場合とでは、数十倍性能に違いが出る事を以前確認しました。

ですので、StartServersMinSpareServersMaxSpareServersMaxRequestWorkersの数を同じにすることでコストの高いhttpdの子サーバプロセスのfork()を極力防止し、かつ、同時に複数のhttpd子サーバプロセスが破棄されないようにする必要があります。詳細は以下のエントリや論文を参考にして下さい。

worker+mod_cgidとprefork+mod_cgiのベンチマーク

それでは上記の考え方に従ってチューニングを行い、CPUコア24個でメモリ32GBの環境でベンチマーク比較を行いました。それぞれApache 2.4のworkerとpreforkの設定は以下になります。(MaxRequestWorkersだとシンタックスハイライトにならないので遺憾ながらMaxClientsで書いています)

worker + mod_cgidの設定
LoadModule mpm_worker_module modules/mod_mpm_worker.so
LoadModule cgid_module modules/mod_cgid.so
<IfModule worker.c>
    ServerLimit            16
    StartServers           16
    ThreadLimit           128
    ThreadsPerChild       128
    MinSpareThreads      2048
    MaxSpareThreads      2048
    MaxClients           2048
    MaxRequestsPerChild 40000
</IfModule>
prefork + mod_cgiの設定
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
LoadModule cgi_module modules/mod_cgi.so
<IfModule prefork.c>
    ServerLimit          2048
    StartServers         2048
    MinSpareServers      2048
    MaxSpareServers      2048
    MaxClients           2048
    MaxRequestsPerChild 40000
</IfModule>

これらに対して、hello.cgiという単純なCGIに対してweighttpで同時接続数300総接続数100万でベンチマークをしました。

hello.cgi
# curl example.jp/hello.cgi
<html><head><title>hello</title></head><body><p>hello world</p></body></html>
weighttpによる同時接続数300総接続数100万
# weighttp -k -c 300 -n 1000000 example.jp/hello.cgi

worker + mod_cgidのベンチマーク結果

CPUの使用状況

ベンチマークの途中でCPUの使われ方を簡単に見てみると、pid31659であるmod_cgidのプロセスのCPU使用率が100%に張り付いており、それがオーバーヘッドとなって各CPUのidle値が高く、遊んでいるCPUがかなり多い状態になっています。mod_cgidはclone()システムコールが大量に発生し、システムCPUの使用時間が支配的になっている状態でした。

top - 16:03:23 up 15 days,  4:08,  6 users,  load average: 0.22, 0.10, 0.04
Tasks: 493 total,   2 running, 488 sleeping,   0 stopped,   3 zombie
Cpu0  : 18.9%us, 10.5%sy,  0.0%ni, 69.9%id,  0.0%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu1  : 15.4%us,  8.4%sy,  0.0%ni, 76.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 10.9%us,  6.0%sy,  0.0%ni, 83.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  4.0%us, 64.9%sy,  0.0%ni, 31.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu4  :  5.6%us, 37.2%sy,  0.0%ni, 57.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu5  :  5.3%us,  3.3%sy,  0.0%ni, 91.4%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  :  8.0%us, 19.1%sy,  0.0%ni, 72.2%id,  0.0%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu7  :  3.4%us,  9.7%sy,  0.0%ni, 86.6%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu8  :  1.0%us,  2.7%sy,  0.0%ni, 96.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu9  :  0.7%us,  2.3%sy,  0.0%ni, 96.7%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu10 :  0.3%us,  2.0%sy,  0.0%ni, 97.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu11 :  0.3%us,  3.3%sy,  0.0%ni, 96.4%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu12 :  3.6%us,  4.3%sy,  0.0%ni, 92.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu13 : 17.5%us, 44.4%sy,  0.0%ni, 38.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu14 : 18.8%us, 48.0%sy,  0.0%ni, 33.2%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu15 :  8.0%us, 15.7%sy,  0.0%ni, 76.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu16 : 10.3%us, 29.7%sy,  0.0%ni, 60.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu17 : 13.0%us, 28.8%sy,  0.0%ni, 58.2%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu18 :  2.3%us,  8.0%sy,  0.0%ni, 89.3%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu19 : 20.9%us, 37.4%sy,  0.0%ni, 41.8%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu20 : 15.1%us, 34.9%sy,  0.0%ni, 50.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu21 :  6.9%us, 28.0%sy,  0.0%ni, 65.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu22 :  1.7%us,  9.7%sy,  0.0%ni, 88.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu23 :  0.3%us,  1.3%sy,  0.0%ni, 98.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32809072k total,  5593952k used, 27215120k free,   243008k buffers
Swap: 16777208k total,        0k used, 16777208k free,  4258232k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                         
31659 apache    20   0  246m  46m  680 R 99.7  0.1   0:15.32 httpd                                                                                                                            
 2746 nscd      20   0 1256m 5840 1796 S 13.6  0.0  14:40.55 nscd                                                                                                                             
30643 root      20   0 96492 9316  716 S  7.3  0.0   0:01.19 weighttp                                                                                                                         
  348 apache    20   0 2703m  56m 5276 S  3.6  0.2   0:00.66 httpd                                                                                                                            
31661 apache    20   0 2703m  55m 4616 S  3.6  0.2   0:00.65 httpd                                                                                                                            
32471 apache    20   0 2703m  55m 4616 S  3.6  0.2   0:00.62 httpd                                                                                                                            
31662 apache    20   0 2703m  55m 4652 S  3.3  0.2   0:00.61 httpd                                                                                                                            
31665 apache    20   0 2703m  56m 5276 S  3.3  0.2   0:00.62 httpd                                                                                                                            
31667 apache    20   0 2703m  55m 4616 S  3.3  0.2   0:00.60 httpd                                                                                                                            
31683 apache    20   0 2703m  55m 4616 S  3.3  0.2   0:00.62 httpd                                                                                                                            
31741 apache    20   0 2703m  55m 5312 S  3.3  0.2   0:00.62 httpd                                                                                                                            

ベンチマークの結果

# weighttp -k -c 300 -n 1000000 exmaple.jp/hello.cgi
weighttp - a lightweight and simple webserver benchmarking tool

starting benchmark...
spawning thread #1: 300 concurrent requests, 1000000 total requests
progress:  10% done
progress:  20% done
progress:  30% done
progress:  40% done
progress:  50% done
progress:  60% done
progress:  70% done
progress:  80% done
progress:  90% done
progress: 100% done

finished in 563 sec, 458 millisec and 904 microsec, 1774 req/s, 537 kbyte/s
requests: 1000000 total, 1000000 started, 1000000 done, 999845 succeeded, 155 failed, 0 errored
status codes: 999845 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 310377231 bytes total, 222409549 bytes http, 87967682 bytes data

mod_cgid専用のプロセスで待たされたせいか、リクエストは155 failedし、性能は1774 req/sという結果になりました。

prefork + mod_cgiのベンチマーク結果

CPU使用状況

mod_cgiではmod_cgidのようにオーバーヘッドとなるhttpdプロセスが存在しないため、各CPUは20%前後のidleで効率良く全てのCPUを80%近く使用できている事が分かります。上述した思惑通りの動きをしています。

top - 16:15:19 up 15 days,  4:20,  6 users,  load average: 84.23, 19.72, 6.71
Tasks: 1720 total,  30 running, 1687 sleeping,   0 stopped,   3 zombie
Cpu0  : 20.6%us, 64.5%sy,  0.0%ni, 14.6%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu1  : 19.2%us, 62.9%sy,  0.0%ni, 17.9%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 18.9%us, 60.3%sy,  0.0%ni, 20.5%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu3  : 18.9%us, 57.9%sy,  0.0%ni, 22.8%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu4  : 18.1%us, 55.9%sy,  0.0%ni, 25.3%id,  0.0%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu5  : 17.0%us, 54.3%sy,  0.0%ni, 28.3%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu6  : 20.1%us, 65.3%sy,  0.0%ni, 14.2%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu7  : 18.6%us, 63.5%sy,  0.0%ni, 17.3%id,  0.0%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu8  : 19.6%us, 61.1%sy,  0.0%ni, 18.9%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu9  : 19.0%us, 58.2%sy,  0.0%ni, 22.2%id,  0.0%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu10 : 17.9%us, 56.5%sy,  0.0%ni, 25.2%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu11 : 17.6%us, 54.8%sy,  0.0%ni, 27.2%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu12 : 18.1%us, 57.6%sy,  0.0%ni, 23.4%id,  0.3%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu13 : 17.4%us, 59.7%sy,  0.0%ni, 22.0%id,  0.3%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu14 : 18.2%us, 58.1%sy,  0.0%ni, 23.4%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu15 : 19.7%us, 56.6%sy,  0.0%ni, 23.0%id,  0.0%wa,  0.0%hi,  0.7%si,  0.0%st
Cpu16 : 18.7%us, 56.1%sy,  0.0%ni, 24.9%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu17 : 19.0%us, 54.1%sy,  0.0%ni, 26.6%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu18 : 18.2%us, 59.6%sy,  0.0%ni, 21.9%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu19 : 19.3%us, 58.7%sy,  0.0%ni, 21.6%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu20 : 18.2%us, 60.1%sy,  0.0%ni, 21.1%id,  0.3%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu21 : 19.1%us, 57.8%sy,  0.0%ni, 22.8%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu22 : 19.7%us, 56.7%sy,  0.0%ni, 23.3%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu23 : 16.8%us, 57.2%sy,  0.0%ni, 25.7%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Mem:  32809072k total,  6643652k used, 26165420k free,   243056k buffers
Swap: 16777208k total,        0k used, 16777208k free,  4627828k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                         
 2746 nscd      20   0 1256m 5844 1796 R 27.0  0.0  15:54.52 nscd                                                                                                                             
16886 root      20   0 96492 9320  716 S 14.6  0.0   0:02.68 weighttp                                                                                                                         
15873 apache    20   0  246m  47m 1680 S  2.6  0.1   0:00.39 httpd                                                                                                                            
15912 apache    20   0  246m  47m 1680 S  2.6  0.1   0:00.40 httpd                                                                                                                            
15974 apache    20   0  246m  47m 1684 S  2.6  0.1   0:00.39 httpd                                                                                                                            
15977 apache    20   0  246m  47m 1684 S  2.6  0.1   0:00.39 httpd                                                                                                                            
16015 apache    20   0  246m  47m 1680 S  2.6  0.1   0:00.39 httpd                                                                                                                            
16051 apache    20   0  246m  47m 1680 S  2.6  0.1   0:00.39 httpd                                                                                                                            
16128 apache    20   0  246m  47m 1680 S  2.6  0.1   0:00.38 httpd                                                                                                                            

ベンチマーク結果

# weighttp -k -c 300 -n 1000000 example.jp/hello.cgi
weighttp - a lightweight and simple webserver benchmarking tool

starting benchmark...
spawning thread #1: 300 concurrent requests, 1000000 total requests
progress:  10% done
progress:  20% done
progress:  30% done
progress:  40% done
progress:  50% done
progress:  60% done
progress:  70% done
progress:  80% done
progress:  90% done
progress: 100% done

finished in 303 sec, 679 millisec and 13 microsec, 3292 req/s, 985 kbyte/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 306598163 bytes total, 220827929 bytes http, 85770234 bytes data

全てのCPUをを満遍なく使う事により、worker + mod_cgidの1774req/sの約2倍程の性能である3292req/sという結果が得られました。

MPMとCGIモジュール 1秒間のリクエスト処理数(req/s)
worker + mod_cgid 1774
prefork + mod_cgi 3292

また、メモリの使われ方も、このベンチマークの範囲内ではworkerが5593952k usedであったのに対しpreforkでは6643652k usedとやはりpreforkの方が多くなっています。ですが、CoW等の影響によりプロセスの数の差ほどには差がないように思います。この辺りは、CGIの実装やアクセスパターンによって変わると思うので、ここで深くは言及しないことにします。

prefork+mod_cgiにもう少し負荷をかけてみる

さらに、prefork型にもう少し高い負荷をかけてみて、その性能を評価してみます。 同時接続数1000で総接続数1000万ぐらいでいってみましょう。すると以下のような結果が得られました。

# weighttp -k -c 1000 -n 10000000 example.jp/hello.cgi

CPU使用状況

もはやロードアベレージが1000近くなっていますが、コアが24個あることを考えるとシングルコアではロードアベレージ40程度とも考えられるのであまり気にしないでおきます。このような負荷をかけて、大体それぞれのCPUが80%前後使っている事がわかります。

top - 01:11:25 up 15 days, 13:16,  4 users,  load average: 991.46, 989.26, 889.42
Tasks: 3483 total,  30 running, 3450 sleeping,   0 stopped,   3 zombie
Cpu0  : 21.3%us, 63.2%sy,  0.0%ni, 15.2%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu1  : 19.4%us, 62.9%sy,  0.0%ni, 17.4%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu2  : 19.3%us, 60.5%sy,  0.0%ni, 19.9%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu3  : 18.1%us, 58.4%sy,  0.0%ni, 22.9%id,  0.3%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu4  : 17.8%us, 56.1%sy,  0.0%ni, 25.5%id,  0.3%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu5  : 16.8%us, 55.0%sy,  0.0%ni, 28.2%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu6  : 21.5%us, 63.8%sy,  0.0%ni, 14.4%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu7  : 19.9%us, 63.5%sy,  0.0%ni, 16.3%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu8  : 19.7%us, 60.3%sy,  0.0%ni, 19.7%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu9  : 18.6%us, 58.0%sy,  0.0%ni, 22.8%id,  0.0%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu10 : 17.5%us, 56.8%sy,  0.0%ni, 25.3%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu11 : 16.8%us, 55.9%sy,  0.0%ni, 27.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu12 : 19.3%us, 57.0%sy,  0.0%ni, 23.4%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu13 : 19.4%us, 57.3%sy,  0.0%ni, 23.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu14 : 19.5%us, 57.2%sy,  0.0%ni, 23.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu15 : 20.1%us, 56.2%sy,  0.0%ni, 23.3%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu16 : 17.2%us, 57.0%sy,  0.0%ni, 25.2%id,  0.0%wa,  0.0%hi,  0.6%si,  0.0%st
Cpu17 : 17.8%us, 55.9%sy,  0.0%ni, 26.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu18 : 19.3%us, 57.9%sy,  0.0%ni, 22.5%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu19 : 19.6%us, 58.0%sy,  0.0%ni, 22.1%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu20 : 19.1%us, 58.6%sy,  0.0%ni, 22.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu21 : 19.0%us, 57.9%sy,  0.0%ni, 22.8%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu22 : 18.4%us, 57.3%sy,  0.0%ni, 24.1%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Cpu23 : 18.5%us, 57.6%sy,  0.0%ni, 23.6%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Mem:  32809072k total, 13668324k used, 19140748k free,   244612k buffers
Swap: 16777208k total,        0k used, 16777208k free, 10148824k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                           
 2746 nscd      20   0 1784m 6204 1796 R 28.9  0.0  27:05.90 nscd                                                                                                                               
 4369 root      20   0  116m  28m  716 S 16.8  0.1   5:46.81 weighttp                                                                                                                           
32273 root      20   0 16020 4184  964 R  4.8  0.0   1:57.66 top                                                                                                                                
 1165 apache    20   0  246m  47m 1752 S  1.0  0.1   0:06.56 httpd                                                                                                                              
 1177 apache    20   0  246m  47m 1748 S  1.0  0.1   0:06.45 httpd                                                                                                                              
 1180 apache    20   0  246m  47m 1952 S  1.0  0.1   0:06.51 httpd                                                                                                                              
 1186 apache    20   0  246m  47m 1744 S  1.0  0.1   0:06.35 httpd                                                                                                                              
 1189 apache    20   0  246m  47m 1744 S  1.0  0.1   0:06.55 httpd                                                                                                                              
 1191 apache    20   0  246m  47m 1752 S  1.0  0.1   0:06.34 httpd                                                                                                                              
 1192 apache    20   0  246m  47m 1748 S  1.0  0.1   0:06.52 httpd                                                                                                                              
 1201 apache    20   0  246m  48m 2312 S  1.0  0.2   0:06.50 httpd                                                                                                                              
 1203 apache    20   0  246m  48m 2316 S  1.0  0.2   0:06.54 httpd                                                                                                                              

ベンチマーク結果

# weighttp -k -c 1000 -n 10000000 example/hello.cgi
weighttp - a lightweight and simple webserver benchmarking tool

starting benchmark...
spawning thread #1: 1000 concurrent requests, 10000000 total requests
progress:  10% done
progress:  20% done
progress:  30% done
progress:  40% done
progress:  50% done
progress:  60% done
progress:  70% done
progress:  80% done
progress:  90% done
progress: 100% done

finished in 3069 sec, 170 millisec and 722 microsec, 3258 req/s, 974 kbyte/s
requests: 10000000 total, 10000000 started, 10000000 done, 10000000 succeeded, 0 failed, 0 errored
status codes: 10000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 3063400835 bytes total, 2207144720 bytes http, 856256115 bytes data

かなりのCGI負荷にも関わらず、取りこぼす事なく3258req/sでているのは頼もしいですね。一つ上のベンチマークとほぼ同等の性能が出ています。

おそらくworkerだとこうはいかず、mod_cgidで待たされてタイムアウトとなる状況が多発し取りこぼす事が増えると思われます。また、メモリ面もこれだけ負荷をかけて4GB増加ぐらいなので、32GBマシンだとメモリ面では十分余裕があると思われます。

まとめ

以上のように、ApacheでCGIを使用する場合には、マシンの性能向上やCGIに対する大量のアクセスを考慮した場合に、workerやeventを使わずに従来型のpreforkとmod_cgiを使った方がスケールする状況があるということが分かりました。

余談ですが、性能の低いサーバ、例えばシングルコアのメモリ2GBといった環境では、mod_cgidがボトルネックにならずほとんど性能差が得られませんでした。ですので、このエントリでかいたようなbusyワーカーのピーク時の数やサーバマシンの性能が高い状況においては、preforkを採用するという選択肢も生じてくるという話でした。

特にCGIの実行可能な高集積Webバーチャルホスティング環境ではprefork+mod_cgiを使った方が良いでしょう。