0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Sidekiqのワーカーは本当にRedisを0.1msごとにポーリングしているのか?BRPOPの仕組みを理解する

Posted at

はじめに

Sidekiqを使ったバックグラウンドジョブ処理を実装していると、「ワーカーはどうやってRedisからジョブを取得しているんだろう?」と疑問に思うことはありませんか?

よくある誤解として「ワーカーが定期的にRedisにジョブがあるかチェックしている」というものがありますが、実際は全く違う効率的な仕組みが使われています。

誤解されがちなポーリング方式

多くの人が想像するワーカーの動作:

# 間違った理解(実際はこうではない)
loop do
  job = redis.get("queue:default")
  if job
    execute_job(job)
  else
    sleep(0.001) # 0.1ms待機
  end
end

この方式だと以下の問題があります:

  • 🚫 CPU使用率が高い: 常にRedisに問い合わせ
  • 🚫 ネットワーク負荷: 無駄な通信が大量発生
  • 🚫 レスポンス遅延: ポーリング間隔分の遅延が発生
  • 🚫 リソース無駄遣い: ジョブがなくてもCPU・ネットワークを消費

実際のSidekiq: BRPOP(Blocking Right Pop)

SidekiqはBRPOPというRedisコマンドを使用しています。

BRPOPとは?

BRPOP queue:default 5
# queue:defaultからジョブを取得
# ジョブがない場合は最大5秒間ブロック(待機)

実際の動作フロー

パターン1: ジョブが追加された場合

  1. ワーカーがRedisに BRPOP queue:default 5 を送信
  2. Redisはジョブがない場合、接続を保持したまま待機状態に
  3. 他のプロセス(Railsアプリなど)がジョブをキューに追加
  4. Redis が待機中のワーカーに即座にジョブデータを返却
  5. ワーカーがジョブを実行
  6. ジョブ完了後、再度 BRPOP queue:default 5 で次のジョブを待機

パターン2: タイムアウトの場合

  1. ワーカーがRedisに BRPOP queue:default 5 を送信
  2. 5秒間ジョブが追加されない
  3. Redis が nil を返してタイムアウト
  4. ワーカーは即座に再度 BRPOP queue:default 5 を実行
  5. 再び最大5秒間待機状態に

BRPOPの特徴

1. 持続的な接続

ワーカー ────────────── Redis
         ^この接続を維持
         (ポーリングとは違い、接続したまま待機)

2. リアルタイム性

# アプリケーション側
MyWorker.perform_async(user_id)  # ジョブをキューに追加

# ↓ 即座に(0.1ms後とかではなく)

# ワーカー側
# BRPOPで待機中のワーカーが即座にジョブを取得して実行開始

3. 複数ワーカーでの競合回避

# 3つのワーカーが同じキューを監視
ワーカー1: BRPOP queue:default 5  # 待機中
ワーカー2: BRPOP queue:default 5  # 待機中  
ワーカー3: BRPOP queue:default 5  # 待機中

# ジョブが1つ追加されると...
# → Redis が自動的に1つのワーカーにのみジョブを渡す
# → 他のワーカーは引き続き待機

実装レベルでの確認

Sidekiqの現在のソースコード(2025年時点)を確認すると、実際にBRPOPを使用していることが確認できます:

# sidekiq/lib/sidekiq/processor.rb
def get_one
  uow = capsule.fetcher.retrieve_work
  # ...
end

# sidekiq/lib/sidekiq/fetch.rb - BasicFetch クラス
def retrieve_work
  qs = queues_cmd
  if qs.size <= 0
    sleep(TIMEOUT)
    return nil
  end

  queue, job = redis { |conn| conn.blocking_call(TIMEOUT, "brpop", *qs, TIMEOUT) }
  UnitOfWork.new(queue, job, config) if queue
end

重要なポイント:

  • BasicFetch クラスの retrieve_work メソッドで conn.blocking_call(TIMEOUT, "brpop", *qs, TIMEOUT) を使用
  • TIMEOUT は 2秒に設定されており、2秒ごとにプロセス終了チェックを行う
  • Sidekiqの公式ドキュメントでも「Sidekiq uses BRPOP to fetch a job from the queue in Redis」と明記

パフォーマンス比較

方式 CPU使用率 ネットワーク負荷 リアルタイム性 実装複雑性
ポーリング 高い 高い 遅延あり 簡単
BRPOP 低い 低い 即座 適度

実際に確認してみる

Sidekiqの管理画面(/sidekiq/busy)で現在の状況を確認できます:

Processes タブ:
- 各ワーカープロセスの状態
- Busy: 現在実行中のジョブ数
- Threads: 使用スレッド数

Busy タブ:
- 現在実行中のジョブ詳細
- どのワーカーが何を実行中か

まとめ

Sidekiqのワーカーは:

ポーリングしない: 定期的なチェックは行わない
BRPOPを使用: Redisとの持続的接続で効率的にジョブを取得
リアルタイム処理: ジョブ追加と同時に処理開始
リソース効率: CPU・ネットワークリソースを無駄遣いしない

この仕組みにより、Sidekiqは高いパフォーマンスとスケーラビリティを実現しています。バックグラウンドジョブの設計時には、この効率的な仕組みを理解しておくことで、より良いアーキテクチャを構築できるでしょう。

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?