メール送信や、重い処理、バッチなどでキューイングってよくやると思うんですが、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
ワーカースクリプトは、一般的な書き方ですね。
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
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設定を行います。
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
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のダッシュボード
わりと今風なダッシュボードで見やすい。グラフの描画が嬉しいですね。
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
Sidekiqのデプロイ
Sidekiqをデーモンとして、capistranoでのデプロイは非常に簡単。capistrano-sidekiq
というGemを追加します。
group :development, :test do
gem 'capistrano-sidekiq', github: 'seuros/capistrano-sidekiq' # 追加
end
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)
}
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ロールをスケールアウトしていくのが良いと思います。
# 追加
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
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
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}"
class Sample
@queue = :default
def self.perform(data)
Rails.logger.debug(data)
end
end
require 'resque/tasks'
$ QUEUE=* ./bin/rake environment resque:work
$ rails c
> Resque.enqueue(Sample, "hoge")
Resqueのダッシュボード
mount Resque::Server, :at => "/resque"