[キャッチアップ] SideKiq + Rails

スクラップについて
Rails アプリケーションにおいて、非同期ジョブを Sidekiq で運用している場合の全体感の理解を深めるための勉強ノート。
Sidekiq は OSS 版を想定し、エンプラ、プロプランについては扱わない。

まず sidekiq 単体で見る

Simple, efficient background processing for Ruby.
Sidekiq uses threads to handle many jobs at the same time in the same process. It does not require Rails but will integrate tightly with Rails to make background processing dead simple.
- Sidekiq は Ruby のための非同期ジョブを効率的に実行するためのシステム
- 同一プロセス内でのマルチスレッドによって同時に複数ジョブを効率的に実行できる
- Rails に依存はしないが、Rails とのインテグレーションも容易

Requirements
Redis: 6.2+
Ruby: MRI 2.7+ or JRuby 9.3+.
Sidekiq 7.0 supports Rails 6.0+ but does not require it.
Redis が必須というのがポイント。Redis を用いたキューイングが Sidekiq の主要構成みたい。

Sidekiq には Web UI がデフォルトで付いてくる。ここからプロセスやキューの管理が出来る模様。

細かいドキュメントはリポジトリの Wiki にまとまってるみたいなのでこっちを読んでいくのが良さそう。

Sidekiq makes every effort to make usage with modern Rails applications as simple as possible. Please see the ActiveJob page if you want to use Sidekiq with ActiveJob.
Rails 必須ではないとはいえ、基本的には Rails (ActiveJob) と組み合わせて使うことを想定してそう。
なので Wiki 上でもActiveJob を使ったサンプルコードが続く。

そもそも Active Job がよくわかってないので、Sidekiq から一旦離れて Rails ガイドのほうを確認する。

- Active Job はバックグラウンドで実行するキューイングするためのフレームワーク
- ジョブ実行のためのインフラ配置が目的で、実際にジョブを実行する様々なツールの差異を吸収する
- Sidekiq
- DelayedJob
- Resque

ジョブは ApplicationJob クラスを継承したクラスで実装される。
class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
# 後で実行するタスクをここに置く
end
end
ジョブには perform
メソッドを実装し、ここで実際のジョブを実装する

ジョブは perform_later
(クラスメソッド) でキューイングできる
GuestsCleanupJob.perform_later guest

ジョブの実行タイミングを相対時刻または絶対時刻で遅延可能
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon)
GuestsCleanupJob.set(wait: 1.week)

production環境でのジョブのキュー登録と実行では、キューイングのバックエンドを用意しておく必要があります。具体的には、Railsで使うべきサードパーティのキューイングライブラリを決める必要があります。
ので、何らかのキューイングライブラリは必要。本スクラップではそれが Sidekiq にあたる。

一旦 Sidekiq の wiki に戻る。
ActiveJob で Sidekiq を使用する場合は、queue_adapter
を設定する。
class Application < Rails::Application
# ...
config.active_job.queue_adapter = :sidekiq
end

ここでこのような言説を見てしまった。
どうやら、Sidekiq 固有の機能を活用する上では ActiveJob を経由すると不都合が多いらしい。
リトライ機構を Active Job と Sidekiq それぞれ実装していてそのあたりのデバッグも困難になるとか。
実際今関わってるプロダクトでも ActiveJob は使用してないことに気づいた。
ので、Active Job についてはここですべて忘れて、以降は Sidekiq 単体で利用することを前提に調べていく。

Sidekiq の基礎知識に戻る
Sidekiq を構成する3要素
- Client
- ジョブを生成するクライアント
- ジョブは引数を含めてシリアライズした文字列を Redis にキューするため、複雑なオブジェクトは避けること
- Redis
- ジョブをキューするストレージ
- 実行済みの過去データについても、Web UI で閲覧するために蓄積される
- Server
- Redis からジョブをデキューして実行するサーバー
- 通常はクライアントと同じランタイムを使用する (クライアントが Rails ならサーバーの Rails を使えるランタイムであるべき)

ベストプラクティス
ジョブのパラメータは小さくシンプルに
前述の通り、パラメータはシリアライズして小さく収まるようにしようね
ジョブは冪等性を持ち、トランザクショナルであるべき
ジョブは実行中にエラーが発生した場合に再実行される仕組みであるため、どこまで実行されるかが保証されない。そのため、何度同じジョブを実行しても結果が安定するようなジョブが望ましい。
また、途中で失敗しても良いように、トランザクション化してエラー時に適切にロールバックできるようにしよう。
並列処理に備える
Sidekiq は基本的に並列実行されるので注意しよう。例えば DB のコネクションプールを Sidekiq だけで枯渇してしまわないように気をつけよう。
用語を正しく使おう
ワーカー (worker
) という単語は Sidekiq 上では曖昧なので、ジョブ、スレッド、プロセス、ジョブクラスという単語を使い分けよう。
プロセスが複数のスレッドを持ち、スレッドでジョブを実行する。ジョブはジョブクラスを実装したモジュール。

ジョブのライフサイクル
- Scheduled Queue
- 実行タイミングを指定され、将来的にキューに積まれる予定のジョブ
- Enqueued Gauge
- キューに積まれている実行待ちのジョブで、サーバーはここからジョブを取り出して実行する
- Retries Queue
- 実行に失敗し、自動でリトライ待ちになっているジョブ、サーバーはここからも取り出して再実行する
- Dead Queue
- リトライでも失敗したジョブの墓場だが、保持され続けるので手動で再実行することはできる
- Busy Gauge
- 現在実行のジョブ
- Processed(Counter)
- 正常終了したジョブ(の総数)
すべてのジョブは最終的には Processed か Dead のどちらかに行き着く

エラーハンドリング
ベストプラクティス
- Sentry みたいなエラーレポートサービスを導入してエラーを検知しよう
- 例外を raise してSidekiqにリトライを促そう
- バグによって Dead したキューは、バグを修正したら手動で再実行しよう
- 6ヶ月でジョブは破棄されるので、その前に対応しよう

概ね把握できたからこのあたりで良さそう。