FrontPage  Index  Search  Changes  RSS  Login

[BackgroundJob][Ruby] Resque の README より

概要

Resque の README を参考にメモ。

概観

class Archive
  @queue = :file_serve

  def self.perform(repo_id, branch = 'master')
    repo = Repository.find(repo_id)
    repo.create_archive(branch)
  end
end
  • ジョブは perform に応答可能(job.respond_to?(:perform) == true)な Ruby のオブジェクト
  • インスタンス変数 @queue はジョブが置かれるキューを示す
  • キューは不定で on the fly に作成できる
class Repository
  def async_create_archive(branch)
    Resque.enqueue(Archive, self.id, branch)
  end
end
  • ジョブは Resque.enqueue でキューに置かれる
klass, args = Resque.reserve(:file_serve)
klass.perform(*args) if klass.respond_to? :perform
  • キューに置かれたジョブは Resque.reserve で取り出せ、オブジェクトとパラメータを受け取れる
$ cd app_root
$ QUEUE=file_serve rake resque:work
  • rake タスクで任意のキューのジョブを処理するワーカーを起動できる

ジョブ

GitHub では、以下の様なジョブを Resque で処理している。

  • キャッシュ作り
  • ディスク使用量の計算
  • tarball 作り
  • RubyGems 作り(やめたはず)
  • WebHooks の発火
  • Creating events in the db and pre-caching them
  • グラフ作り
  • ユーザの削除
  • 検索インデックスの更新

永続性

ジョブは JSON オブジェクトとしてキューに置かれる。

{
    'class': 'Archive',
    'args': [ 44, 'masterbrew' ]
}

パラメータは JSON エンコード可能なものに限られる。

  • パラメータにマーシャルしたオブジェクトを指定すると、オブジェクトが拡張された様な場合に古いままであるといった不都合が考えられる
    • 注:英文理解に非常に自身がない…

send_later / async

examples/ ディレクトリにあるサンプル参照との事。

失敗

  • ジョブが例外を発生させたら、ログに記録され Resque::Failure モジュールに渡される
  • 失敗はローカルの Redis か、いくつかの別のバックエンドに記録される
  • 例えば、Resque は Hoptoad をサポートしている

ワーカー

Resque のワーカーは動き続ける rake タスク。

start
loop do
  if job = reserve
    job.process
  else
    sleep 5
  end
end
shutdown

ワーカーの起動は以下のようにすれば良い。

$ QUEUE=file_serve rake resque:work

デフォルトでは Resque はアプリケーションの environment を知らない。 Resque を Rails プラグインとしてインストール済みなら以下のように environment を指定できる。

[RAILS_ROOT/] $ QUEUE=file_serve rake environment resque:work

これによって、ワーカーを起動する前に environment をロードする。 この代わりに、resque:setup タスクを定義する事で、依存タスクに environment を指定できる。

task "resque:setup" => :environment

Logging

Resque は標準では標準出力へのログ出力をサポートしている。VERBOSE 環境変数によってデバッグ情報を吐き出す事もできる。

$ VVERBOSE=1 QUEUE=file_serve rake environment resque:work

優先度とキューリスト

Resque は数値優先度をサポートしていないが、代わりに "queue list" と呼ばれる任意のキュー順序を利用する事ができる。

$ QUEUES=file_serve,warm_cache rake resque:work

ワーカーをこの様に起動した場合、ワーカーはまず file_serve キューをチェックし、キューにジョブがなくなったら warm_cache キューをチェックする様になる。

全てのキューを実行

起動時に存在しないキューも含めて、全てのキューのジョブを実行するワーカーを起動するためには、以下の様にすればよい。

$ QUEUE=* rake resque:work

この場合、キューリストの順序はアルファベット順になる。

Forking

  • 特定のプラットフォームでは、Resque のワーカーがジョブを予約する際に直ちに子プロセスを fork する
  • 子プロセスはジョブがあればジョブを処理する
  • 子プロセスが成功裏に終了すれば、ワーカーは他のジョブを予約して処理を繰り返す

Resque は無秩序を当然のものと考える。

  • Resque は、バックグラウンドのワーカーについて以下の様に考えている
    • ロックされる事があるだろう
    • 長時間実行し続ける事もあるだろう
    • 予期しないほどにメモリを消費する事もあるだろう

親プロセスと子プロセス

$ ps -e -o pid,command | grep [r]esque
92099 resque: Forked 92102 at 1253142769
92102 resque: Processing file_serve since 1253142769
$ ps -e -o pid,command | grep [r]esque
92099 resque: Waiting for file_serve,warm_cache

シグナル

Resque のワーカーはいくつかのシグナルを受け付ける。

  • QUIT
    • 子プロセスの処理を待ち、それを終え次第終了する
  • TERM, INT
    • 直ちに子プロセスを kill し終了する
  • USR1
    • 直ちに子プロセスを kill するが終了はしない

フロントエンド

Resque は、Sinatra ベースのフロントエンドを持っている。

スタンドアロン

$ resque-web

rackup ベースなので、調整可能。

$ resque-web -p 8282

設定ファイルのパスを指定する事もできる。

$ resque-web -p 8282 rails_root/config/initializers/resque.rb

Passenger

config.ru によって Passenger を利用する事ができる。

Rack::URLMap

Rack::URLMap(Rack のミドルウェア)を使えば、URL の調整も可能。

require 'resque/server'

run Rack::URLMap.new \
  "/"       => Your::App.new,
  "/resque" => Resque::Server.new
  • examples/demo/config.ru を参考にするとよい

Resque vs DelayedJob

どちらを選ぶべきかの考察。

  • Resque は複数のキューをサポートしている
  • DelayedJob は優れた優先度をサポートしている
  • Resque のワーカーはメモリリーク/ブロートを跳ね返す
  • DelayedJob のワーカーは非常にシンプルで簡単に変更できる
  • Resque には Redis が必要
  • DelayedJob は ActiveRecord が必要
  • Resque は JSON に変換可能な Ruby オブジェクトのみ、キューに引数として置く事ができる
  • DelayedJob はいかなる Ruby オブジェクトも、キューに引数として置く事ができる
  • Resque は Sinatra ベースのモニタリングアプリケーションを同梱している
  • DelayedJob では、インターフェイスの追加を望むなら、Rails アプリケーションから問い合わせる事ができる

Rails で開発しているなら、データベースと ActiveRecord は利用可能であるはずなので、DelayedJob は非常に簡単にセットアップでき、よく働いてくれる。 GitHub では、しばらくの間利用していて、おそらく 200,000,000 個のジョブを処理してきた。

常に Resque がより良い選択という訳ではないので、用途に応じて選択する必要がある。

Resque を選ぶべき基準

  • 複数のキューを必要としている
  • 数値優先度を必要としないか好まない
  • Ruby オブジェクトのままの引数をキューに置けなくても良い
  • 非常に巨大なキューを扱う可能性がある
  • 何が起きているかを見たい
  • 多くの失敗や無秩序を予想している
  • Redis をセットアップできる
  • RAM が小さすぎない

DelayedJob を選ぶべき基準

  • 数値優先度を好む
  • 毎日非常に多くのジョブを実行するわけではない
  • キューが小さく、すぐに空く
  • 多くの失敗や無秩序がない
  • 簡単に何でもキューに投げ込みたい
  • Redis のセットアップを望まない

Redis のインストール

$ brew install redis
$ redis-server /usr/local/etc/redis.conf
  • OSX なら Homebrew を使うと簡単にインストールできるとの事だが、Homebrew を知らない自分としては何を言っているのか意味が分からない
  • MacPorts にパッケージがあるので、MacPorts を利用しているなら MacPorts で問題ない
  • 普通にソースからコンパイルする手順は Redis の Wiki に書かれている
$ git clone git://github.com/defunkt/resque.git
$ cd resque
$ rake redis:install dtach:install
$ rake redis:start

Resque の rake タスクでインストールと起動を行える。

Homebrew

横道にそれるが、気になるので Homebrew についても調査した。

要するに、パッケージ管理用ソフトウェア。

Resque の依存パッケージ

$ gem install redis redis-namespace yajl-ruby --source=http://gemcutter.org

yajl-ruby がインストールできなければ、json をインストールする事で、Resque は代わりにそれを使う様になる。

Resque のインストール

Rack アプリケーション

まず、gem をインストールする。

$ gem install resque --source=http://gemcutter.org

アプリケーションから Resque を使える様にする。

require 'resque'

アプリケーションを起動する。

$ rackup config.ru

これで、Resque のジョブをアプリケーションから作成可能になった。

rake タスクは、Rakefile に以下を加えれば良い。

require 'your/app'
require 'resque/tasks'

Rails アプリケーション

Rack の場合と同様に、Resque の gem をインストールする。

アプリケーションから Resque を使える様に、initializer を追加する。

$ cat config/initializers/load_resque.rb
require 'resque'

アプリケーションを起動する。

$ ./script/server

rake タスクは、Rakefile に以下を加えれば良い。

require 'resque/tasks'

忘れずに、resque:setup タスクを定義し、environment タスクを呼び出せる様にしておく事。

設定

  • Resque は、文字列か Redis オブジェクトを受け取る redis というセッターを持っている
    • アプリケーションで Redis を利用しているなら、コネクションを再利用できるという事
    • Resque.redis = 'localhost:6379'
    • Resque.redis = $redis
      • 原文中では Redus.redis となっている
  • config/initializers/resque.rb から Redis 設定を含んだ config/resque.yml を読み込むと良い
config/resque.yml
development: localhost:6379
test: localhost:6379
staging: redis1.se.github.com:6379
fi: localhost:6379
production: redis1.ae.github.com:6379
config/initializers/resque.rb
rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..'
rails_env = ENV['RAILS_ENV'] || 'development'

resque_config = YAML.load_file(rails_root + '/config/resque.yml')
Resque.redis = resque_config[rails_env]
  • RAILS_ROOT や RAILS_ENV をチェックするだけでないのは、Sinatra アプリケーションで config/initializers/resque.rb を読み込ませている場合を考慮しているから

デモ

  • snatra をインストールしていないなら、インストールしておく必要がある
  • redis-server が起動していないなら起動させておく

デモアプリケーションを起動

$ git clone git://github.com/defunkt/resque.git
$ cd resque/examples/demo
$ rackup config.ru
$ open http://localhost:9292/
  • 何度か "Create New Job" をクリックすると、ペンディング中のジョブ数が増える事が確認できるはず

デモ用のワーカーを起動

$ VERBOSE=true QUEUE=default rake resque:work

上記を実行すると、以下の様な出力を得られるはず。

*** Starting worker hostname:90185:default
*** got: (Job{default} | Demo::Job | [{}])
Processed a job!
*** done: (Job{default} | Demo::Job | [{}])

VVERBOSE でより多くの情報を出力させる事ができる。

$ VVERBOSE=true QUEUE=default rake resque:work
*** Starting worker hostname:90399:default
** [05:55:09 2009-09-16] 90399: Registered signals
** [05:55:09 2009-09-16] 90399: Checking default
** [05:55:09 2009-09-16] 90399: Found job on default
** [05:55:09 2009-09-16] 90399: got: (Job{default} | Demo::Job | [{}])
** [05:55:09 2009-09-16] 90399: resque: Forked 90401 at 1253141709
** [05:55:09 2009-09-16] 90401: resque: Processing default since 1253141709
Processed a job!
** [05:55:10 2009-09-16] 90401: done: (Job{default} | Demo::Job | [{}])

Resque フロントエンド

$ open http://localhost:9292/resque/

モニタリング

god で Resque をモニタリングしたいなら、examples/god/ を参考にするとよい。

Last modified:2009/11/08 22:11:28
Keyword(s):[ruby] [backgroundjob]
References: