Edited at

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