🐕

[Ruby on Rails] Sidekiq でキューに重みをつける方法

2022/09/29に公開

はじめに

初めまして。電話自動応答サービスIVRyでエンジニアをやっている小瀬です。
https://twitter.com/ats312kose

IVRyではRailsを中心にバックエンドのコードを書いています。
https://ivry.jp/

開発中に溜まった知見などをzennブログの方に書いていこうと思っていますので、よろしくお願いします。

Sidekiqとは

SidekiqはJobの並列実行を簡単に実現してくれるgemです。
https://github.com/mperham/sidekiq

Jobが並列数を超えていた場合もキューに詰めて処理してくれるのでかなり便利です。

やりたかったこと

Job(Workerとも呼ぶが、この記事ではJobに統一)の種類が多くなり、ユーザー影響があり早く処理したい「優先度の高いJob」と、特に急ぎではないけど1日のどこかで実行できればいい「優先度の低いJob」が存在していました。
先に優先度の低いJobを大量にキューに詰めた場合、デフォルト設定のままSidekiqを使用すると、優先度の高いJobが優先度が低いJobの後に実行されてしまいます。

これを、優先度の高いJobを優先的に処理されるようにしたい、というのがやりたかったことです。

キューの優先度の付け方

早速実装の話ですが、実はこれめちゃくちゃ簡単に実現できて感動しました。(ドキュメント見つけるのに時間がかかってしまいましたがw)
ドキュメントのリンクも貼っておきます
https://github.com/mperham/sidekiq/wiki/Advanced-Options

sidekiq.yml に下記の設定を加えるだけです

:queues:
  - ['high_priority', 2]
  - ['low_priority', 1]

high_priority の部分は単にキューの名前です。
2つ目の引数(index) に書いている、21 の部分がキューの重みです。
公式ドキュメントには下記のように書いています。

By default, sidekiq uses a single queue called "default" in Redis. If you want to use multiple queues, you can either specify them as arguments to the sidekiq command, or set them in the Sidekiq configuration file. Each queue can be configured with an optional weight. A queue with a weight of 2 will be checked twice as often as a queue with a weight of 1:

この設定を書くだけで、重みがついたキューを優先的に実行してくれました!
Sidekiq便利すぎますね。

default は付けた方が安心!

こんなミスは僕以外しないかもれませんが
ロジック側を実装する時に

class SampleJob
  include Sidekiq::Job

  sidekiq_options queue: :high_priority

  def perform
    .
    .
  end
end

このように、 sidekiq_options queue: :high_priority と書くことでキューを指定できます。
実は、この sidekiq_options queue: :high_priority の部分の行は省略することができます。
ただし、その場合 default という名前のキューでJobが登録されます。

このせいで、あれ?なんでJobが動かないんだ?ということになりかなりハマりました。
ですので、

:queues:
  - ['high_priority', 3]
  - ['default', 2]
  - ['low_priority', 1]

とするのが安全かと思います。

どのような順番で実行されるのか実験

この重みづけでSidekiqの実行順序がどのように変化するのか実験してみます。

重みは、high_priorityを3、low_priorityを1で実行します。

:queues:
  - ['high_priority', 3]
  - ['low_priority', 1]

同時実行数は5に設定しました。

:concurrency: 5

実行するプログラムはputssleep(10)するだけのJobです。

class HighPriorityJob
  include Sidekiq::Job

  sidekiq_options queue: :high_priority

  def perform(order)
    puts "#{order}番目のHIGH PRIORITY JOB"
    sleep(10)
  end
end

class LowPriorityJob
  include Sidekiq::Job

  sidekiq_options queue: :low_priority

  def perform(order)
    puts "#{order}番目のLOW PRIORITY JOB"
    sleep(10)
  end
end

上記のJobに対して、まず最初にlow_priorityのJobを20個
その後にhigh_priorityのJobを20個実行します。

20.times do |order|
  LowPriorityJob.perform_async(order + 1)
end
sleep(1)
20.times do |order|
  HighPriorityJob.perform_async(order + 1)
end

実行結果は下記のようになりました。
並列実行なので順番をうまく並べられず見にくくてすみません(sleepとかでうまくできたのかも...)

4番目のLOW PRIORITY JOB
2番目のLOW PRIORITY JOB
1番目のLOW PRIORITY JOB
5番目のLOW PRIORITY JOB
3番目のLOW PRIORITY JOB
# (5並列でJob実行中...)
2番目のHIGH PRIORITY JOB
1番目のHIGH PRIORITY JOB
4番目のHIGH PRIORITY JOB
3番目のHIGH PRIORITY JOB
6番目のLOW PRIORITY JOB
# (5並列でJob実行中...)
5番目のHIGH PRIORITY JOB
9番目のHIGH PRIORITY JOB
6番目のHIGH PRIORITY JOB
8番目のHIGH PRIORITY JOB
7番目のHIGH PRIORITY JOB
# (5並列でJob実行中...)
11番目のHIGH PRIORITY JOB
12番目のHIGH PRIORITY JOB
10番目のHIGH PRIORITY JOB
13番目のHIGH PRIORITY JOB
7番目のLOW PRIORITY JOB
# (5並列でJob実行中...)
14番目のHIGH PRIORITY JOB
15番目のHIGH PRIORITY JOB
16番目のHIGH PRIORITY JOB
17番目のHIGH PRIORITY JOB
18番目のHIGH PRIORITY JOB
# (5並列でJob実行中...)
19番目のHIGH PRIORITY JOB
20番目のHIGH PRIORITY JOB
9番目のLOW PRIORITY JOB
10番目のLOW PRIORITY JOB
8番目のLOW PRIORITY JOB
# (5並列でJob実行中...)
11番目のLOW PRIORITY JOB
12番目のLOW PRIORITY JOB
15番目のLOW PRIORITY JOB
13番目のLOW PRIORITY JOB
14番目のLOW PRIORITY JOB
# (5並列でJob実行中...)
16番目のLOW PRIORITY JOB
17番目のLOW PRIORITY JOB
20番目のLOW PRIORITY JOB
19番目のLOW PRIORITY JOB
18番目のLOW PRIORITY JOB

low_priorityも実行されていますが、後からキューイングしたhigh_priorityの方が多く実行され
8番目の low_priorityよりも早く20番目のhigh_priorityが実行されていることがわかります。
同時実行は同着と判定しても、11番目より早く実行されています。

狙い通り、キューに重みを付けることに成功していますね!

最後に会社の宣伝

IVRyには壁もありますし
https://note.com/u16m/n/n3b31ede1065e
めちゃくちゃいいスピーカーもあります
https://note.com/kose_atsuya/n/n6889336dd3d7

そしてなにより事業もすごいスピードで伸びています!
切実に人が足りません!笑
IVRyで働くことに興味ある方、是非ご連絡ください!

https://ivry-jp.notion.site/IVRy-e1d47e4a79ba4f9d8a891fc938e02271

IVRyテックブログ

Discussion