Sidekiq でジョブの同時実行数を制限する

イタンジ株式会社の清水です。「申込受付くん」という賃貸物件への申込みをWEBで完結させるサービスを開発しており、最近は主にRailsを触っています。趣味はキャンプです。

さて、みなさまはRailsで非同期ジョブを実行する仕組みをどう構築されているでしょうか。私のチームではSidekiq(Enterprise版)を利用しています。

とある非同期ジョブの開発で、処理が重く時間を要する場合もあるため、負荷の平準化の観点から同時実行数を絞りたいという要件がありました。その際に取り組んだことをまとめます。

同時実行数制限で採用した方法

Sidekiq Enterprise にて提供されている、RateLimiter Concurrent を利用しました。(せっかくEnterprise版使ってるので)

※ちなみに無料版をお使いの場合は、sidekiq-throttled のSidekiq拡張gem使う方法がありそうです。

使い方

公式ドキュメントのままなのですが、以下のようにLimiterを定義し、ブロック内で処理を行うことでその処理の同時実行数を制限できます。

Limitter = Sidekiq::Limiter.concurrent(
  limiter_name, # リミッターの名前
  2, # 同時並列実行可能数
  wait_timeout: 0, # ロックされた場合の待機時間
  lock_timeout: 3.minutes # ロックを継続する時間
)

def perform
  Limitter.within_limit do
    # 同時実行数制限をしたい処理...
  end
end

もし同時実行制限に達している場合に同種のジョブが実行されると、以下のような挙動となるようです。

  • ジョブ実行後 wait_timeout まで実行中のまま待機(ブロック内の処理は実行されない)
  • wait_timeout までにロックが解放されていなければリスケジュールされて「予定」に入り、後ほど再実行されます。ロックが解放されていればそのまま処理続行となります。

また、以下のようにSidkiqコンソールからも状況を見れるようになります。ちなみにこれは上記のサンプルコードのジョブを4つ一気にエンキューした結果です。同時実行数2なので、2件が実行され、wait_timeoutが0なので残り2件は即座にリスケジュールされ、予定に入っています。

実装上気を付けたこと

簡単な設定で同時実行数を制限できるのでとても便利なのですが、運用する上では以下の点に気を付けないといけないなと思いました。

lock_timeoutの設定

ジョブの想定完了時間を見積もって十分な長さで設定しておく必要がありそうです。lock_timeout は処理がクラッシュしたときなどにロックを永遠に保持してしまわないように制限時間を設けるための設定です。

lock_timeoutが短すぎると、並列で最大数までジョブが実行中にもかかわらず、ロックが不適切なタイミング解放され、上限を超えて並列実行されてしまいます。

wait_timeoutの設定

出来る限り短くした方が良さそうです。wait_timeoutは 上限数を超えてジョブが実行された際、ロックの解放をどれだけ待機するかの設定です。場合によっては0 でも良いかと思います。

出来る限り短くしておかないと、上限を超えたジョブは一旦実行中の状態で wait_timeout まで待機するため、アイドル状態のジョブで長期間多くのスレッドを占有することなりかねません。

UX上の問題

実行の頻度に対して同時実行可能数が少ないと、状況によってはロック解放までの待ち時間が長くなり、処理完了まで時間がかかってしまいます。ユーザーからのリクエストをフックとして実行されるようなジョブの場合、完了までの時間が長いのも問題ですし、もし仮に条件次第で失敗として処理しないといけないジョブである場合、待たされた挙句結局失敗するという体験になってしまいます。

上記の問題に対して、「申込受付くん」上ではロック中はそもそも実行リクエストを受け付けず、しばらく経った後の再実行をお願いするメッセージを出すようして対処しています。現在の残り実行可能数は以下のようにすれば取得できるので、これが0の場合は混雑中メッセージを返し、ジョブを実行しない制御にしています。

# lmtr-c- は固定値。limiter_name はLimiter登録時の第一引数。
Sidekiq::Limiter::Status.new("lmtr-c-limiter_name").available

終わりに

以上、RateLimiterを用いたSidekiq ジョブの同時実行数制御に関して簡単にまとめてみました。特に現在の残り実行可能数を取得する方法が調べても見つけられず、コンソールでは表示されてるんだからと、ソースコードを潜って特定したので備忘も兼ねてブログネタにしてみました。この記事をご覧の方の開発の一助になれば幸いです。それでは!