Ruby
Rails
Resque
sidekiq
ActiveJob

Railsで非同期処理:キュー。Sidekiq(+ActiveJob)がResqueよりも、とても簡単便利。

More than 1 year has passed since last update.

メール送信や、重い処理、バッチなどでキューイングってよくやると思うんですが、Rubyではそれらを簡単に実現できるライブラリがいくつかあります。有名所だと、Sidekiq / Resque / Delayed Jobとか。僕はSidekiq / Resqueこの2つを使ったことがあるんですが…最近ではSidekiqの方が設定も簡単で使いやすかったので、Sidekiqにまつわる設定などもろもろまとめておきます。

ActiveJobについて

Rails4.2からActiveJobというキュー操作のフレームワークが導入されました。これを使用することで、書き方が統一されるため、バックグラウンドのキューライブラリがSidekiqだろうとResqueだろうと気にせず書けるっていうやつです。Rails4.2を使っているのなら積極的に採用して良い感じ。

(2017/08/07:追記) 実際にいくつかのプロジェクトでActiveJobを使ってみましたが、今はActiveJobを使わずSidekiqを直接使うほうがメリットが有るなと感じています。抽象化されてSidekiqのオプションが使えなかったりするからです。キューライブラリを乗り換えるとなると、それなりの変更が発生するため、ActiveJobを使わなくてもいいかなーと感じています。

$ ./bin/rails g job guests_cleanup
      invoke  test_unit
      create    test/jobs/hoge_job_test.rb
      create  app/jobs/hoge_job.rb

ワーカースクリプトは、一般的な書き方ですね。

app/jobs/hoge_job.rb
class HogeJob < ActiveJob::Base
  queue_as :default

  def perform(*args)
    # Do something later
  end
end

キューの実行もほぼほぼ同じ。

> HogeJob.perform_later("hoge")
> HogeJob.set(wait: 1.week).perform_later(record) # 1週間後

コールバックもActionMailer・例外にも対応しているので、使いやすいです。

ResqueとSidekiqの大雑把な比較

Resque

  • Redis使う
  • ジョブごとにフォークされてメモリ初期化されるからスッキリ
    • でも大量にジョブを処理するとフォークオーバヘッドが…
  • リトライ処理ができない
  • デーモン起動が面倒
  • ダッシュボードがある(質素)

Sidekiq

  • Redis使う
  • マルチスレッド(Pumaっぽい)
    • 軽くて大量に処理しまくりたい場合に向いているかも
    • 長時間動かしているとコードによってはプロセスメモリ肥大化するかも
  • リトライ処理ができる
  • デーモン起動が簡単
  • ダッシュボードがある(おしゃれ)

Delayed Job

  • 使ってないからわからぬが、Redisではなく専用にテーブルを作成して使う

とりあえずRedisのインストールなど

ResqueもSidekiqもキューを貯めるためにRedisを使用します。

$ sudo yum install -y redis
$ sudo service redis start

Sidekiqのインストール・設定

sinatraは、ダッシュボードを閲覧するために必要です。

gem 'sidekiq'
gem 'sinatra', require: false
gem 'redis-namespace'
$ bundle install

SidekiqのRedis設定を行います。

config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  case Rails.env
    when 'production' then
      config.redis = { url: 'redis://prd.redis-example.com:6379', namespace: 'sidekiq' }
    when 'staging' then
      config.redis = { url: 'redis://stg.redis-example.com:6379', namespace: 'sidekiq' }
    else
      config.redis = { url: 'redis://127.0.0.1:6379', namespace: 'sidekiq' }
  end
end

Sidekiq.configure_client do |config|
  case Rails.env
    when 'production' then
      config.redis = { url: 'redis://prd.redis-example.com:6379', namespace: 'sidekiq' }
    when 'staging' then
      config.redis = { url: 'redis://stg.redis-example.com:6379', namespace: 'sidekiq' }
    else
      config.redis = { url: 'redis://127.0.0.1:6379', namespace: 'sidekiq' }
  end
end

Sidekiqのワーカースクリプトを生成します。

$ rails g sidekiq:worker Sample
app/workers/sample.rb
class Sample
  include Sidekiq::Worker

  def perform(data)
    Rails.logger.debug(data)
  end
end

ワーカープロセスを起動。

$ bundle exec sidekiq

あとは、RailsConsoleからキューの登録を行えば、自動で実行されるはずです。

$ rails c
> Sample.perform_async('hoge')

Sample.perform_async('hoge')が一般的なキュー登録ですが、他にも色々あります。

Sample.perform_in(3.hours, 'hoge', 1) # 3時間後に実行する
Sample.perform_at(3.hours.from_now, 'hoge', 1) # こちらも3時間後に実行する

Sidekiqのダッシュボード

わりと今風なダッシュボードで見やすい。グラフの描画が嬉しいですね。

config/routes.rb
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'

スクリーンショット 2015-05-12 9.12.33.png

Sidekiqのデプロイ

Sidekiqをデーモンとして、capistranoでのデプロイは非常に簡単。capistrano-sidekiqというGemを追加します。

Gemfile
group :development, :test do
  gem 'capistrano-sidekiq', github: 'seuros/capistrano-sidekiq' # 追加
end
config/deploy/production.rb
set :rails_env, "production"
set :unicorn_rack_env, "production"
set :pty, false # これをセットしないと、デプロイ時にSidekiqがデーモン起動しない…

role :app, %w{vagrant@192.168.33.40}
role :web, %w{vagrant@192.168.33.40}
role :db,  %w{vagrant@192.168.33.40}

server '192.168.33.40', user: 'vagrant', roles: %w{web app}

set :ssh_options, {
  keys: %w(/home/vagrant/.ssh/id_rsa),
  forward_agent: false,
  auth_methods: %w(publickey)
}
Capfile
require 'capistrano/sidekiq' # 追加
$ bundle exec cap production deploy

これでデプロイ完了です。

Webサーバとバッチサーバを分ける場合

Sidekiqのデーモンはバッチサーバをだけに分けたい場合は、:sidekiq_roleを設定します。これによって、192.168.33.40はRailsのWebサーバ、192.168.33.41はSidekiqだけが走るバッチサーバになります。Sidekiq自体は並列にいくつも走らせても大丈夫なので、キューが(多い|重い)場合はbatchロールをスケールアウトしていくのが良いと思います。

config/deploy/production.rb
# 追加
set :sidekiq_role, :batch
role :batch,  %w{vagrant@192.168.33.41}
server '192.168.33.41', user: 'vagrant', roles: %w{batch}

Sidekiqの起動/停止/再起動

デプロイはしたくないけど、Sidekiqプロセス操作したい場合は(start|stop|restart)などのオプションがあります。便利。

$ bundle exec cap production sidekiq:start

monitでSidekiqプロセス管理(監視)

SidekiqにしろResqueにしろデーモン化されたプロセスは、いつ死ぬかわからないし、ざっくりとしたメモリリーク対策として定期的なプロセス再起動とかやりたいので、プロセス管理(監視)が必要になります。僕はmonitがシンプルで好きなので、monitをインストール設定します。

$ sudo yum install -y monit
$ sudo chkconfig monit on
/etc/monit.conf
set httpd port 2812 and
  allow localhost
$ sudo service monit start
require 'capistrano/sidekiq/monit' # 追加

後は、デプロイすると自動的にmonitの設定ファイルを生成、転送してくれます。デフォルトだと、/etc/monit/conf.d/hoge_production.confに配置しようとするのでデプロイユーザのパーミッションを適切に設定してください。

$ sudo monit status

これで、プロセスの状態を見ることが出来ます。

Resqueのインストール・設定

とりあえずResqueのインストール・設定コードも残しておきます。

gem 'resque', :require => 'resque/server'
$ bundle install
config/initializers/resque.rb
case Rails.env
  when 'production' then
    Resque.redis = 'prd.redis-example.com'
  when 'staging' then
    Resque.redis = 'stg-example.com'
  else
    Resque.redis = '127.0.0.1'
end
Resque.redis.namespace = "resque:prj-name:#{Rails.env}"
app/workers/sample.rb
class Sample
  @queue = :default

  def self.perform(data)
    Rails.logger.debug(data)
  end
end
lib/tasks/resque.rake
require 'resque/tasks'
$ QUEUE=* ./bin/rake environment resque:work
$ rails c
> Resque.enqueue(Sample, "hoge")

Resqueのダッシュボード

config/routes.rb
mount Resque::Server, :at => "/resque"

スクリーンショット 2015-05-12 8.47.30.png