C言語で書かれた任意のホストプログラムにmrubyを組み込み、そのプロセスリソースを簡単かつ強力に制御するために、mruby-cgroupというmrbgemを作り、以前の記事で紹介しました。
もちろん、mruby-cgroupをmod_mrubyに組み込むことで、ホストプログラムであるmod_mruby、さらにはApacheそのもののリソース制御が可能になり、Webサーバへのリクエスト単位でもリソース制御が可能になります。
しかし、軽量なリクエストに対して、無条件にリソース制御の機能を適応してしまうと、リソース制御を適応すること自体がボトルネックになる可能性が十分あります。また、それとは別に、cgroupの想定していない挙動が得られるかもしれません。それがどういうものなのかを評価してみました。
Webサーバのリクエストをリソース制御
まずは簡単にリソース制御を可能にする設定方法を説明します。mrubyにmrbgemであるmruby-cgroupを組み込んでビルドします。
mod_mrubyをcloneしてsubmoduleであるmrubyもcloneします。
[program lang=’bash’ escaped=’true’]
git://github.com/matsumoto-r/mod_mruby.git cd mod_mruby git submodule init git submodule update cd mruby
[/program]
build_config.rbに以下の設定を追記し、
[program lang=’ruby’ escaped=’true’]
conf.gem :git => 'git://github.com/matsumoto-r/mruby-cgroup.git'
[/program]
mrubyとmod_mrubyをビルドします。Fedora64bit環境などでは-fPICが必要です。
[program lang=’bash’ escaped=’true’]
cd mod_mruby cd mruby rake CFLAGS="-fPIC" cd .. ./configure make make install
[/program]
そして、httpd.confに以下のように設定します。
[program lang=’apache’ escaped=’true’]
mrubyAccessCheckerMiddle /etc/httpd/hooks/cgroup_attach.rb mrubyLogTransactionMiddle /etc/httpd/hooks/cgroup_detach.rb
[/program]
それぞれ、cgroup_attach.rbとcgroup_detach.rbは以下のように処理を記述します。
cgroup_attach.rb
[program lang=’ruby’ escaped=’true’]
r = Apache::Request.new if r.filename == "/var/www/html/while.cgi" c = Cgroup::CPU.new("apache/mod_mruby_group") c.cfs_quota_us = 10000 if c.exist? c.modify else c.create end c.attach end
[/program]cgroup_detach.rb
[program lang=’ruby’ escaped=’true’]
r = Apache::Request.new if r.filename == "/var/www/html/while.cgi" c = Cgroup::CPU.new("apache/mod_mruby_group") if c.exist? c.delete end end
[/program]
このように書くことで、while.cgiにアクセスがあった場合は、そのプロセスをCPU10%に制限することができます。また、Apacheのプロセスはapache権限で動作しているので、事前にcgroup以下にapache権限でアクセスできるcgroupを作っておく必要があります。例えば/sys/fs/cgroup/cpu/apacheを作り、chmod -R apache.aparchで権限を変更しておきましょう。
実際にリソース制限できているか確認
では実際にCPUを100%食うようなwhile.cgiを適当に作って、適切にCPU制御できているかを確認しました。
while.cgi
[program lang=’bash’ escaped=’true’]
#!/bin/sh while :; do true ; done
[/program]
このスクリプトにcurl等でアクセスします。そして、topコマンドにてCPU使用率を確認すると、
[program lang=’bash’ escaped=’true’]
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 18504 apache 20 0 11548 1120 936 R 9.9 0.0 0:23.07 while.cgi
[/program]
きちんとCPU10%で制御できていますね。
cgroupへのattachによるパフォーマンスの影響について
では、it works!!を出力するような軽量な静的コンテンツindex.htmlに対して、cgroupに都度attachした場合、どのようにパフォーマンスに影響するかを評価しました。これによって、ある程度外から見たcgroupの挙動が理解できそうです。
cgroup_detach.rbのフックは除外し、cgroup_attach.rbのwhile.cgiのマッチ条件を無しにしました。これで、すべてのアクセスに対してCPU10%のcgroupにプロセスがattachされるはずです。しかし、index.htmlはCPUをそこまで使う処理ではないので、本来はリソースを制限することなくそのまま処理することが期待されます。
では、実際にindex.htmlに対して同時接続数100、総接続数10万でabで負荷をかけ、1秒間に処理できたリクエスト数を計測しました。以下がその結果になります。
cgroup_attach.rbをフックした場合(すべてのリクエストをCPU10%に制限)
- 230req/sec
cgroup_attach.rbをフックした場合(while.cgiのみをCPU10%に制限、実質cgroupのattachは生じない)
- 5000req/sec
このように、大きく性能に差がでました。cgroupにattachしない場合と比べて、cgroupにattachした場合は5%程度の性能に落ち込みました。
むむむ…?
5%…少し気になる数値ですね。
考察
ここからは、推測を前提に考察してみます。
index.htmlに対して5%程度しか性能がでなかった理由は、cgroupにattachするコストによる性能劣化なのでしょうか。CPUを10%も使わないような軽量な処理は、cgroupにattachする事自体がボトルネックとなり、そこで性能が劣化してしまうのでしょうか?
しかし、一方で次のように理解することもできます。このあたりはcgroupの実装をきちんと読めていないので、あくまで推測になります。
index.htmlはCPUを10%も使いませんが、abコマンドによる同時多発的に連続したリクエストを処理する際にも、cgroupはトータルでCPUコアの10%となるように処理をしているのでは?、ということです。
今回の検証環境は2コアですので、制限しない場合は単純に計算すると100% * 2コア分のCPU200%が使用できるとします。それが今回、「cgroup_attach.rbをフックした場合(while.cgiのみをCPU10%に制限、実質cgroupのattachは生じない)」に該当し、これが5000req/secです。
一方、「cgroup_attach.rbをフックした場合(すべてのリクエストをCPU10%に制限)」では、cpu.cfs_quota_usの設定値である10%が全体の10%を示していたとすると、5000req/secの200分の10で、250req/secになります。むむむ、上記の実験結果の値である230req/secにかなり近い値ですね。
このように、cgroupにattachしたプロセスの処理は、最終的にトータルでそのcgroupの設定値となるように実装されているのであれば、上記の値は正常であるといえますし、むしろcgroupは緻密でとても賢いことになります。
そうでなければ、単なるボトルネックとして、あまりうれしい結果ではありません。
最後に
もうちょっと他の比較実験を追加すれば明らかになるのでは?という突っ込みもありそうですが、今回はここまでにとどめておきます。今後は、その他の比較実験をしてみたり、予測の元cgroupのコードを読んでいくことになりそうです。もし、この記事を読んだだけで、理由を説明できそうな方がいればぜひとも記事にして頂けたらうれしいです。必ずはてブします!