Rubyのすゝめ #01 – ベンチマークテストとプロセス管理編

いつもエンジニアブログをチェックしていただいてありがとうございます。グリー株式会社でエンジニアをしている takano32 です。

読者の方々にはプログラマやエンジニアをしている方々も多いと思いますが、みなさんは何か手に馴染んだ道具を持っていますか?

グリーについて言えば「グリーといえば、PHPとMySQL」というイメージをお持ちの方も多いと思いますし、実際にシステムの核心部はPHPとMySQLによって機能しています。PHPとMySQLはグリーに欠かせない道具となっています。

ただ、グリーで働くエンジニアひとりひとりにはやはり個性があり、手に馴染んでいる道具も人それぞれです。
Pythonで社内システム向けのクローラを作成して情報を収集している方、C言語でグリーのシステムに適合するDNSサーバやMySQLプロキシ(g2proxy)を作成している方々などさまざまなエンジニアがグリーでは働いています。

私もいつもPHPのプログラムを書いているわけではなく、手に馴染んだ道具としてはRubyを用いる頻度が高いです。今回はデータベースシステムのベンチマークに用いたRubyスクリプトについて紹介してみたいと思います。

今回のベンチマークの対象となるシステムや前提は以下の通りです。

  • コアのシステムはフレームワークで記述されている
  • 負荷を与えるベンチマークのPHPスクリプト(フレームワークを用いるため、PHPで記述)がある
  • ベンチマーク方法は負荷を与えるPHPスクリプトが実行を開始し、終了するまでの実行時間をスコアとする
  • 負荷を与えるスクリプトを実行する計算機はマルチコアである
  • 負荷を計測するには大量のリクエストが必要である

PHPスクリプトをマルチスレッドにすることやプロセスをフォークすることも考えられましたが、PHPがWebフロントエンド向けの用途を得意としていることや、わたしがまだPHPに詳しくないことを考えると躊躇いを感じました。
そこで、今回は以下のようなRubyスクリプトを記述し、いくつかのベンチマークを同時実行し、すべてのプロセスが実行を終了するまでの時刻を計測することにしました。

Rubyスクリプトを記述するにあたって注意した部分は以下のような箇所です。

  • Rubyのスレッドは処理系のバージョンによってはユーザレベルスレッドであるため、バージョンに依存しないように複数のコアを使い切るためにはプロセスを複数起動したほうがよい
  • プロセスのフォークは単一の子プロセスを実行し、待ち合わせをするのには便利だが、複数の子プロセスを制御するには少し複雑なスクリプトとなってしまう
  • exec は処理のあとに exec 元の行には戻らない

書きあがったRubyスクリプトはThread, fork, exec の各特徴を組み合わせ、Threadで実行の管理を実現、forkで複数のプロセスでの実行を実現、execでRubyスクリプトから他のスクリプトへプロセスの処理を変更するというものになりました。

#!/usr/bin/env ruby
# process_group.rb

class ProcessGroup
        def initialize
                @threads = ThreadGroup.new
        end

        def exec(*args)
                th = Thread.start do
                        fork_exec(*args)
                end
                @threads.add(th)
        end

        def fork_exec(*args)
                pid = fork
                unless pid then
                        Kernel.exec(*args)
                else
                        Process.waitpid(pid)
                end
        end

        def wait
                @threads.list.each do |th|
                        th.join
                end
        end
end


if $0 == __FILE__ then
        pg = ProcessGroup.new
        pg.exec("/usr/bin/php5", "bench.php", "input01.txt")
        pg.exec("/usr/bin/php5", "bench.php", "input02.txt")
        pg.exec("/usr/bin/php5", "bench.php", "input03.txt")
        pg.exec("/usr/bin/php5", "bench.php", "input04.txt")
        pg.wait
        puts "end"
end

名前はProcessをGroupで管理するクラスということでProcessGroupクラスとしました。Rubyには標準でThreadGroupクラスというものがあるのですが、ProcessGroup is-a ThreadGroup という関係性はないので、特に継承などはしていません。
ベンチマークではこのスクリプトの実行時間をtimeコマンドで計測することでベンチマークのスコアとしました。

今回はRubyスクリプトで複数のプロセスを並行に実行してベンチマークを行う例を紹介してみました。他にもグリーでは大規模なサーバ群にちょっとした一斉処理を行うときや、情報の収集などにRubyスクリプトが使われている箇所などがあります。

みなさんも自分の手に馴染んだ道具を大切にし、良いエンジニアライフをお送りください。また、ご意見ご感想などありましたら、ぜひ @takano32 までご連絡くだされば幸いです。

では、また。

Author: takano32