ApacheとNginxの性能比較でevent_mpmの本気を見た

Apache2.4から正式にデフォルトのMPMになったevent_mpmですが、いまいち良さを実感する事ができていませんでした。たいしてパフォーマンスも出ないし、これ本当に意味あるの?という人が多かったかもしれません。

そこで、マルチプロセッシング周りを真面目にチューニングしてみようと思い、あれこれ試してみると、ある設定値において急にevent_mpmが本気を出し始め、すごいベンチマークスコアを叩きだしたので、それらをまとめようと思います。正直びっくりしています

今回の比較ではキャッシュ等のチューニングは行わず、マルチプロセッシング周りのみのチューニングを行って、純粋なリクエスト処理のアーキテクチャの性能比較を行いました。

同時接続数固定の最高処理能力比較

これまでは、数十バイトの静的コンテンツを単純に処理する場合、同時接続数を100総接続数10万ぐらいのベンチでNginxよりApacheが性能を出した事を見たことはありませんでした。ブログ等でもあまり見かけた事はありません。それほどNginxの独壇場だったように思います。この分野はNginxの圧勝だろうと思って…いた時期が僕にもありました。

実際にevent_mpmの設定を多くのパターンを試して、以下の設定をしたときに、とんでもない性能が発揮されたのです。

まずは、event_mpmのデフォルトの設定(インストール時)は以下のようになっていますね。
[program lang=’apache’ escaped=’true’]

StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 0
[/program]
ふむふむ、なんかそれらしい値ですね。Nginxのプロセス周りのデフォルトの設定は以下のようになっています。

[program lang=’apache’ escaped=’true’]

worker_processes 1;

events {
    worker_connections 1024;
}
[/program]
この設定で、例えば以下のabコマンドで同時接続数100総接続数10万でベンチをとると以下のようになります。実験環境はいつもと同じです。
  • コマンド

[program lang=’bash’ escaped=’true’]

ab -c 100 -n 100000 http://example.com/

[/program]

  • 結果
Apache2.4.3 Nginx1.2.3
Requests/sec 10746.47 14708.75

はい、これは僕がいつも良く見るApacheとNginxの性能差に見えます。大体、ApacheはNginxの75%程度の性能に落ち着きます。数十バイトの静的コンテンツに対するリクエスト処理はNginxの得意分野だと思っていたので、大体こんなものです。 そこで、真面目にevent_mpmのチューニングを行ってみました。で、幾度となくベンチを試した結果導き出した、静的コンテンツに対する同時接続数100程度に対して最高のパフォーマンスを示すevent_mpmの設定は以下のようになりました。 [program lang=’apache’ escaped=’true’]

StartServers 4
MinSpareThreads 4
MaxSpareThreads 4
ThreadsPerChild 2
MaxRequestWorkers 2
MaxConnectionsPerChild 0
[/program]
とてもシンプルですね。デフォルトの設定の見る影もありません。

僕の実験サーバはメモリ8GのXeon X5355 2.66GHzでOS上は4コアに見えていますので、4コアを生かして並列処理時には4スレッドでevent_mpmの特徴である多重化I/Oをするようにしました。なるべくコンテキストスイッチを抑える事を意図して、されにMaxRequestWorkersとThreadsPerChildを2にしています。本来MaxRequestWorkersはMaxClientであるため、この設定だと8にする必要がありますが、なぜか、この値をそれぞれ2にするか否かで、性能が20%以上向上する事が分かっています。後述しますが、同時接続数を1000のオーダーにしようとすると、この値はもっとあげないといけませんが、100程度であれば、とにかく1スレッドで同時に処理する数が多くなるように設定した方が、パフォーマンスが大幅に改善する事がわかりました。

また、Nginxに関しては以下のように設定しました。
[program lang='apache' escaped='true']
worker_processes 4;

events {
    worker_connections 1024;
}
[/program]
これは定番の設定ですね。コアの数だけ非同期I/O処理をするプロセスを立ち上げておく事で、より効率よく処理ができます。

この設定で、再度同時接続数100総接続数10万でベンチをとりました。
  • 結果
Apache2.4.3 Nginx1.2.3
Requests/sec 18682.31 17021.03

おお、abコマンドのベンチによる静的コンテンツの処理で、ApacheがNginxを上回っているのをはじめてみた気がします。Apacheのevetn_mpmも適切なチューニングによっては、上記のような値が得られる事が分かりました。これはびっくりです。 上記で述べたMaxRequestWorkersとThreadsPerChildの値を例えば4とかに変えると、以下のような値になります。

Apache2.4.3 Nginx1.2.3
Requests/sec 13777.59 17021.03

MaxRequestWorkersとThreadsPerChildの値を1にしてみると以下のような値になります。

Apache2.4.3 Nginx1.2.3
Requests/sec 9058.07 17021.03

MaxRequestWorkersを4、ThreadsPerChildの値を1にしてみると以下のような値になります。

Apache2.4.3 Nginx1.2.3
Requests/sec 12949.39 17021.03

このように、これらの値をWebサーバへのアクセスパターンに合わせてやることで、大きく性能に変化が生じます。event_mpmにおいてはとても大事な値だとわかりました。

同時接続数の変化における性能比較

上記では、Webサーバへのアクセスパターンが例えば同時接続数100ぐらいだと仮定して、それに合わせた最適なチューニングで性能比較を行いました。次に、Nginxやevent_mpmの売りである、同時接続数が1000のオーダーになった時に耐えられるチューニングを行い、性能を評価しました。 まずは、openfile数の制限を変更します。 [program lang=’bash’ escaped=’true’]

ulimit -n 65536
[/program]
また、httpd-2.4.3/server/mpm/event/event.cのMAX_SERVER_LIMITの値を以下のように変更します。
[program lang='c' escaped='true']
118 #ifndef MAX_SERVER_LIMIT
119 #define MAX_SERVER_LIMIT 60000
120 #endif
[/program]
さらに、event_mpmの設定を以下のようにします。
[program lang='apache' escaped='true']
ServerLimit 60000
MaxRequestWorkers 60000
StartServers 4
MinSpareThreads 4
MaxSpareThreads 4
ThreadsPerChild 2
MaxConnectionsPerChild 0
[/program]
Nginxの設定は以下のようにします。
[program lang='apache' escaped='true']
worker_processes 4;

events {
    worker_connections 60000;
}
[/program]
これで、同時接続数を100から5000に変化させた時のベンチマーク結果は以下のようになります。



同時接続数においては、やっぱりNginxはすごいというか、event_mpmは現状千のオーダーでの同時接続数の処理は不十分な所があるという印象を受けました。それでも、元のpreforkやworkerからみると、同時接続数3000とか処理できているのはかなりの進歩にも思えます。

Nginxに関しては同時接続数1万どころか、2万でも処理できましたし、それ以降はむしろabの同時接続数の制限値である2万を超えてしまったため計測できませんでした。またabコマンド弄ってそれ以上のベンチをとってみたいと思います。

また、event_mpmに負荷を与えていくと、全く反応しなくなることがありました。その時のスレッドをstraceしてみると、以下のようにfutexで処理が全スレッド止まっている事がわかりました。
[program lang='c' escaped='true']
[pid 24118] read(4, <unfinished ...>
[pid 24120] poll([{fd=11, events=POLLIN}], 1, 60000 <unfinished ...>
[pid 24121] poll([{fd=7, events=POLLIN}], 1, 60000 <unfinished ...>
[pid 24122] futex(0x9550d68, FUTEX_WAIT_PRIVATE, 695987, NULL <unfinished ...>
[/program]
このロック周りで刺さってしまって、多重I/Oできなくなるあたりが、まだevent_mpmの不完全な所だと言えるでしょう。

ちなみにですが、1スレッドで同時接続処理をするという、Nginxやevent_mpmの特徴を無視して、以下のような力技設定をすれば、余裕でevent_mpmも同時接続数10000とか万のオーダーを処理できるようになります。
[program lang='apache' escaped='true']
ServerLimit 60000
MaxRequestWorkers 60000
ThreadLimit 1000
StartServers 1000
MinSpareThreads 1000
MaxSpareThreads 1000
ThreadsPerChild 100
MaxConnectionsPerChild 0
[/program]
これはあまりc10k的ではない、ということで参考情報にして下さい。2万同時接続数とかでも、4265.17request/secぐらいは僕の検証環境でも出ます。nginxだと7368.6くらい出ていました。

追記

twitter上で@kfujieda さんにアドバイスを頂きつつ、再度eventの同時接続数20000あたりをさばけるような設定を検討しました。event_mpmといえども、nginxのようにそれぞれのworkerが複数のリクエストを同時に処理できている、つまりコアの数だけスレッドを用意すれば良いわけではなく、多重スレッド、マルチスレッド、マルチプロセスを組み合わせたようなMPMのアーキテクチャであることが分かってきました。 つまり、nginxとは違って、きちんとスレッドの数とプロセスの数、及び、同時接続数の数などの組み合わせをきちんと考慮する必要があり、スレッドやプロセスに依存する面があると考えられます。詳しくはマニュアルをご覧下さい。 以上を踏まえて、@kfujiedaさんのJAISTでのworker_mpmにおける設定も考慮しつつ、コア4つのメモリ8G環境で、同時接続数を万のオーダーで処理できるような設定を検討した結果、以下のような設定になりました。 [program lang=’apache’ escaped=’true’]

AsyncRequestWorkerFactor 2
ServerLimit 32
MaxRequestWorkers 8192
ThreadLimit 256
StartServers 2048
MinSpareThreads 2048
MaxSpareThreads 2048
ThreadsPerChild 256
MaxConnectionsPerChild 0
[/program]
この設定で、再度上記で行った同時接続数の変化に対するベンチマークを行うと以下のようになりました。

nginxとApacheのeventはかなり良い勝負ができている事がわかります。Apache的にはかなり良い結果なのではないかと思われます。

最後に

今回はevent_mpmをワークロードに最適化したチューニングを行えば、ある条件下ではNginxよりも早くなる事がわかりました。これは、これまであまり見かけない情報だったので、少し未来を感じる検証結果になったように思えます。

また、追記にも書いた通り、大量の同時接続数を想定したチューニングを行っておけば、nginxと同じような性能を出せる事も分かってきました。ワークロードで細かく設定したい場合はeventを使うという選択肢もでてきたかもしれません。

しかし、多重I/Oのブロック周りで負荷をかけるとevent_mpmは問題をかかえているように思えます。ここさえクリアになれば、安心してかなり使えるマルチプロセッシングモジュールになるのではないかと思います。

いずれにせよ、MaxRequestWorkersとThreadsPerChildのチューニングがevent_mpmを使う上での肝になる事は明らかだと言えるでしょう。この設定方法が、今後のevent_mpmのチューニングの参考になれば良いなと思っています。

Nginxの設定に関して勉強したい方はまずは以下の本がよいでしょう。

Apacheの設定に関しては以下がおすすめです。