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


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

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

```ruby: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・例外にも対応しているので、使いやすいです。

- とても分かりやすい公式ドキュメント [http://railsguides.jp/active_job_basics.html](http://railsguides.jp/active_job_basics.html)


# 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は、ダッシュボードを閲覧するために必要です。

```Gemfile
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
```

```ruby: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](https://qiita-image-store.s3.amazonaws.com/0/2152/50f301a2-dd72-2f34-f950-8da02b41c333.png "スクリーンショット 2015-05-12 9.12.33.png")

## Sidekiqのデプロイ

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

```ruby: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)
}
```

```ruby: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
```

```Capfile
require 'capistrano/sidekiq/monit' # 追加
```

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

```
$ sudo monit status
```

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









# Resqueのインストール・設定

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

```Gemfile
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}"
```

```ruby:app/workers/sample.rb
class Sample
  @queue = :default

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

```ruby: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](https://qiita-image-store.s3.amazonaws.com/0/2152/cc7c324c-fc89-c956-27e1-662ad0ab00c9.png "スクリーンショット 2015-05-12 8.47.30.png")