Resqueを利用したRailsでの非同期処理/バッチ処理

2011/03/23

Railsアプリケーションの中で非同期処理(バッチ処理)を実現したいことがあります。例えば、こんな場合です。

  • ユーザーが「送信」ボタンを押したら数千通のメールを送る。

数千通のメールを送るにはかなり時間がかかるので、その処理は後回しにして、ユーザーにはすぐにレスポンスを返したいところです。

非同期処理を行うためのRubyライブラリとしてはBackgrounDRbdelayed_jobなどが有名ですが、もう一つ有望な選択肢としてResqueというのがあることを最近知りました。

と言っても、私が知らなかっただけで、RubyGems.orgによれば11万回以上もダウンロードされている有名なライブラリです。昨年(2010年)1月に書かれた、あるブログ記事には詳しい評価が載っています。

以下、私の試用報告を書きます。なお、OSはUbuntu 10.04(LTS)、Railsのバージョンは3.0.5です。


ResqueはバックエンドとしてRedisを用います。最近話題のNoSQLデータベースの一種です。まず、これをインストールします。

$ sudo apt-get install redis-server

インストールに成功したら、念のためバージョン番号を確認します。

$ redis-cli --version
redis-cli 2.2.2

続いて、動作確認のためのRailsアプリケーションを作ります。名前は適当に、kodamaとしておきましょうか。

$ rails new kodama
$ cd kodama

Gemfileに必要なgemライブラリを列挙します。

source 'http://rubygems.org'

gem 'rails', '3.0.5'
gem 'sqlite3'
gem 'resque'
gem 'SystemTimer', :platform => :ruby_18

[訂正] SystemTimer は Ruby 1.9 では不要です。

gemライブラリをインストールします。

$ bundle install

config/initializersディレクトリにload_resque.rbというファイルを作ります。

require 'resque'

Resque.redis = 'localhost:6379'

config/routes.rbを次のように変更します。

Kodama::Application.routes.draw do
  get 'hello/:text', :to => 'hello#show'
end

app/controllersディレクトリにhello_controller.rbというファイルを作ります。

class HelloController < ApplicationController
  def show
    Resque.enqueue(Echo, params[:text])
    render :text => params[:text]
  end
end

appディレクトリの下にworkersディレクトリを作り、そこにecho.rbというファイルを作ります。

class Echo
  @queue = :default

  def self.perform(text)
    sleep 3
    path = File.expand_path("log/echo.log", Rails.root)
    File.open(path, 'a') do |f|
      f.puts "Hello #{text}!"
    end
  end
end

lib/tasksディレクトリにresque.rakeというファイルを作ります。

require 'resque/tasks'

空のログファイルを作ります。

$ touch log/echo.log

これで準備完了です。


ワーカーを起動します。

$ QUEUE=default rake environment resque:work

別のターミナルを開いて、Railsサーバを起動します。

$ rails server

さらに別のターミナルを開いて、ログファイルを監視してください。

$ tail -f log/echo.log

ここで、ブラウザで http://localhost:3000/hello/world にアクセスします。ログファイルに注目してください。アクセスした瞬間、ブラウザには「world」と表示されますが、ログファイルには何も起こりません。しかし、約3秒数秒後に

Hello world!

と表示されます。さらに、http://localhost:3000/hello/boys にアクセスすると、やはり約3秒数秒おいてログファイルに

Hello boys!

という行が追加されます。

うーむ。驚くほど簡単ですね!

[訂正] 本文中で2回「約3秒」と書きましたが、「数秒」と訂正します。実は、ワーカーは5秒間隔で新たなタスクが追加されていないかチェックしているので、アクセスのタイミングによって3秒後から8秒後にログファイルが書き換わります。(2011/03/23朝)


もう夜も遅いのでソースコードの説明はしませんが、何となく使い方はお分かりになるんじゃないでしょうか。

ポイントを挙げるとすれば、Echoクラスのクラスインスタンス変数@queueにセットした:defaultというシンボルです。これは何でもいいのですが、ここで指定した値が、ワーカーを起動するときの環境変数QUEUEの値になります。両者を一致させる点に注意してください。

なお、ファイル echo.rb を置くディレクトリは、app/modelsでもいいです。

書いてある通りにやってみたけど動かないよ、という方は、ワーカーを起動するコマンドを次のように変更してみてください。

$ QUEUE=default VVERBOSE=true rake environment resque:work

状況が細かく画面に表示されますので、うまく動かない原因が分かるかもしれません。