Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsのバックグラウンドジョブ処理はどちらを選ぶ?Delayed JobとSidekiq徹底比較

メール送信、定期的なクリーンアップ処理など、ユーザーの待機を必要としない時間のかかる操作には、ほとんどのアプリケーションでバックグラウンドジョブが欠かせません。

Railsの世界には、ジョブキューとバックグラウンド処理をサポートするGemが複数存在します。中でも特に人気が高いのが「Delayed Job」と「Sidekiq」の2つです。

この記事では、Delayed JobとSidekiqについて詳しく解説し、両者をさまざまな観点から比較していきます。

それでは始めましょう!

Delayed Jobとは

Delayed JobはShopifyのコードベースから抽出されたライブラリで、データベースのテーブルを使ってすべてのバックグラウンドジョブを管理します。performメソッドに応答する任意のRubyオブジェクトをジョブテーブルに登録できる、非常にシンプルな設計になっています。

さらに、専用のジョブオブジェクトを作りたくない場合(ただし、テスト容易性や長時間処理の明確な分離の観点からは専用クラスの作成が強く推奨されます)は、任意のRubyオブジェクトに対して.delay.method(params)を呼び出すことも可能です。これにより、指定したメソッドがバックグラウンドで実行されます。

一般的な使用パターンの詳細については、Delayed JobのREADMEが非常によくまとまっています。

多くのチームがDelayed Jobを選ぶ理由は、そのシンプルさと、既存のデータベースをそのまま活用できる点にあります。新たなインフラを用意・管理する手間がかかりません。

ただし、ジョブはデータベーステーブル内のスペースを消費します。同時に大量のジョブがキューに溜まった場合、それらを格納するためにより多くのディスク容量が必要になる可能性がある点には注意が必要です。

Sidekiqとは

一方のSidekiqは、Redisをデータストアとして採用し、すべてのジョブメタデータを管理します。Delayed Jobが使用するような一般的なRDBMSよりもはるかに高速に動作するのが大きな利点です。加えて、各Sidekiqプロセスは複数のスレッドを生成してジョブを並列処理するため、さらなる高速化が実現されています。

Sidekiqでバックグラウンドジョブを作成するには、Sidekiq::Workerモジュールをincludeし、performメソッドを持つ専用クラスを定義します。ジョブをエンキューするには、ワーカーに対してperform_async(arg1, arg2)を引数付きで呼び出します。

Sidekiq公式の「Getting Started」ガイドでは、これらの基本的な使い方をはじめ、さまざまなパターンが丁寧に解説されています。

Active Jobとの併用

Railsには、ジョブの宣言やハンドリングを統一的に行うための成熟したフレームワーク「Active Job」が標準で備わっています。Delayed JobとSidekiqはどちらも、Active Jobの統一APIを通じたジョブ実行をサポートしています。ApplicationJobを継承したジョブクラスに対してperform_laterを呼び出すだけで、設定されたキューバックエンドにジョブが投入されます。

Active Job経由でジョブを実行するメリットは、アプリケーションコードが特定のフレームワークに依存しなくなることです。これにより、Delayed JobからSidekiqへの移行(またはその逆)が非常に簡単になります。また、ActiveJob::TestHelperを活用すれば、エンキューされたジョブのテストも簡単に行えます。

ただし、Active Jobによる抽象化にはパフォーマンス上のオーバーヘッドも伴います。ストアにデータを送る前にジョブ情報をラップする必要があるためです。Sidekiqの公式見解によると、Redisへの書き込み時に約2〜20倍遅くなり、処理オーバーヘッドは約3倍になるとされています。

Delayed Job vs. Sidekiq

ここまでで両者の基本を押さえたところで、次はそれぞれの違いと特徴をより深く掘り下げていきましょう。

機能面の比較

基本的な用途であれば、SidekiqもDelayed Jobも標準で十分な機能セットを備えています。具体的には、ジョブの優先度設定、名前付きキュー、失敗時の自動リトライなどです。

Delayed Jobは、最大実行時間(max run time)の設定にも標準で対応しています(こちらはSidekiqにはありません)。

一方のSidekiqは、ジョブメタデータの更新、キューイングのスキップ、ジョブ実行の制御などを行えるミドルウェアをサポートしている点が強みです。Sidekiqの方が多くのコールバックを提供していますが、Delayed Jobでも一部のフックは利用可能です。また、コールバックの代わりに、Delayed JobをActive Jobと組み合わせれば、Rails組み込みのbefore_enqueuearound_performといったコールバックも使えます。

Web UIも、Sidekiqが標準で提供している機能の一つです。ジョブの履歴統計、ワーカー情報、キューに入っているジョブやデッドジョブの状態を確認でき、コンソールを開かなくてもジョブの削除や即時実行といった操作が可能です。

Delayed Jobには組み込みのWeb UIはありませんが、delayed_job_webを導入すれば、Sidekiqとほぼ同等の基本機能を持つシンプルなWeb UIを利用できます。

パフォーマンスはSidekiqが圧倒的に有利

パフォーマンスの観点では、SidekiqがDelayed Jobを大きく上回ります。Sidekiqの公開ベンチマークによると、その速度はDelayed Jobの約30倍です。主な理由は以下の2つです。

  1. Redisはディスクではなくメモリ上にデータを保持するため、Postgresのような従来型データベースよりもはるかに高速にデータを照会できます。
  2. Delayed Jobは単一スレッドでジョブを処理するのに対し、Sidekiqはマルチスレッドで並列処理を行います。

数字だけ見ると魅力的ですが、大規模な運用(目安として毎分1万件程度のジョブ)でない限り、この差が実際の問題になることは少ないでしょう。実際の差はジョブの平均実行時間にも左右されます。1件あたりの実行時間が長いほど、Delayed Jobのオーバーヘッドは相対的に小さくなります。

Delayed Jobのパフォーマンスが気になる場合は、いくつか最適化の手立てがあります。追加すべきインデックスはジョブシステムの統計情報によって異なります。たとえば、複数のキューを使っており特定のキューにジョブが集中しているなら、queueカラムへのシンプルなインデックス(add_index :delayed_jobs, :queue)を追加するだけで、パフォーマンスが大幅に改善することがあります。

AppSignalを利用している場合、Sidekiq用のマジックダッシュボードが自動生成され、キューの長さ、キューレイテンシ、ジョブの実行時間、ジョブのステータス、メモリ使用量などを継続的に監視できます。

Railsのバックグラウンドジョブ処理はどちらを選ぶ?Delayed JobとSidekiq徹底比較

デプロイ

Delayed JobとSidekiqは、ワーカーのデプロイ手法もよく似ています。Herokuであれば、Procfileにジョブプロセッサを起動するエントリを追加し、ワーカーを起動するだけでOKです。

Sidekiqの場合:


Delayed Jobの場合:


メモリ使用量

ここからが少し興味深いポイントです。Sidekiqには、実行スレッド数を制御するconcurrencyオプションがあります。Sidekiq対Delayed Jobのベンチマークの多くは、最大25スレッドという高い並列性がSidekiqの超高速性能を支えていると述べています。

しかし、実際の運用環境では、スレッド数はもっと控えめに設定する必要があります。適切な値はアプリケーションの負荷や扱うジョブの種類によって異なります。筆者の経験では、512MBのメモリ(Herokuのstandard-1x相当)でワーカーを動かす場合、現実的なスレッド数は25ではなく2〜5程度でした。

Sidekiqの作者Mike Perham氏による「Taming Rails memory bloat」は、メモリ問題についてより深く掘り下げており、一読の価値があります。詳細な議論はそちらに譲りますが、同氏はSidekiqを動かすすべてのワーカーでMALLOC_ARENA_MAX=2を設定することを推奨しています。

また、通常のmallocの代わりにjemallocを使うのも効果的です。導入方法はプラットフォームによって異なりますが、Herokuであれば簡単です。heroku-buildpack-jemallocを最初のビルドパック(heroku/rubyビルドパックより前)として設定するだけです。

Delayed Jobはシンプルなリソース構成で済む

前述のとおり、Delayed Jobは既存のデータベースインスタンス上で動作します。ジョブの負荷やワーカー数に応じて、以下のリソースを増強する必要が出てくるかもしれません。

  • 利用可能なメモリ
  • ディスク容量
  • 最大接続数

ただし、新たに必要となるリソースはジョブプロセッサのみです。

一方、Sidekiqはジョブ処理のためにRedisインスタンスが必要です。キャッシュストアとしてもRedisを使っている場合は、Sidekiqジョブ専用に「永続ストア」として設定された別インスタンスを用意することが推奨されます。

Redisはすべてのデータがメモリに収まる場合に最高のパフォーマンスを発揮します。そのため、ジョブが大量に滞留すると(たとえば、アプリ側の不具合でSidekiqが一定期間ジョブを処理できなくなった場合など)、解消までにダウンタイムが必要になることがあります。Redisがアプリと同じサーバー上にある場合、これは特に厄介です。メモリを奪い合うことでスワッピングが発生し、最終的にはアプリ全体のパフォーマンスを損ないかねません。

Redisに関してもう一つ重要な注意点があります。Sidekiqのデータがサイレントに破棄されるのを防ぐため、maxmemory-policy noevictionを設定しておかなければなりません。これを怠ると、実行されるはずだったジョブが痕跡もなく消失することになります。

補足:Sidekiqの有料プラン

さらに高度な機能が必要な場合は、SidekiqのPro版とEnterprise版が用意されています。

Pro版で最も注目すべき追加機能が「Batch Jobs」です。ジョブ群を並列実行し、グループとして監視・連携させ、全ジョブの完了時にコールバックを起動できます。Pro版には、ネットワーク障害発生時でもジョブがサイレントに失われないよう保証する、信頼性向上機能も含まれています。

Enterprise版にはさらに多くの機能が搭載されています。標準のSidekiqでは解決できない課題を抱えている場合は、有料機能を検討してみるといいでしょう。

実際のところ、無料版のSidekiqでも十分に実用的です。ただし、別のソリューションへ乗り換えるのではなく、必要になった時点で有料オプションへアップグレードできるという道が開いていることは知っておいて損はありません。

コミュニティと開発状況:Sidekiqが優勢

SidekiqにもDelayed Jobにも大きなコミュニティが存在します。ただし、Stack Overflowや公式ドキュメントで疑問への迅速な回答を見つけられるとは限りません。

開発の面では、Delayed Jobの先行きはあまり明るくありません。2021年12月〜2022年1月にいくらかの修正が行われたものの、今後大きな開発が進む気配はありません。事実上メンテナンスモードに入っており、GitHubには多数の未解決Issueが残されたままです。

対照的に、Sidekiqは現在も活発に開発が続いており、作者自身がフルタイムで取り組んでいます。未解決のIssueはごくわずかで、定期的に対応されています。

まとめ:SidekiqかDelayed Jobか?要件次第

この記事では、Railsアプリケーション向けの主要なジョブ処理システムであるSidekiqとDelayed Jobについて、それぞれの長所と短所を確認しました。

それぞれに適したユースケースがあり、最終的には予算と運用規模によって判断すべきです。

パフォーマンスと長期的な保守性を重視するなら、Sidekiqが間違いのない選択です。一方、ランニングコストを抑えたいのであれば、Delayed Jobが有力な候補になるでしょう。

Delayed Jobを選ぶにせよSidekiqを選ぶにせよ、あなたのプロジェクトの成功と快適なコーディングライフをお祈りしています!Ruby(Rails)のパフォーマンス改善に関心があれば、パフォーマンス監視チェックリストの他の記事もぜひ参考にしてください。

  1. Ruby on RailsでAction Policyをマスターする:安全な認可のための実践ガイド(前編)

    アプリケーションを安全に保つためには、誰が・何に対してアクセスできるのかを制御する必要があります。アクセス制御は、「誰を許可するか」を扱う認証(Authentication)と、「何にアクセスできるか」を扱う認可(Authorization)の2つに分類できます。 認証の詳細は別の機会に譲るとして、ユーザーの認可については、一般的に「ロールベース」と「リソースベース」の2つのアプローチがあります。 本記事は2部構成のシリーズで、Ruby on Railsのブログアプリケーションを題材に、Action Policy gemの使い方を深く掘り下げて解説します。この前編では、Action Polic

  2. AppSignalでDigitalOcean上のRuby on Railsアプリを監視する:初心者向け完全ガイド

    想像してみてください。新しいRailsアプリの開発を終え、DigitalOceanのようなクラウドプロバイダーにデプロイしたばかりのところです。開発者であれば誰でも、自分の作品に誇りを感じる一方で、次のような不安や疑問が残るものです。 新しいアプリはトラフィックにどれくらい耐えられるのか 実装した最適化が実際に機能するのかどうか 目標は、最高のユーザー体験を提供することです。エラーや重要なイベントが発生した際にすぐ通知を受け取り、迅速に対処したいと考えるのは自然なことでしょう。 そんなときに役立つのが、アプリケーションを自動的に監視してくれる仕組みです。そこで登場するのがAppSignal