Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: SidekiqはActive Jobを経由せずに直接使おう(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

参考: 週刊Railsウォッチ20211018: SidekiqをActive Job経由ではなく直接使う

sidekiq/sidekiq - GitHub

参考: Active Job の基礎 - Railsガイド

Rails: SidekiqはActive Jobを経由せずに直接使おう(翻訳)

Webアプリケーションを構築する場合は、ユーザーごとのレスポンスに要する時間を最小限に留めるべきです。Webサイトが速ければ速いほど、その分ユーザーも幸せになれます。

そのための方法の1つは、重くなる可能性のある処理(実行に長時間かかる、パラレル化可能な処理)を、イミディエイトなWebリクエストの外で非同期実行することです。メール送信、計画的なクリーンアップ、長時間かかる計算、外部APIを用いるすべての作業などが該当する可能性があります。

Active Jobは、エンキューおよびバックグラウンド処理の実行のためにRailsで推奨されている方法です。Active Jobは、さまざまなアダプタ実装の概念のラッパーを提供しています。アダプタ実装のほとんどは、Rails 4.2でActive Jobが登場する前から存在しています。

アダプタ実装の中でもよく知られていて、実地のバトルテストで実績を積んでいる高性能な非同期フレームワークのひとつがSidekiqです。Sidekiqはマルチスレッド化されており、キューイングストレージにRedisを利用します。Sidekiqはオープンソース版ライブラリに加えて、ハイエンド機能を提供する2種類の有料版もあります。

以下のようにするよりも

ジョブをActive Jobで定義する。

class DoThingsInBackgroundJob < ApplicationJob
  queue_as :default

  def perform(an_active_record_object)
    an_active_record_object.do_things
  end
end

以下のようにしよう

Sidekiqを直接利用する。

class DoThingsInBackgroundJob
  include Sidekiq::Worker
  Sidekiq_options queue: "default"

  def perform(id)
    an_active_record_object = ActiveRecordObject.find_by(id: id)
    an_active_record_object.do_things
  end
end

そうする理由

Sidekiqを直接利用しない場合、Sidekiqの高度な機能の多くが利用できなくなります。

Sidekiqを直接使っていれば、バックグランドの処理量がものすごく多い場合でも、一般によく推奨されるように、高速で小さい多数のジョブに分割してキューに登録することでパフォーマンスが2倍から20倍向上します。どの程度パフォーマンスが向上するかは、各自のセットアップによって異なります。

多数のジョブをキューに登録する場合は、Sidekiqのバルクキューイング機能も利用できます。これは、SidekiqをActive Jobの元で使う場合は難しくなります。

ジョブ失敗時のリトライには、混乱しそうなぐらいさまざまなレベルがあります。Active Jobは独自のリトライメカニズムを持っていて、そこでのリトライが完了するとSidekiq独自のメカニズム(まったく別物です!)に引き継がれるので、問題が起きたときのデバッグが厄介になります。

Active Jobでは、#performメソッドにActive Recordオブジェクトを渡すと、Global IDを用いてテキスト引数にシリアライズできます。これは探索の手間を省けますが、キューからジョブが取り出される前にレコードが削除されるとエラーが発生します。この自動シリアライズは、WebダッシュボードでSidekiqジョブの引数が読みづらくなる原因にもなります。

SidekiqをActive Jobでラップせずに使うと、Sidekiqにロックインされて切り離せなくなるのではないかという心配は無用です。今使っているキューイングシステムを、今と同じぐらい制約の多いActive Jobアダプタに差し替えるような事態は、メインのデータベースを別のものに差し替える場合ほどレアではないにしても、めったなことでは発生しません。

もし本気で非同期インフラを差し替えるのであれば、設定を1行書いてアダプタを切り替える作業では済まない、大規模な移行プロジェクトになることを覚悟すべきです。

そうしない理由があるとすれば

Sidekiqを直接使う場合は、ジョブの引数をどんな形にすべきかを十分考えておく必要があり、オブジェクトの探索も自分で行わなければならなくなります。Sidekiqではジョブにシンプルな値しか渡せません。

既にSidekiqをActive Job経由で利用していても、必ずしも既存のジョブを完全にSidekiqに変更しなければならないわけではありません。SidekiqかActive Jobかという二者択一を迫られる必要はなく、両方を同時に定義して使うことも可能です。管理面は少しぐらい面倒になるかもしれませんが、解決方法としては悪くありません。高パフォーマンスとバルクエンキューが必要なときだけSidekiqのジョブを直接利用すればよいのです。

負荷が軽い場合やプロジェクトの初期段階なら、Sidekiq(少なくともSidekiqのRedis依存)はまったく不要かもしれません。

Good JobQue(PostgreSQLのみ)、Delayed Job(任意のSQLデータベース)は、Active Jobアダプタとしてよく知られています。これらのオプションのいずれかを使う場合は、データベースインフラを追加して実行しておく必要はありません。

おすすめ

production環境でSidekiqを使うのであれば、Pro版のライセンスを購入すべきです。クラッシュしたときのジョブデータが"喪失"しなくなる高信頼性のsuper_fetch戦略はproduction環境で利用する理由付けとして十分ですし、ジョブidやジョブ種別によるジョブ削除や、強化版Webダッシュボードといった多くの追加機能も利用できます。

免責事項

私はSidekiq "Pro版"の熱心なファンで、かつ顧客でもあります。また、Sidekiq作者であるMikeにはさまざまなRubyのカンファレンスでお会いしています。だからといって、私はこのライブラリを色眼鏡で見ることはしていません。Sidekiqはいつも素晴らしく、私たちの業務で使う分には対価を払う価値があります。

関連記事

Rails: Active Jobスタイルガイド(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。