背景と目的
Rails8.0 ではActive Jobの機能が拡充されたことに加え、これまでの Sidekiq
や Resque
などに加えて、新たに Solid Queue
という選択肢が提示されました。しかしながら、Sidekiq の公式ドキュメントでは以下のような性能差が示唆されています:
- Active Job 経由と Sidekiq ネイティブ使用で 約1.2倍の差
- Solid Queue と比較すると 最大15倍の差
果たしてこれらの差は現実にどの程度再現されるのか? また、Solid Queue を使う場合に Sidekiq に並ぶ性能を発揮するにはどのような設定が必要なのか? OSS ライセンスの範囲内で各方式の性能を定量的に検証しました。
実験環境
実験環境としては筆者のPCにてDocker Desktopのコンテナを利用しました。
-
Ruby: 3.3.5
-
Rails: 8.0.2
-
Sidekiq: 8.0.3
-
Redis: 8.0.1
-
PostgreSQL: 17.5
-
mac mini 2018
- 3.2GHz 6-Core Intel Core i7
- 64GB 2667MHz DDR4
- macOS 15.4.1
-
Docker Desktop: 4.41.0
- CPU: 4 / Memory: 16GB / Swap: 1GB
測定対象
-
Sidekiq:
Sidekiq::Job
-
Active Job:
Active Job + SidekiqAdapter
-
Solid Queue:
Active Job + SolidQueueAdapter
測定シナリオ
測定シナリオとしてはジョブのEnqueueとPerformについてそれぞれ測定します。またEnqueueにおいては逐次処理とバルク処理、Performについてはスレッド数とプロセス数を変化させた場合の違いについても測定します。
Enqueue 編
モード |
件数パターン |
備考 |
逐次 |
100 / 1,000 / 10,000件 |
perform_async vs perform_later
|
バルク |
100x1 / 100x10 / 100x100 |
perform_bulk vs perform_all_later
|
モード |
件数 |
スレッド数 |
備考 |
単プロセス |
上記同様 |
5 / 10 |
各方式共通 |
複プロセス |
上記同様 |
5 x 2プロセス |
Solid Queue のみ(Sidekiq OSS 非対応) |
計測指標・方法
実行時の指標としてはメモリ使用量、CPU使用率、実行時間についてを下記の基準にて測定します。
指標 |
測定方法 |
ツール |
メモリ使用量 |
最大RSS(常駐メモリ) |
Docker stats |
CPU使用率 |
平均・最大 %CPU |
Docker stats |
実行時間 |
enqueue ~ 完了の経過時間 |
time |
測定結果
それでは実際の測定結果を見ていきましょう。
📎 ソースコード・検証用スクリプトは naoto-k/sidekiq-memory-test にて公開しています。
Enqueue
1件ずつ投入
メモリ使用量
件数 |
Sidekiq |
Active Job |
Solid Queue |
100 |
90.06MB |
91.59MB |
200.6MB |
1,000 |
88.08MB |
92.05MB |
201.6MB |
10,000 |
88.75MB |
91.85MB |
203.7MB |
CPU使用率
件数 |
Sidekiq |
Active Job |
Solid Queue |
100 |
N/A |
N/A |
72.57% |
1,000 |
N/A |
72.59% |
76.34% |
10,000 |
65.31% |
73.22% |
77.41% |
実行時間
件数 |
Sidekiq |
Active Job |
Solid Queue |
100 |
0.06s |
0.32s |
1.81s |
1,000 |
0.37s |
1.96s |
15.99s |
10,000 |
2.47s |
17.3s |
2m 34.16s |
考察
Sidekiqが圧倒的に高速であることが明確に現れた結果となりました。
Active Job経由では約5〜7倍、Solid Queueでは最大で60倍近い実行時間の差が見られました。
これはActive Jobがシリアライズ処理やラップ処理を内部で行うことによるオーバーヘッド、Solid QueueではRDBMSトランザクション制御によるレイテンシの影響が大きいと考えられます。
メモリ・CPUの消費はSolid Queueがやや高めで、パフォーマンスの代償としてシステムリソースを多く消費している様子が見て取れます。
バルク投入
メモリ使用量
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
91.75MB |
94.49MB |
208.4MB |
100x10 |
92.66MB |
95.04MB |
210.8MB |
100x100 |
95.37MB |
96.43MB |
212.5MB |
CPU使用率
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
N/A |
N/A |
N/A |
100x10 |
N/A |
N/A |
N/A |
100x100 |
N/A |
N/A |
66.43% |
実行時間
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
0.01s |
0.01s |
0.13s |
100x10 |
0.01s |
0.06s |
0.59s |
100x100 |
0.12s |
0.62s |
3.84s |
考察
バルク処理においては、Active JobとSidekiqの性能差が大きく縮まり、特に小規模なバルク(100件程度)ではほぼ同等の性能を示しました。
これは1件ずつエンキューする際の処理負荷が amortized(平準化)されるためと考えられます。
一方、Solid Queueはバルクでも処理負荷が高く、PostgreSQLを用いた複数行の INSERT 処理やロックの影響を受けやすいことが要因と思われます。
5スレッド
メモリ使用量
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
90.4MB |
98.2MB |
202.2MB |
100x10 |
90.44MB |
99.56MB |
207.7MB |
100x100 |
91.6MB |
100.2MB |
210.6MB |
CPU使用率
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
99.99% |
100.04% |
105.81 % |
100x10 |
100.49% |
101.78% |
106.01% |
100x100 |
101.34% |
103.22% |
110.19% |
実行時間
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
3.59s |
6.11s |
5.57s |
100x10 |
28.03s |
53.98s |
48.87s |
100x100 |
4m 13.03s |
9m 18.79s |
7m 33.98s |
考察
この構成ではSidekiqが引き続き優れた実行時間とメモリ効率を示しつつ、Solid QueueがActive Jobを上回る性能を示した点が注目されます。
特に中規模バッチ処理(100x10や100x100)ではSolid QueueがActive Jobよりも安定したスループットを発揮しており、適切な条件下では十分実用的であることが確認できました。
Active Jobについては、やはり非同期処理ラッパーとしての内部処理のオーバーヘッドが効いており、スレッド数が少ないほど相対的に不利になる傾向が見られます。
10スレッド
メモリ使用量
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
90.99MB |
94.92MB |
195.4MB |
100x10 |
91.95MB |
96.45MB |
207.6MB |
100x100 |
93.64MB |
96.56MB |
212.4MB |
CPU使用率
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
102.72% |
103.21% |
103.98% |
100x10 |
102.45% |
103.3% |
109.33% |
100x100 |
103.23% |
103.24% |
107.07% |
実行時間
件数 |
Sidekiq |
Active Job |
Solid Queue |
100x1 |
3.7s |
6.56s |
5.49s |
100x10 |
29.82s |
59.63s |
42.38s |
100x100 |
4m 44.26s |
9m 40.25s |
6m 59.32s |
考察
スレッド数を10に増やしたことで、Solid Queueのスループットがさらに向上し、Active Jobとの性能差が一層広がりました。
実行時間ではSidekiqが依然として最速ですが、Solid Queueとの差はわずかに縮まってきています。
CPU使用率はSolid Queueが最も高く、ワーカーリソースをより効率的に活用できていることを示しています。
メモリ使用量は増加していますが、全体としては許容範囲であり、スケール性の高さが評価されます。
5スレッド x 2プロセス / Solid Queueのみ
メモリ使用量
件数 |
Solid Queue |
100x1 |
255.9MB |
100x10 |
269.8MB |
100x100 |
276.2MB |
CPU使用率
件数 |
Solid Queue |
100x1 |
202.47% |
100x10 |
206.81% |
100x100 |
210.51% |
実行時間
件数 |
Solid Queue |
100x1 |
3.99s |
100x10 |
25.53s |
100x100 |
4m 2.4s |
考察
Solid Queueの真価が発揮されるのはこの構成です。
プロセスを分割してワーカーを分散させることで、CPU使用率は200%を超え、単一プロセス構成よりも約1.5倍のスループット向上が見られました。
メモリ使用量は当然ながら倍増しますが、それに見合った処理効率の向上が得られています。
OSS版のSidekiqがマルチプロセス非対応であることを踏まえると、Solid Queueのスケール性は大きなアドバンテージとなり得ます。
まとめ / ベストプラクティス
今回の検証により、性能面ではやはりSidekiqネイティブ利用が最も優れており、圧倒的な速度と軽量性を誇ることが改めて確認されました。
ただしSolid Queueも、スレッド数やプロセス数を適切に調整すれば十分に実用に耐える性能を発揮することがわかりました。
特に新規プロジェクトで Redis を導入せずに構成をシンプルにしたいケースでは、有力な選択肢となります。
Active Job + Sidekiq の構成は、既存のActive Jobベースのコード資産がある場合や、開発者の習熟度を考慮してSidekiqをラップしたい場合に適しており、妥協案として有効です。
一方で、Active Jobが持つ非同期処理のラップ処理やキュー管理の抽象化は、性能面ではオーバーヘッドとなるため、その点を考慮する必要があります。
ユースケース別推奨
ユースケース |
推奨構成 |
高性能・リアルタイム性が求められるサービス |
Sidekiq(ネイティブ) |
小規模〜中規模で構成を簡素にしたい場合 |
Solid Queue(1プロセスでOK) |
Active Jobベースの既存コードがある |
Active Job + Sidekiq Adapter |
高いスケーラビリティが必要なワークロード |
Solid Queue(マルチプロセス構成) |
Rails 8.0 時代における非同期ジョブの選択肢として、Solid Queue は 十分に戦える 存在になってきています。
運用面・構成面も踏まえて、最適な選択をしていきたいですね。
Discussion