Rails 非同期で処理を実行する方法(Sidekiq, Resque, Delayed Job, Active Job比較)
概要
Rails で WEB 画面からのキックでジョブをバックグラウンドで実行するときどうするか。
例えば、メール送信・画像変換・CSVアップロードによる大量SQL実行など。
そんなときはバックグラウンドで非同期にジョブを実行してくれる便利な gem がある。
ruby-rails.hatenadiary.com
このサイトで丁寧に説明してくれてます。
代表的だと言われている下記3つについて、それぞれ実装して使い心地を比較する。
- Sidekiq: mperham/sidekiq · GitHub
- Resque: resque/resque · GitHub
- Delayed Job: collectiveidea/delayed_job · GitHub
それと上記サイトで書かれている Active Job これの使い勝手も試してみる。
それぞれの gem について、もっと詳しく知りたい方は下記参照。
メリット・デメリットがそれぞれまとめられているし、ジョブを実行する際の違いも書かれてます。
準備
Rails を 4.2.1 へ
勉強用の Rails 環境は 4.1.8 なので、 Active Job が導入されたという 4.2.1 (現時点で最新)へアップデートする。
Gemfile を必要に応じて書き換え、下記を実行。
# bundle update ... Installing rails 4.2.1 (was 4.1.8) ...
redis をインストール・起動
Sidekiq, Resque はキューの管理に redis を使うので、インストールして起動しておく。
# brew install redis # redis-server
gem をインストール
Gemfile に下記を追加。
gem 'resque' gem 'sidekiq' gem 'delayed_job_active_record'
ActiveRecord でなくて、Mongoid を利用している場合は
gem 'delayed_job_mongoid'
をインストール。
# bundle install
実装
Sidekiq 編
準備
Sidekiq 向けに Redis 接続先の設定をする。
# vim config/initializers/sidekiq.rb
でファイル作成。
Sidekiq.configure_server do |config| config.redis = { url: 'redis://localhost:6379', namespace: 'sidekiq' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://localhost:6379', namespace: 'sidekiq' } end # 環境が同居している場合は下記のように設定する # 設定しないと環境の区別がつかないので何の環境で実行されるかわからなくなる # namespace: "sidekiq_#{Rails.env}" # アプリ & 環境名を付けるといいっぽい
次は Sidekiq 起動時の設定。
# vim config/sidekiq.yml
でファイル作成。
--- :pidfile: ./tmp/pids/sidekiq.pid :logfile: ./log/sidekiq.log
Sidekiq 向けの Worker を実装
Worker クラスを定義し、perform メソッドに非同期で処理させたい実装を行う。
# vim app/workers/worker_for_sidekiq.rb
class WorkerForSidekiq include Sidekiq::Worker sidekiq_options queue: :sidekiq def perform(text) sleep 5 p "sidekiq: #{text}" end # ActiveRecord を扱う場合など、引数で全情報を与える必要がある # def perform(id, text) # hoge = Hoge.find(id) # hoge.update_attributes({text: text}) # end end
Sidekiq 起動
Sidekiq を起動する。
画面はそのままにしておく。
# bundle exec sidekiq -q sidekiq
-q オプションでsidekiq_options queue: :sidekiq
で定義したキュー名を指定。
キューに追加
コントローラーの実装が面倒なのでコンソールを立ち上げる。
# bundle exec rails c
コンソール上からキューに追加する。
[1] pry(main)> WorkerForSidekiq.perform_async("aiueo") => "358c09a3547d3d52dc1901ab" [2] pry(main)> WorkerForSidekiq.perform_async("kakikukeko") => "804d15dadef12e34a5beb1cd" [3] pry(main)> WorkerForSidekiq.perform_async("sasisuseso") => "ab4fda7f312442fe0fddb892"
確認
Sidekiq を立ち上げたコンソール側では、下記が非同期で処理されて表示されることが確認できる。
"sidekiq: aiueo" "sidekiq: kakikukeko" "sidekiq: sasisuseso"
# tail log/sidekiq.log
にも実行のログが残っていることが確認できる。
これで Sidekiq はおしまい。
参考
Resque 編
Resque 向け Redis 設定
# vim config/initializers/resque.rb
設定ファイルを作る。
Resque.redis = 'localhost:6379' Resque.redis.namespace = "resque" # 補足:アプリ & 環境 でネームスペースを決めた方がいいらしい # Resque.redis.namespace = "resque:app_name:#{Rails.env}"
Resque 向け Worker 起動用タスクを追加
# vim lib/tasks/resque.rake
でファイル追加。
require 'resque/tasks' task 'resque:setup' => :environment
Resque 向けの Worker を実装
Worker クラスを定義し、self.perform メソッドに非同期で処理させたい実装を行う。
class WorkerForResque @queue = :resque def self.perform(text) sleep 5 p "resque: #{text}" end end # Sidekiq と同じで必要情報は全て引数で perform メソッドに渡す必要あり
Resque 起動
Resque を起動する。
画面はそのままにしておく。
# QUEUE=resque bundle exec rake resque:work
QUEUE で指定する値は Worker の @queue へセットした値。
QUEUE=* と指定して実行すると全てのキューが実行対象となる。
キューに追加
コンソールを立ち上げる。
# bundle exec rails c
コンソール上からキューに追加。
[1] pry(main)> Resque.enqueue(WorkerForResque, "aiueo") => true [2] pry(main)> Resque.enqueue(WorkerForResque, "kakikukeko") => true [3] pry(main)> Resque.enqueue(WorkerForResque, "sasisuseso") => true
確認
Resque 起動のコンソールが下記のように表示されることを確認。
"resque: aiueo" "resque: kakikukeko" "resque: sasisuseso"
これで Resque の確認はおしまい。
参考
Delayed Job 編
特徴
Delayed Job では Sidekiq と Resque とは異なる方法でキューにジョブを追加する方法がある。
Delayed Job をインストールすると、Object と Module に delay メソッドが追加される。
Hoge.delay.send_mail
や hoge.delay.send_mail
などのようにすることで、
どんな処理でも非同期で実行できる。
またキューの管理は Redis を使わず、Rails と同じ DB (ORM)を利用する。
準備
キュー管理用のテーブルを追加して、反映する。
# bundle exec rails g delayed_job:active_record # bundle exec rake db:migrate
Delayed Job 向けの Worker を実装
class WorkerForDelayedJob def initialize(text) @text = text end def perform sleep 5 p "delayed_job: #{@text}" end end # perform メソッドに引数取れないっぽいのでこんな実装になった
Delayed Job 起動
コンソールを立ち上げて、コマンドを打ったらそのままの状態にしておく。
# QUEUE=delayed_job bundle exec rake jobs:work
キューに追加
コンソール起動。
# bundle exec rails c
コンソールからキューを追加。
[1] pry(main)> Delayed::Job.enqueue(WorkerForDelayedJob.new("aiueo"), queue: "delayed_job") [2] pry(main)> Delayed::Job.enqueue(WorkerForDelayedJob.new("kakikukeko"), queue: "delayed_job") [3] pry(main)> Delayed::Job.enqueue(WorkerForDelayedJob.new("sasisuseso"), queue: "delayed_job") [4] pry(main)> job = WorkerForDelayedJob.new("tatututeto") [5] pry(main)> job.delay(queue: "delayed_job").perform
Delayed Job 特有の delay
メソッドでのキュー追加も試してみる。
確認
Delayed Job を起動したコンソールを確認する。
実行ログに混じって、実行結果が下記のように表示されることを確認。
"delayed_job: aiueo" "delayed_job: kakikukeko" "delayed_job: sasisuseso" "delayed_job: tatututeto"
Delayed Job 編おしまい。
参考
RailsでAcitiveJobとDelayedJobを使ってバックグランド処理を行う - Rails Webook
Active Job を使ってみる
概要
Active Job で実装すれば、上記のような非同期処理の gem を意識する必要がなくなる。
対応している gem を確認するには下記ディレクトリを覗く。
https://github.com/rails/rails/tree/master/activejob/lib/active_job/queue_adapters
(美しい Adapter パターン)
Active Job で実装して、queue_adapter の指定をそれぞれ Sidekiq, Resque, Delayed Job にしてみて、 同じように非同期で動作するところまでを確認する。
ジョブを作成する
下記コマンドを実行。
# bundle exec rails g job hoge invoke test_unit create test/jobs/hoge_job_test.rb create app/jobs/hoge_job.rb
ジョブを実装する
# vim app/jobs/hoge_job.rb
でファイル開くと、下記の状態。
class HogeJob < ActiveJob::Base queue_as :default def perform(*args) # Do something later end end
それを下記のように実装。
class HogeJob < ActiveJob::Base queue_as :default def perform(text) sleep(5) p "ActiveJob: #{text}" end end
Sidekiq アダプターで実行
アダプターの設定をする
# vim config/application.rb
でファイルを編集。
module Hoge class Application < Rails::Application ... config.active_job.queue_adapter = :sidekiq ... end
Sidekiq 起動して、Rails コンソールからキュー登録
# bundle exec sidekiq ↑立ち上げっぱなし ↓別コンソールで実行 # bundle exec rails c
[1] pry(main)> HogeJob.perform_later("aiueo") Enqueued HogeJob (Job ID: b5bfa62b-11f6-4d16-848c-0a1aee98abb8) to Sidekiq(default) with arguments: "aiueo" => #<HogeJob:0x007ff5e35427f8 @arguments=["aiueo"], @job_id="b5bfa62b-11f6-4d16-848c-0a1aee98abb8", @queue_name="default">
確認
Sidekiq を起動したコンソールで下記が表示される。
"ActiveJob: aiueo"
Resque アダプターで実行
アダプターの設定をする
# vim config/application.rb
でファイルを編集。
...
config.active_job.queue_adapter = :resque
...
Resque 起動して、Rails コンソールからキュー登録
# QUEUE=* bundle exec rake resque:work ↑立ち上げっぱなし ↓別コンソールで実行 # bundle exec rails c
[1] pry(main)> HogeJob.perform_later("kakikukeko") Enqueued HogeJob (Job ID: f205128d-5941-4abd-a579-35b992bd06a2) to Resque(default) with arguments: "kakikukeko" => #<HogeJob:0x007fac1a9237b8 @arguments=["kakikukeko"], @job_id="f205128d-5941-4abd-a579-35b992bd06a2", @queue_name="default">
確認
Resque を起動したコンソールで下記が表示される。
"ActiveJob: kakikukeko"
Delayed Job アダプターで実行
アダプターの設定をする
# vim config/application.rb
でファイルを編集。
...
config.active_job.queue_adapter = :delayed_job
...
Delayed Job 起動して、Rails コンソールからキュー登録
# bundle exec rake jobs:work ↑立ち上げっぱなし ↓別コンソールで実行 # bundle exec rails c
[1] pry(main)> HogeJob.perform_later("sasisuseso") Enqueued HogeJob (Job ID: 052dc5e2-5f37-4947-a193-3b91783a84e1) to DelayedJob(default) with arguments: "sasisuseso" (0.1ms) begin transaction SQL (0.7ms) INSERT INTO "delayed_jobs" ("queue", "handler", "run_at", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["queue", "default"], ["handler", "--- !ruby/object:ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper\njob_data:\n job_class: HogeJob\n job_id: 052dc5e2-5f37-4947-a193-3b91783a84e1\n queue_name: default\n arguments:\n - sasisuseso\n"], ["run_at", "2015-06-05 02:04:21.435653"], ["created_at", "2015-06-05 02:04:21.435913"], ["updated_at", "2015-06-05 02:04:21.435913"]] (0.6ms) commit transaction => #<HogeJob:0x007f934b23b370 @arguments=["sasisuseso"], @job_id="052dc5e2-5f37-4947-a193-3b91783a84e1", @queue_name="default">
確認
Delayed Job を起動したコンソールで下記が表示される。
"ActiveJob: sasisuseso"
まとめ
Sidekiq, Resque, Delayed Job どれもコーディングするにあたってはほとんど大差ない。
それぞれ Web UI でキューの確認もできるし、ジョブの優先度付けなどもできる。
Delayed Job は既存のDBを利用するし、導入も使い方も一番簡単な印象。
delay メソッド拡張によって、どの Object に対しても、Hoge.delay.fuga
で非同期実行にできるのは楽。
小規模サイトで手っ取り早く非同期処理を実現したいなら、Delayed Job がいいと思う。
Redis が使える環境ならば、Sidekiq と Resque も選択肢に入る。
それぞれ下記の特徴があるのでインフラとの兼ね合いや好みか。
Rails 4.2 以上の環境であれば、とりあえず Active Job で実装しておくのがいいと思う。
アダプターの交換も今回実装したように簡単にできるので、 開発環境は Delayed Job で実装してしまって、 本番環境ではジョブキューを何で処理するか相談して決めれば良い。
正直、どれが優れているとか決められないので、
どんな処理をどれくらいの頻度・量で実行するか洗い出し、
下記サイトを参考にインフラ担当者と話し合うのがいいかと。
ジョブキュー処理のResqueとDelayed Jobの使い分けの方針などはありますか? - QA@IT