これは何?
Rails 4.2以降で標準で利用可能になったのがActive Job。
バックグラウンドでジョブを実行するために利用するという理解はしてるけど、実は全然理解できていない人向けです。
公式ドキュメントはこちらを参照してください。
そもそもバックグランド処理って何?
開発するアプリケーションが大規模であればあるほど、
プログラムの処理に時間がかかるようなことが出て来ると思います。
(例えば、レコメンド処理の計算とか、何かしらの解析とか)
そういった時間のかかる処理というのを非同期で実行できるようにすることでユーザビリティを向上するのがバックグラウンド処理です。
そのため、バックグラウンド処理で求められる機能は、
- ジョブの登録
- キューの操作
- すぐ実行
- 時間おいて実行
- 定期実行
- 優先度など
- ジョブの実行
- 並列処理など
- ジョブの失敗時の対応
- リトライ
- エラー出力など
などなど様々なことが求められます。
Active Jobって何?
Railsガイドによると、下記のように記載があります。
Active Job is a new framework in Rails 4.2. It is a common interface on top of queuing systems like Resque, Delayed Job, Sidekiq, and more.
そのため、実際にバックグラウンドでジョブを実行できるものではなく、
あくまでバックグラウンドでジョブを実行するgemの共通インターフェースになってるようです。
※ Active Job単体ではキューを実現できないようです。
要は、Active JobがキューにJobを登録して、SidekiqのようなgemがJobを実際に実行するイメージです。
サンプルコード
作成イメージ
下記のようなイメージでコードを作成していきます。
ControllerからジョブがRedisにキュー登録されると、
ActiveJobをインターフェースとしたSidekiqが常にRedisを監視して新規登録があればジョブを実行するという流れ。
詳細
redis
と sidekiq
は入れておいてください。
brew install redis
gem sidekiq
:concurrency: 25
:pidfile: ./tmp/pids/sidekiq.pid
:logfile: ./log/sidekiq.log
:queues:
- default
=> concurrencyは同時実行数だが、sidekiq自身で一つカウントするらしい。
# ActiveJobがSidekiqを使うことを宣言
config.active_job.queue_adapter = :sidekiq
bundle exec rails g job My
queue_as :default
def perform(message)
# ジョブの実行時刻を記録します
p Time.now
end
# CSRF対策がされてるので、その回避のため
protect_from_forgery :except => [:run]
def run
# 即時実行
MyJob.perform_later('Task execute')
# 時間置き実行
MyJob.set(wait: 3.second).perform_later('Task execute')
render plain: 'success'
end
=> perform_later
メソッドはJobのpeform
メソッドを呼び出します。
post 'start/run'
これで準備おけいです。
あとは、Redisの起動、Railsの起動、sidekiqの起動をして、リクエストを投げればsidekiq上で時刻が表示されると思います。
ほぼこちらのコードで勉強させてもらいました。ありがとうございます。
リトライ制御
ActiveJobはリトライ制御が弱いです。
Rails5.1からActiveJob::Base.retry_job
とActiveJob::Base.discard_on
というメソッドが追加されたのですが、そこで制御してもSidekiqのリトライ回数などが優先されてしまいます。
Rails5.1のリリースノート
ここで、リトライ回数を制御する必要性が出て来るわけです。
僕が色々試した中で最も良かったのは、activejob-retryというgemの利用です。
gemをインストールした後は、制御したいジョブクラスの中で下記のように宣言すれば制御可能です。
queue_as :default
include ActiveJob::Retry.new(strategy: :constant, limit: 3, delay: 3.seconds)
rescue_from(StandardError) do
p("All Retry Failed")
end
def perform(message)
p("Time:#{Time.now}:#{message}")
error #エラーを起こすためわざと宣言してない変数を置いてます。
end
実行してみた感じ、きちっと3秒おきに実行されるわけではない(誰かわかる方ぜひ教えてくださいませ)のですが、
今回の目的であったリトライ回数に関してはしっかりと達成されていました。
導入も簡単なのでこちらで試してみるのがいいかもしれません。