とーますメモ

Ruby on Rails / Goなどの学習メモ

【Rails】Sidekiqを使用してみた。

自分用メモ

ActiveJob経由か、直接使うかは
以下の記事で詳細を書いた。
thoames.hatenadiary.jp

自分の場合は、フルにSidekiqの機能を使用したかったため
直接Sidekiqを使用する」方法を取る。

インストール

Gemfile

gem 'sidekiq'
gem 'redis-namespace'

[注意] Redisの設定にnamespaceを設定している場合、
'redis-namespace'のgemをインストールしていいないと、
以下のエラーが出る。

ERROR: Your Redis configuration uses the namespace 'xxxxxxxxx'
but the redis-namespace gem is not included in the Gemfile.
Add the gem to your Gemfile to continue using a namespace. 
Otherwise, remove the namespace parameter.

[参考]
Sidekiq - DesignAssembler

実行

設定ファイルを読み込んで起動する場合

$ bundle exec sidekiq -C config/sidekiq.yml

ベストプラクティス

以下のようにオブジェクトを渡した後、キューが実行される前に
そのオブジェクトが変わった場合、
意図したものと違う内容が実行されてしまう可能性がある。
またRedisにデータを保存する時、引数の情報がJSONとして保存される。
(内部ではRedisに保存するときに、JSON.dumpを使用し、
Redisからデータを参照するときにJSON.loadを使用している)

このため、perform_asyncに渡す引数は必ずJSONとして保存できる
「string, integer, float, boolean, null, array and hash」のいずれかでなければならない。
シンボルやオブジェクト(dateやtimeなども)は引数にしてはいけない。

そのため、処理を特定できる識別子(IDなど)を引数として設定し、
キュー処理の内部で、IDを用い処理を行うことが推奨されている。

✕: 悪い例

quote = Quote.find(quote_id)
SomeWorker.perform_async(quote)

○: 良い例

SomeWorker.perform_async(quote_id)

Best Practices · mperham/sidekiq Wiki · GitHub

リトライ

例外を発生させれば、自動的にリトライが走る。

class HogeJob < ActiveJob::Base
  queue_as :default

  def perform(hoge_id:)
    raise "error" # 明示的に例外投げる
  end
end

リトライさせたくない場合の設定方法は2種類がある。

① retry: 0
② retry: false

設定例)

...
sidekiq_options queue: :xxx, retry: 0
# または
sidekiq_options queue: :xxx, retry: false
...

違いは①の場合は、例外が発生した場合、Sidekiq画面の死亡タブで
内容を確認でき手動で再試行が可能だが、②の場合は死亡タブで確認できない。

再試行する必要がなく、死亡したジョブ履歴を追う必要が無い場合を除き
大体の場合、①の方法を選択するほうが良い。

各workerに設定したくない場合はsidekiq.rbに以下のように設定すれば
全体に設定される。

config/initializers/sidekiq.rb

...
Sidekiq.default_worker_options = { 'retry' => true }
# または
Sidekiq.default_worker_options = { 'retry' => 0 }
...

[参考]
Sidekiq で最大回数リトライ後に失敗した場合出すログに例外のバックトレースを含める - Qiita
sidekiqのリトライ回数は26回以上を指定できる - PartyIX
Delayed::Job で絶対にやっておいた方がいいたった1つの設定
sidekiqにretryしてほしくないが、死亡には入って手動retry出来るようにしたい場合

バックトレース

例外発生時に、その例外のbacktraceログも残したい場合は以下の設定を行う。

sidekiq_options backtrace: true

ただし、大量にJOBが失敗するなど、かなりのbacktraceログが発生する可能性がある場合は
Redisに割り当てるメモリを増やすか、backtraceログの出力行を設定する。
出力行は上から数えた行数を設定する。

例)上から20行までを出力する設定

sidekiq_options backtrace: 20

各workerに設定したくない場合はsidekiq.rbに以下のように設定すれば
全体に設定される。

config/initializers/sidekiq.rb

...
Sidekiq.default_worker_options = { 'backtrace' => true }
# または
Sidekiq.default_worker_options = { 'backtrace' => 20 }
...

死亡直前のログ出力

各workerで設定する場合

...
include Sidekiq::Worker

sidekiq_retries_exhausted do |msg, e|
  Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
end
...

一括で設定する場合(config/initializers/sidekiq.rb)

Sidekiq.configure_server do |config|
  config.default_retries_exhausted = -> (job, ex) do
    Sidekiq.logger.warn "#{job['class']} job is now dead: args(#{job['args']}) => msg(#{job['error_message']})"
  end
end

[参考]
Sidekiq で最大回数リトライ後に失敗した場合出すログに例外のバックトレースを含める - Qiita

死亡時にSlack通知

Slack通知にはslack-notifierを使用する。
詳細は以下のページが詳しい。

slack-notifierでrailsからSlackへ簡単にメッセージを送る - Qiita

config.default_retries_exhausted内に設定を追記すれば良い。

config.default_retries_exhausted = -> (job, ex) do
  message = "#{job['class']} job is now dead: args(#{job['args']}) => msg(#{job['error_message']})"
  Slack::Notifier.new(Settings[:sidekiq][:slack_notifier_url]).ping(message)
  Sidekiq.logger.warn message
end

ベーシック認証

require 'sidekiq/web'

Sidekiq::Web.use Rack::Auth::Basic do |username, password|
  username == 'basicuser' && password == 'basicpass'
end
mount Sidekiq::Web => '/sidekiq'

プラグイン

sidekiq-failures

GitHub - mhfs/sidekiq-failures: Keep track of Sidekiq failed jobs

失敗したジョブの詳細をWebUIで見ることができる。
デフォルトだと最新の失敗を、1000個まで確認できる。

以下のヘルパーメソッドは便利そう

Sidekiq::Failures.count
Sidekiq::Failures.reset_failures
sidekiq-history

GitHub - russ/sidekiq-history: Sidekiq History

ダッシュボードの拡張。
種類に関係なく全ジョブの履歴ログを見ることができるようになる。
デフォルトだと最新の履歴ログを、1000個まで確認できる。

sidekiq-statistic

GitHub - davydovanton/sidekiq-statistic: See statistic about your workers

ダッシュボードの拡張。
解析用画面がWebUIに追加される。
グラフで成功、失敗したJobの確認ができたり
解析用のAPIが使えるようになる。

sidekiq-unique-jobs

GitHub - mhenrixon/sidekiq-unique-jobs: The missing unique jobs in sidekiq

The job will be unique for the number of seconds configured (default 30 minutes) or until the job has been completed.
Thus, the job will be unique for the shorter of the two.

同じJobが複数登録された場合、それを1つのJobにするっぽい。
デフォルトだと30分。

capistrano-sidekiq

GitHub - seuros/capistrano-sidekiq: Sidekiq integration for Capistrano

capistranoを使用している場合は、デプロイ時に Sidekiq がロードしているコードが更新されないため、
下記の設定を追記して、デプロイと同時に再起動されるように設定します。

config/Capfile

require 'capistrano/sidekiq'

config/deploy.rb

set :sidekiq_role, :web

By Railsで非同期処理を行える「Sidekiq」

[参考]
Sidekiqで非業の死を遂げたキューを知る方法 - Qiita
Railsで非同期処理:キュー。Sidekiq(+ActiveJob)がResqueよりも、とても簡単便利。 - Qiita
capistrano-sidekiqを使うのをやめる - Qiita