Friday, October 23, 2009

Queuing ruby background processes with Ernie and BERT-RPC

僕のようにインフラ系が弱い Rails プログラマにとっては、 GitHub のインフラ担当? Tom Preston-Werner (aka mojombo) の GitHub ブログポスト How We Made GitHub Fast - GitHub は非常に参考になります。なかでも興味を引いたのは続くポスト Introducing BERT and BERT-RPC で紹介されている BERT-RPC とその実装の Ernie ですね。

過去には Rails アプリケーションのバックグラウンド処理で苦労した思い出があるので今度ツールを選ぶときは以下の課題を解決してくれるやつがほしいなと思ってました。
1. 重たい処理が連続してリクエストされたときに並列で実行せずに1タスクごとキューイングして実行してほしい
2. worker が死んでも自動で代わりを起動してほしい
3. 非同期も同期(終了するまでブロック)も両方OK

1 は Ernie の -n オプションで同時実行される handler の数は制限できるので -n 1 にすれば queue になるはず。

2 は BERT-RPC は call (同期), cast (非同期), info (コールバック情報などの送付) 3つの request が定義されていて、 ernie (v0.1) では call と cast の両方をサポートしているので同期/非同期両方OK。

3 はうろ覚えだけど Erlang でふつうにサーバーを実装すれば wroker (handler) の再起動とかはかってにやってくれるはず。実際に handler の ruby プロセスを kill してもすぐ代わりが起動するので大丈夫そうだ。

実際に queue になるか試してみた。 erlang ernie をインストール、簡単な handler を書いて、サーバーを handler 1つで起動。

$ sudo port install erlang +ssl # 時間かかります
...
$ sudo gem install ernie -s http://gemcutter.org

$ cat > sleep.rb
require 'rubygems'
require 'ernie'
mod(:sleep) do
fun(:wait) do |idx, sec|
sleep sec
print "#{idx}: awake from #{sec} sec.\r\n"
end
end
^d

$ ernie -p 9999 -n 1 -h sleep.rb
erl -boot start_sasl -detached +Bc +K true -smp enable -pz /Library/Ruby/Gems/1.8/gems/ernie-1.0.0/bin/../ebin \
-ernie_server_app port 9999 -ernie_server_app handler '"sleep.rb"' -ernie_server_app number 10 -ernie_server_app log_level 2 -run ernie_server_app boot
...

別 shell の irb で 2秒スリープ指定 で 10回 cast。

$ irb -r bertrpc
>> svc = BERTRPC::Service.new('localhost', 9999)
=> #
>> 10.times{|i| svc.cast.sleep.wait(i, 2) }
=> 10

$ ernie を実行した erlang shell では

0: awake from 2 sec.
1: awake from 2 sec.
2: awake from 2 sec.
3: awake from 2 sec.
4: awake from 2 sec.
5: awake from 2 sec.
6: awake from 2 sec.
7: awake from 2 sec.
8: awake from 2 sec.
9: awake from 2 sec.

実際に見てると2秒ごとに出力されていたので、ちゃんと queuing されてますね。

No comments: