BackgrounDRb 0.2.1 README

とりあえず、興味ないところは飛ばしています。
ちなみに gem install backgroundrb だと古いのが入るので注意。(2007/8/7現在)

http://backgroundrb.rubyforge.org/files/README.html

BackgrounDRb

BackgrounDRbはrubyのジョブサーバー兼スケジューラーです。主な目的としてはRuby on Railsアプリケーションと組み合わせて使用し、実行時間の長いタスクを受け持つことを考えています。Railsアプリケーションはリクエストを処理している間その他の処理をブロックしてしまうので、長時間掛かるタスクはHTTPのリクエスト/レスポンスサイクルと切り離してバックグランドプロセスにするのがベストです。


新しくリリースされたBackgrounDRbもモジュール化されていて、Railsがなくても利用できます。そのためどのようなRubyプログラムまたはフレームワークであっても利用することが可能です。

免責条項

テクノロジ概観

BackgrounDRbの0.2.xブランチでは以前のバージョンとは全く異なる新規のアーキテクチャが導入されました。新しいシステムでは、シングルプロセス・マルチスレッド環境ではなく、ワーカーを管理するためにIPCを使用してマルチプロセスで動作します。そのためそれぞれのワーカーはそれら自身のRubyインタプリタ上で実行されることになります。Railsから使用する場合のインターフェースはほとんど変更ありません。これまで同様にサーバーとのやり取りにはMiddleManオブジェクトを使用できます。ただし、処理結果を扱うには新しいやり方が必要であることに注意してください。


新方式では永続的なResultワーカーが存在し、そこに結果を保存したり、そこから結果を取得したりします。これは、今ではワーカーがそれぞれのプロセス上で動作するため、処理が終了するとすぐにプロセスも終了してあまりに多くのプロセスが立ち続けないようにしたいという要求に答えるための処置です。したがって、共有したいデータは全てワーカークラスの中でresultsハッシュに保存するだけで構いません。つまり、次のようになります:

def do_work(args)
  logger.info('ExampleWorker do work')
  results[:do_work_time] = Time.now.to_s
  results[:progress] = 0
  # more code here..
end

これでこれらの結果はresultsハッシュを通じてRails内で利用可能になります。

do_work_time = MiddleMan.worker(session[:job_key]).results[:do_work_time]
progress = MiddleMan.worker(session[:job_key]).results[:progress]

ここで、サーバー起動時にはプロセスが3つ実行中であることに注意してください。一つはMiddleManサーバー、もう一つはresultsワーカー、そしてloggerワーカーです。ワーカー内でlogger.info("foo log!")のようにすると、Loggerワーカーにログを出力できます。


おそらく想像通り、マルチプロセスを使った新方式はマルチスレッドシングルプロセスを使用するバージョンよりもスケールします v(^o^) ただし、middleman内にはまだサイズをコントロールできるスレッドプールが存在していることに注意してください。それによってプラグインが大量のプロセスを生成しすぎなようにできます。It will allow however many workers you specify to run at once and any more then that will just queue up and wit for their turn to spawn.

依存ライブラリ

BackgrounDRbを使用するには次のパッケージがインストールされている必要があります:

  • Slave 1.1.0 (or higher)
  • Daemons 1.0.2 (or higher)

RailsBaseワーカーを使用するのであれば、BackgrounDRbはvendors/plugins/backgroundrbにインストールしなければいけません。

0.2.x以前のバージョンからのアップグレード

古いBackgrounDRbがRailsプラグインとしてインストールされている場合はscript/backgroundrbディレクトリを削除する必要があります。新しいバージョンでは、script/backgroundrbはサーバープロセスを制御するスクリプトになっています。


古い設定ファイルに関してはおおむねそのまま使用できますが、いくつかのオプションはobsoleteになっていたり、単純に無視されたりします。


今回のワーカーはMiddleManに登録して受理される必要があります。ワーカークラス定義の直後に処理を記述するとよいでしょう。このドキュメントのワーカーセクションを参照してください。


旧BackgroundDRb::Railsは新しくBackgrounDRb::Worker::RailsBaseとなりましたが、とりあえず互換性のためのラッパークラスが用意されています。ただし将来破棄される予定なのでワーカーを更新しておいたほうがいいでしょう。


周期的な実行はワーカークラスの外部に出されました。'repeat_every'と'first_run'は削除してください。ワーカー外部でどのように定義するかについてはスケジューラーセクションを参照してください。

インストール

BackgrounDRbはスタンドアロンまたはRailsプラグインのどちらででも実行できます。

BackgrounDRb Railsプラグイン

通常のインストール:

cd RAILS_ROOT/vendor/plugins
svn co http://svn.devjavu.com/backgroundrb/tags/release-0.2.1 backgroundrb
cd RAILS_ROOT
rake backgroundrb:setup

svn externalにする場合:

svn propedit svn:externals vendor/plugins
[add the following line:]
backgroundrb http://svn.devjavu.com/backgroundrb/tags/release-0.2.1
[exit editor]

svn ci -m 'updating svn:external svn property for backgroundrb' vendor/plugins
svn up vendor/plugins
rake backgroundrb:setup

ワーカージェネレータの使い方はRailsセクションを参照してください。デフォルトのファイル配置位置は次のようになります:

RAILS_ROOT/
  config/backgroundrb.yml.
  config/backgroundrb_schedules.yml.
  lib/workers
  log/backgroundrb.log
  log/backgroundrb_server.log
  log/backgroundrb.pid
  log/backgroundrb.ppid
BackgrounDRbスタンドアロン

スタンドアロンモードではconfとworkersディレクトリはデフォルトでbackgroundrbディレクトリからの相対パスになっています。このモードではRails workersは使用できません。

設定

設定例:

host: localhost
port: 2999
worker_dir: lib/my_workers
rails_env: development
pool_size: 15
acl:
  deny: all
  allow: localhost 127.0.0.1
  order: deny allow
デフォルト設定

デフォルトではサーバーは'drbuniz'プロトコル(ドメインソケット)を使用して動作するように設定されていて、ソケットファイル名は設定されたホスト名とポート番号に基づいて生成されます。もし指定されていない場合は、'localhost'と'2000'が使用され、その場合名前付きパイプは/tmp/backgroundrbunix_localhost_2000となります。


'druby'(DRb over TCP/IP)を指定した場合は、サーバーはデフォルトのDRb ACLもインストールし、ローカルホストから到着する接続を制限します。設定ファイルでどのようにACLを指定するかについては上記を参照してください。

サーバー

BackgroounDRbサーバーはbackgroundrbディレクトリ(または'rake backgroundrb:setup'を実行していればRAILS_ROOT)の下にある./script/backagroundrbスクリプトを通じて制御されます。


サーバーをデフォルトの設定ファイルまたは組み込みのデフォルト値でデーモンとして起動する場合:

./script/backgroundrb start

または、RAILS_ROOTなら(WARNING: at least stop and restart is broken in 0.2.1 - for now please call the server script directly)

rake backgroundrb:start

フォアグランドでサーバーを実行するには(ワーカーが作成されたときに詳細が確認できます)

./script/backgroundrb start -- -p 7777

デフォルト、設定ファイル、コマンドラインオプションで設定された有効な値はサーバーログに書き込ますが、起動時に表示することも可能です。

./script/backgroundrb run -- -l

backgroundrbサーバーオプションの完全な一覧は以下のようになります:

 -c, --config file_path           BackgrounDRb config file (path)
 -h, --host name                  Server host (default: localhost)
 -l, --list                       List configuration options
 -p, --port num                   Server port (default: 2000)
 -P, --protocol string            DRb protocol (default: drbunix)
 -r, --rails_env string           Rails environment (default: development)
 -s, --pool_size num              Thread pool size (default: 5)
 -t, --tmp_dir dir_path           Override default temporary directory
 -w, --worker_dir dir_path        Override default worker directory

完全なヘルプは次のようにすれば表示できます:

./script/backgroundrb --help

MiddleMan

MiddleManはBackgrounDRbサーバー内のワーカーとのインターフェースを提供します。新しく作成したMiddleManを通じて既存のワーカーにアクセスしたり、削除したり、スケジュールしたりできます。MiddleManオブジェクトはDRbサービスとして公開されます。

ワーカー

ワーカーは下記のいずれかのサブクラスである特定のワーカークラスを指定して作成します。

 - BackgrounDRb::Worker::Base: 通常のワーカー
 - BackgrounDRb::Worker::RailsBase: Rails環境をロードして、
   モデルクラスにもアクセスできるワーカー

単純な例としては次のようになるでしょう(RailsワーカーについてはこのドキュメントのRailsセクションを参照してください)

class ExampleWorker < BackgrounDRb::Worker::Base

  # do_work is called when the worker is created
  def do_work(args)
    logger.info('ExampleWorker do work')
    results[:do_work_time] = Time.now.to_s
    results[:done_with_do_work] ||= true
  end

  def other_method
    logger.info('other_method in ExampleWorker called')
    results[:extra_data] = "Just a plain old string"
  end

  def arg_method(arg)
  end

end
ExampleWorker.register

loggerとresultsメソッドについては次のセクションで説明します。これでMiddleManを通じてワーカーを作成できるようになりました。(以下の構文はRailsからMiddleManにアクセスできることを仮定しています)

key = MiddleMan.new_worker(:class => :example_worker)
worker = MiddleMan.worker(key)
worker.other_method
worker.delete

new_worker呼び出しは別プロセスとしてワーカーを作成し、do_workを実行します。その後、ワーカーオブジェクトのキーを返し、そのキーを通じてワーカーにアクセスし、また別のメソッドを呼ぶことができます。最終的に作業が終わるとワーカーをdeleteするか、自動的にdo_workが呼び出さずに再度アクセスする必要があるならそのまま放っておくこともできます。


ワーカー名は:job_keyオプションで指定できます。

MiddleMan.new_worker(:class => :example_worker, :job_key => :foo_name)
worker = MiddleMan.worker(:foo_name)
worker.other_method
worker.delete

明示的に名前が指定された場合、MiddleMan.workerはシングルトンとして振る舞い、既存のワーカーをかえすか、存在しなければ新しくワーカーを作成します。

コンソール

BackgrounDRbサーバーにはconsoleスクリプトで'console'コマンドを実行してアクセスすることもできます。こうするとMiddleMan DRbオブジェクトのコンテキストでIRBセッションを開始されます。

 ./script/backgroundrb console
 >> loaded_worker_classes
 ["ExampleWorker", "OtherWorker"]
 >> new_worker(:class => :example_worker)

サーバーを起動する際にコマンドラインオプションでデフォルト値や設定ファイルの値を上書きしていた場合、それらはconsoleコマンドでも指定する必要があることを忘れないで下さい:

 ./script/backgroundrb start -- -P drbunix -w /srv/testworkers
 ./script/backgroundrb console -- -P drbunix -w /srv/testworkers

ResultsとLogging

BackgrounDRbサーバーは二つのデフォルトワーカー、resultsとloggingを起動します。これまで見たようにresultsとloggingへのアクセスメソッドはワーカーのスーパークラスで提供されていて、何も準備しなくても利用できます。


現在のresultsの実装にはトップレベルへの直接代入だけが可能と言う制限があります:

results[:somekey] = "some value"

以下は動作しません:

results[:other_key] = []
results[:other_key] << "add to key"

代わりに一時的なデータ構造を組み立てたあと、それをresultsキーの値に設定するか:

tmpary = []
tmpary << "add to ary"
tmpary << "add more to ary"
results[:the_key] = tmpary

既存のデータがあるなら:

results[:foo] = { :bar => 'yay', :baz => 'huh' }
tmpdata = results[:foo]
tmpdata[:bob] = 'alice'
results[:foo] = tmpdata

Resultsは長寿命なワーカーなので、ワーカーが完了して削除されてもそのresultsはまだ有効です。

MiddleMan.new_worker(
  :class => :example_worker,
  :job_key => :test_result
)
worker = MiddleMan.worker(:test_result)
worker.other_method
worker.delete
p worker.results.to_hash
=> { :do_work_time => "Mon Oct 23 11:40:34 EDT 2006",
     :done_with_do_work => true,
     :extra_data => "Just a plain old string" }

resultsを明示的に変更することも可能です:

worker.results[:external] = 42

The logger method gives you access to a regular Logger object, which can be called inside as well as externally, but does not out live the worker object. See the Workers section for examples of use.

スケジュール

免責: この内容はいまだ流動的な新しいBackgrounDRbの領域に属します。スケジューラーはこの提案よりも有用で、あらゆるProcオブジェクトをいくらでも投入できます。ただしスケジュールされたあとにそれらを特定して処理するよい方法はありません。


MiddleManに直接指定されるか、または外部のスケジュール定義ファイルで指定されるschedule_workerの引数リストは以下です。

 :class                  # worker_class
 :job_key                # job key for singleton worker
 :args                   # new_worker(:args)
 :worker_method          # worker method when schedule is triggered
 :worker_method_args     # args for worker method
 :trigger_type           # type (:trigger, :cron_trigger)
 :trigger_args           # args for trigger, see below.

ワーカーは二つの組み込み'trigger'タイプでスケジュールされます。単純な'trigger'はstart, stop, intervalで指定できます。その他、UNIXスタイルのcron構文を使用できる'cron trigger'もあります。


トリガータイプは:trigger_typeを使って明示的に設定できます。MiddleManはそれがハッシュであろうと文字列であろうと識別できます。

トリガー

基本のトリガーは3つの引数を取ります:

:start => time, :end => time, :interval => seconds
Cronトリガー
特殊ケース: :do_work
config/backgroundrb_schedules.ymlを使ったスケジューリング
MiddleManを使用したスケジューリング

Rails

Rakeタスク
rake backgroundrb:setup

注意: start/stop/restartは0.2.1では動作しません。以前述べたserverスクリプトを直接使用してください。

Ticket: http://backgroundrb.devjavu.com/projects/backgroundrb/ticket/27

rake backgroundrb:start (using options from configuration file)
rake backgroundrb:stop (using options from configuration file)
rake backgroundrb:restart (using options from configuration file)
ワーカージェネレーター

RailsワーカージェネレーターはRailsBaseワーカークラスのスケルトンを生成します:

RAILS_ROOT/script/generate worker Testing

上記のコマンドは:

lib/workers/testing_worker.rb

を作成し、その内容は以下のようになります:

# Put your code that runs your task inside the do_work method it will
# be run automatically in a thread. You have access to all of your
# rails models.  You also get logger and results method inside of this
# class by default.
class TestingWorker < BackgrounDRb::Worker::RailsBase

  def do_work(args)
    # This method is called in it's own new thread when you
    # call new worker. args is set to :args
  end

end
TestingWorker.register

既知の問題