【Ruby on Rails】 Sidekiq Proを既存のシステムに導入してみた

広告事業本部でリードアプリケーションエンジニアの内原です。
今回は以前保守運用していた社内サービスのライブラリを別のライブラリに移行した話をしていこうかと思います。
私たちのチームでは、フレームワークであるRuby on Railsを利用しバックグラウンドキューのライブラリResqueを用いて非同期に処理を行うようなWebアプリケーションの保守・運用をしています。 弊チームではResqueに関係するライブラリを複数利用していますが、我々が扱うフレームワークの最新バージョンに対応していないなどの問題があり、それらのライブラリを自分たちでForkしてメンテナンスしている状態でした。コードのメンテナンス性や運用面でも課題を感じており、この機会に別のバックグラウンドキューのライブラリを導入してみようという話が上がったのがきっかけです。

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.

Github からの引用になりますが、Sidekiqとは、シンプルで効率的なRubyのバックグラウンド処理であり、スレッドを用いて同じプロセスで同時に多くのジョブを処理する とあります。 Sidekiqはgemライブラリで提供されるため、 Rubyシステム・Ruby on RailsのWebアプリケーションどちらでも利用することが可能です。我々のチームではRuby on RailsのWebアプリケーションに導入を行いました。

バックグラウンドキューシステムとは

何かしらの処理を即時に処理するのではなく、後で処理するためにキュー(待ち行列)に追加するシステムのことです。メインで動いているアプリケーションのプロセスを利用しないため、リソースを効率よく使用できるだけでなく時間のかかる重めの処理に対しても長時間レスポンスを待たなくても非同期で処理することができます。

Sidekiq Proとは

Sidekiqには複数のプランが存在します。バックエンドキューシステムだけではなく、強力な機能が増え様々なユースケースに対応できるようになっています。 今回は実際にシステムに導入することになったSidekiq Proについて触れていきます。

Sidekiq let us stop worrying about queues and focus on our app. Half a billion jobs later and it’s holding up great

公式ページによると、Sidekiqよりバックエンドキューシステムを気にする必要がなくアプリ開発に集中できると紹介がありました。 主な機能は以下になります。(2024/07/31時点)

  • BatchesとCallbackにより複雑なジョブワークフローを実現できる
  • Ruby VMがクラッシュした時のサーバーの信頼性を向上
  • Redisのネットワーク接続に問題があった時、クライアントの信頼性の向上
  • キューの一時停止
  • デッドラインに到達した実行していないジョブを削除させる

まだまだ他にもSidekiq Proへアップグレードすることで様々な恩恵が受けられます。また導入実績のある企業がたくさんある点もすごく信頼できますね!

環境

今回Sidekiqを導入した環境は以下の通りです。

  • Ruby 3.2.2
  • Ruby on Rails 7.0.4
  • Redis 6.2.6

導入手順

Sidekiq Proプランの導入について紹介をします。事前にSidekiq Proのライセンスを購入しておいてください。
ライセンスを購入したらメールにてクレデンシャルが渡されると思います。
gemライブラリをインストールする際に、メールで配布されたクレデンシャルを設定しないとインストールできないかと思います。 bundlerのReference通りにクレデンシャルの設定を行います。方法は2通りあります。

  • bundle configに設定
bundle config gems.contribsys.com <メールで配布されたcredential>
  • 環境変数に設定
export BUNDLE_GEMS__CONTRIBSYS__COM=<メールで配布されたcredential>

1.ライブラリのインストール

Sidekiq ProをRuby on Railsに導入するために既存のGemfileに sidekiq-pro を追加します。

source 'https://gems.contribsys.com/' do
  .
  .
  .
  gem 'sidekiq-pro'
  .
  .
  .
end

bundle install を行い、 sidekiq-pro をインストールします。

2.初期設定

Sidekiqクライアントではジョブの管理や履歴などのデータ保持にRedisを利用するため、Redisへの接続の設定を行います。
Redisのセットアップの通りに設定していきます。 config/initializers/sidekiq.rb を作成し、以下のように設定します。redisへのurlは適宜ご自身の設定に合わせてください。

Sidekiq.configure_server do |config|
  config.redis = { url: 'redis://redis.example.com:7372/0' }
end

Sidekiq.configure_client do |config|
  config.redis = { url: 'redis://redis.example.com:7372/0' }
end

Redisのセットアップが終わりましたら 次にSidekiqの標準機能の1つであるWeb UIについて設定を行います。
config/routes.rb に以下のようにページにルーティングできるようにします。

require 'sidekiq/pro/web'
Your::Application.routes.draw do
  mount Sidekiq::Web => '/sidekiq'
  ・
  ・
  ・
end

Sidekiqのクライアントを立ち上げ、 localhost:30000/sidekiq にアクセスすると下記のようなSidekiqのダッシュボードが確認できるかと思います。

Batches機能の紹介

Some businesses upload a lot of Excel spreadsheets to load data into their database. These spreadsheets might have hundreds of rows, each row requiring a few seconds of processing. I don't want to process the file synchronously (the web browser will time out after 60 seconds) and I don't want to spin off the upload as a single Sidekiq job (there's no performance benefit to serial execution). Instead I want to break up the Excel spreadsheet into one job per row and get the benefit of parallelism to massively speed up the data load time. But how do I know when the entire thing is done? How do I track the progress?

公式によるとBatchesは、Sidekiqのジョブをグループとして監視できるようになり、グループ内の全てのジョブが完了・失敗などのステータスにより後続の処理を実行することができる機能です。
公式にある通り、この機能はSidekiq Proのライセンスがないと利用できません。

Batches機能の事例

Batchesを採用したシステムのユースケースとしては、

  1. 広告データを各媒体・各案件ごとに非同期処理で取得
  2. 取得されたデータを別システムに渡し、データを整形

非同期処理(Sidekiq)の際、各媒体は案件単位でAPI取得を行います。しかし、2の整形を行う際は案件ごとではなく媒体ごとになります。

媒体ごとにデータ取得が終わったら整形システムにデータを渡したいのですが、現状のシステム(Ruby on Rails)に採用しているバックグラウンドキューライブラリのクライアントではどのジョブとどのジョブが媒体Aから取得してきたデータなのかがわからないため自前で状態管理の実装を行なっていました。
そこで、Sidekiq ProのBatches機能を利用することでこのユースケースに変更を加えることなく、また自前で実装していた状態管理も責任をBatches機能に持たせることでメンテナンス性を高めることができました。 Sidekiq Proを選定した理由の1つとして、このようにBatches機能を有効活用できる点が挙げられます。

Batchesの実装例

batches機能による実装の流れは以下の通りになると思います。

1. Batchインスタンスを作成
2. Batchインスタンスにコールバックを設定
  2.1 Batchに登録した全てのジョブの実行が成功したら〇〇を実行して欲しい
  2.2 Batchに登録した全てのジョブの実行が完了したら〇〇を実行して欲しい
  2.3 Batchに登録した全てのジョブのうち1つでも失敗したら〇〇を実行して欲しい
3. Batchインスタンスにジョブを登録

公式では下記のように紹介しています。

batch = Sidekiq::Batch.new # 1. Batchの作成
batch.description = "Batch description (this is optional)"
batch.on(:success, MyCallback, :to => user.email) # 2. Batchにコールバックを登録。ここでは2.2の成功時のみのコールバックを登録
batch.jobs do # 3. Batchにジョブを登録
  rows.each { |row| RowJob.perform_async(row) }
end
puts "Just started Batch #{batch.bid}"

私たちのチームのシステムのユースケースでは以下のようなイメージで実装を行いました。

全媒体のModel.each do |媒体|
  batch = Sidekiq::Batch.new
  batch.description = "#{媒体}の全案件取得グループ"
  batch.on(:success, 'CallbackClass#on_success', id: 媒体.id)
  batch.on(:death, 'CallbackClass#on_death', id: 媒体.id)

  batch.jobs do
    媒体.全案件.each do |案件|
      RequestJob.perform_async(案件)
    end
  end
end

# -------------------
# Batchに登録したコールバックの具体的な処理内容
class Callback
  def on_success(status, options)
    # 後続の整形に媒体〇〇の全ての取得が終わったことを伝える処理
    something_request(options[:id])
  end

  def on_death(status, options)
    # 媒体の中で何かしらの案件の取得が失敗したのでslack通知
    send_slack(options[:id])
  end
end

まとめ

今回はSidekiqの導入とSidekiq Proのライセンスで利用できるBatchesの紹介でした。ResqueにもありましたがSidekiqのWebUIはグラフィカルになっており、Deadになったジョブ一覧で検索を行うことができてとても便利な印象でした。また、これらのUIのセットアップもすごく簡単で0から素早くアプリケーションを作ることができるRuby on Railsとの相性も良いなと感じました。
Batchesの機能では、今まで自前で実装していた「複数ジョブのグルーピング機能」を手放すことができました。グルーピング機能の責任をSidekiq内に包括しているためメンテナンスコストが下がることが期待できそうです。私たちのフロー例ではそこまで複雑ではありませんでしたが、システムが肥大化して複雑なワークフロー処理を行いたい場合はぜひSidekiq Proライセンスを購入しBatchesを使ってみてください。

参考記事

Sidekiq ホームページ
Sidekiq Github
Sidekiq Wiki