LoginSignup
345

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-05-18

メール送信や、重い処理、バッチなどでキューイングってよくやると思うんですが、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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
345