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

(2009年版)backgrounDRbについて調査してみた

ruby

ruby on railsでバックグラウンド処理を行うためのプラグイン「backgrounDRb」が使いたくて調べてみました。
このプラグインは結構前から存在しているみたいでWeb上でも情報はたくさん存在しているんですが「backgrounDRb」自体のバージョンアップに伴う仕様変更が大きいみたいで昔の情報だとうまく行かないパターンが多く苦労しました。
今回はハマったポイントをふまえつつ、「backgrounDRb」についてまとめてみたいと思います。

backgrounDRbとは

ruby on rails上でバックグラウンド処理を行うためのプラグインです。例えば、web上から何かの操作をした際にメールを送りたいことがありますよね?このときに添付ファイル付きの大きなメールだと送信が完了するまで画面遷移は発生せず「待ち」の状態となってしまいます。backgrounDRbを使用すると「メールを出してね」という指示さえ発行すればweb上からはすぐに処理が完了したように見えます。backgrounDRbは指示された処理をバックグラウンドで淡々と順番にこなしていくだけです。
また、backgrounDRbは定期的に実行するプログラムもスケジューリングさせることができます。

使用できるOS

LinuxMac OS XなどUnix系のOSでは使用可能ですがWindows上では使用できません。
仮想環境上にLinuxをインストールするなどして試してください。

インストール

使用するためにはchronicとpacketをgemでインストールしてください。

gem install chronic packet

プロジェクトをまだ作成していないようであれば先に作成しておきましょう。
ここではtestプロジェクトとします。

rails test

プロジェクトを作成したらpluginとしてbackgrounDRbをインストールしましょう。

ruby script/plugin install http://svn.devjavu.com/backgroundrb/trunk

インストールが完了したらセットアップを行います。

rake backgroundrb:setup

これで準備完了です!

動作確認

backgrounDRbを起動します。

ruby script/backgroundrb start

railsのコンソールを起動します。

ruby script/console

起動しているworkerを確認します。

MiddleMan.all_worker_info

MiddleMan.query_all_workersではありません。(その情報は古いです)
うまく起動してたら

>> MiddleMan.all_worker_info
=> {"0.0.0.0:11006"=>[{:status=>:running, :worker_key=>"", :worker=>:log_worker}]}

という感じに表示されます。
ちなみにbackgrounDRbが起動していなければ

>> MiddleMan.all_worker_info
=> {"0.0.0.0:11006"=>nil}

という感じになります。インストール方法や起動コマンドをもう一度確認してみてください。

テストプログラム

ここでは画面をクリックするたびにログに出力するプログラムを作成してみます。
まずはbackgrounDRbのworkerというものを作成します。

ruby script/generate worker printlog

実際にlib/workers以下にprintlog_worker.rbが作成されているのが確認できると思います。
printlog_workerの中身を作成しましょう。

class PrintlogWorker < BackgrounDRb::MetaWorker
  set_worker_name :printlog_worker
  def create(args = nil)
    # this method is called, when worker is loaded for the first time
  end
  
  #ここに重い処理を記述する。
  #引数は1つしか設定できないので複数の引数を渡したいときはhash等に詰めて渡すこと
  def writer(arg)
    sleep 20
    msg = arg[:msg]
    name = arg[:name]
    logger.debug(msg+':'+name+':'+Date.today.to_s('yyyy-mm-dd')+Time.now.to_s)
  end
end

続いてPrintLogWorkerを呼び出すためのcontrollerを作成しましょう。

ruby script/generate controller logwrite

中身はこんな感じです。

class LogwriteController < ApplicationController
  def write
    MiddleMan.worker(:printlog_worker).async_writer(:arg => {:msg => 'log',:name => 'Kazuhisa'})
    redirect_to(:action => 'index')
  end
end

async_writerという感じにworkerで指定したアクション名の頭にasyncをつけると非同期で実行されます。asyncなしでアクション名を呼び出すと、backgrounDBr経由で実行しない場合と同じくフツーに待たされます。

さてwriteアクションを呼び出すためのviewを作成しましょう。
・app/views/logwrite/index.html.erb

<%= link_to('WriteLog', :controller => 'Logwrite', :action => 'write') %>

実行結果

ruby script/server

で実行して、画面に表示されている「WriteLog」をクリックしまくってみましょう。
backgrounDRbで出力したログはlog/backgroundrb_XXXXX.logに出力されます。

kazuhisa-mac:log kazuhisa$ tail -f backgroundrb_11006.log 
# Logfile created on Sun Aug 16 22:28:22 +0900 2009 by /
log:Kazuhisa:2009-08-16Sun Aug 16 22:29:21 +0900 2009
log:Kazuhisa:2009-08-16Sun Aug 16 22:29:41 +0900 2009

20秒ごとに出力されているのが分かりますね。

うまく実行されていない場合の注意点

ログ(log/backgroundrb_XXXXX.log)を見てみましょう。worker内のエラーはここに出力されているはずです。railsのconsoleで動作を確認してみるのもいいかもしれません。
あと、log用のworkerは元々起動しているので

ruby script/generate worker log

とかはやめましょう。私はこれでかなり悩みました。
それからworkerを修正した場合はbackgrounDRbを必ず再起動しましょう。restartオプションは無いのでstopしてstartです(笑)。
困ったら公式マニュアルも役に立ちます。
http://backgroundrb.rubyforge.org/manual/index.html

まとめ

backgrounDRbはrailsのmodelを使いながらバックグラウンド処理ができる便利なpluginです。Unix系環境でしか使用できないのが玉にキズですがアイデア次第で便利な使い方ができそうです。