Ruby on Rails

Sidekiqとは?Rails 8.1での導入・リトライ・Web UI設定【Sidekiq 8.1.7】

Sidekiqとは?Rails 8.1での導入・リトライ・Web UI設定【Sidekiq 8.1.7】

Sidekiqは、Rubyのジョブを1つのプロセス内の複数スレッドで並行処理するバックグラウンドジョブのgemです。ジョブはRedis(またはValkey・Dragonfly)に積まれ、Webアプリとは別に起動したSidekiqプロセスが取り出して実行します。2026年10月時点の最新版は2026年8月17日公開の8.1.7です。

記事中の出力は、Ruby 4.0.6・Sidekiq 8.1.7・Valkey 9.1.2・Active Job 8.1.3.1の最小構成で実行した結果です(UserMailerやroutesを含むRailsへの組み込み例は、アプリ側での確認を前提にしています)。メール送信やCSV生成をリクエストの外へ逃がしたいRails開発者向けに、導入から本番運用の判断までを扱います。

まとめ:Sidekiq 8.1の要点

  • 動作要件はRuby 3.2以上、Redis 7.0以上・Valkey 7.2以上・Dragonfly 1.27以上のいずれか。Rails・Active Jobは7.0以上に対応します。
  • 既定のconcurrency(スレッド数)は5です。Sidekiq 7.0で10から下がりました。
  • 失敗したジョブは既定で25回までリトライされます。待ち時間は「試行回数の4乗+15秒+ランダム」で、25回の基本待ち時間を合計すると約20.4日です。
  • リトライを使い切ったジョブはDead setへ移り、1万件か6か月のどちらかを超えると消えます。
  • Web UIはRailsのroutesでmountします。Rackセッションが無いと例外で落ちるため、APIモードのRailsではセッションの設定が要ります。
  • Rails 8の既定はSolid Queue(DB保存)です。Redisを運用できる体制があるか、Rails以外のRubyでも使うならSidekiqを選びます。キャッシュ用のRedisとは別のインスタンスを用意します。
  • 本体はLGPL-3.0で無料です。処理中のジョブをクラッシュで失わない仕組み(super_fetch)とBatchesはPro(月99ドル)の機能です。

Sidekiqの仕組み:Redisのリストとスレッドで処理する構造

Sidekiqの登場人物は3つです。ジョブを積む側(Railsアプリ)、ジョブを保存するRedis、取り出して実行するSidekiqプロセスです。perform_asyncを呼ぶと、クラス名と引数がJSONになり、Redisのqueue:defaultのようなリストに追加されます。Sidekiqプロセスのconcurrency本のスレッドが、それを1件ずつ取り出してperformを呼びます。

ジョブごとにプロセスをforkするResqueと違い、Sidekiqはスレッドを使い回すためメモリが少なく済む代わりに、ジョブのコードがスレッドセーフであることが前提です。クラス変数やグローバル変数に状態を書くジョブは、並行実行で値が混ざります。

項目 Sidekiq 8.1系の要件・既定値
最新版 8.1.7(2026-08-17)
Ruby MRI 3.2以上/JRuby 9.4以上
データストア Redis 7.0+/Valkey 7.2+/Dragonfly 1.27+
Rails・Active Job 7.0以上
concurrency 5
停止時の待ち時間(timeout) 25秒
リトライ上限 25回
Dead set上限 10,000件/6か月

RedisのフォークであるValkeyでもそのまま動きます。Valkey側の導入手順はValkeyとは?Redisとの違い・Streamsの使い方と導入手順にまとめています。

Solid Queue・Active Job経由との選び分け

Rails 8既定のSolid Queueとの違い

Rails 8.0から、Active Jobの既定のバックエンドはSolid Queueです。ジョブをSQLデータベースに保存するので、Redisを持たずに済みます。

観点 Sidekiq Solid Queue
保存先 Redis/Valkey PostgreSQL・MySQL・SQLite
追加インフラ Redis等が必要 不要(既定はqueue用DBを分離)
Railsでの扱い gemを追加 Rails 8.0以降の既定
Web UI 同梱 Mission Controlを別途
Rails以外のRuby 使える Rails前提

Redisの運用経験はSidekiqの導入に生かせますが、キャッシュ用のRedisとの共用は避けます。公式wikiは、キャッシュ用とSidekiq用でインスタンスを分け、Sidekiq側はmaxmemory-policy noeviction(メモリが溢れてもキーを消さない)にするよう求めています。キャッシュ向けの退避設定のままだと、ジョブが黙って消えるためです。Redisを持っていないアプリなら、Solid Queueから始めるほうが構成が軽くなります。SinatraやHanami、素のRubyスクリプトでも使える点はSidekiqだけの利点です。Solid Queueの設定はRails Active Jobとは?非同期ジョブ処理の実装とSolid Queue・リトライ設定で扱っています。

Active Job経由とSidekiq::Jobの直書き

RailsからSidekiqを使うには、ApplicationJobを継承してアダプタに:sidekiqを指定する方法と、include Sidekiq::Jobで直接書く方法があります。

  • 引数:Active JobはTimeやActive Recordのオブジェクト(GlobalID)、キーワード引数を自前で直列化します。直書きはJSONの基本型しか受け付けません。
  • リトライ:Active Jobのretry_onと、Sidekiqのリトライ(25回)の2層になります。片方に寄せないと、失敗時の回数が読みにくくなります。
  • 機能:sidekiq_optionsの細かい指定、sidekiq_retry_in、Proのバッチなどは直書きのほうが素直に使えます。

Sidekiq 8.1.7とActive Job 8.1.3.1で確かめると、Rails組み込みのsidekiqアダプタには「Rails 8.2で削除する」という非推奨警告が出ました。Sidekiq gem側のアダプタが読み込まれると、Redis上のジョブのクラス名はSidekiq::ActiveJob::Wrapperになります(8.0で旧名から変更)。Railsアプリではgemのrailtieがこちらを自動で読み込みます。Sidekiq固有の機能を使うなら直書き、バックエンドを将来差し替える可能性があるならActive Job経由、と決めます。

Railsへの導入手順(Sidekiq 8.1・Redis/Valkey)

Gemfileへの追加、Redisの接続先、Active Jobのアダプタ指定、起動の4手順です。

bundle add sidekiq
redis-cli ping   # PONG が返ればRedisは起動済み
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") }
end
# config/application.rb(Active Job経由で使う場合だけ)
config.active_job.queue_adapter = :sidekiq
bundle exec sidekiq -C config/sidekiq.yml

configure_serverはSidekiqプロセス側、configure_clientはジョブを積むRails側の設定です。片方を省くと、そちらは環境変数REDIS_URLを読み、それも無ければlocalhost:6379へ接続します。

config/sidekiq.ymlのキューと重み

:concurrency: 5
:timeout: 25
:queues:
  - [critical, 3]
  - [default, 1]
  - [low, 1]

数値は取り出す確率の重みです。上の例ではcriticalがdefaultの3倍の頻度で選ばれますが、lowも止まらず処理されます。重みを省いて- criticalのように並べると厳密な優先順になり、criticalが空にならない限りdefaultは処理されません。大量のジョブが流れるキューを上に置いた厳密順は、下位キューが何時間も止まる典型的な原因です。

concurrencyとRedis・DBの接続数

8.1.7のソースでは、Sidekiqプロセスはconcurrencyと同じ大きさのRedis接続プールに加えて、内部処理用に10本のプールを作ります。起動ログにも{size: 10, pool_name: "internal"}と{size: 5, pool_name: "default"}が並びます。どちらも必要になるまで接続を張りません。Sidekiq側の接続数は、プロセスごとに「全Capsuleのconcurrency合計+10」を足し合わせて見積もり、Redis全体の上限にはRails側やWeb UIの接続も加えます。

ジョブがActive Recordを使うなら、config/database.ymlのpoolも、全Capsuleのconcurrency合計以上にします。足りないと、スレッドがDB接続を待ってActiveRecord::ConnectionTimeoutErrorになります。公式wiki(Advanced Options)はconcurrencyを50より上げないよう勧めています。それ以上が必要ならプロセスを増やします。

Capsuleによる特定キューのプロセス内直列化

Sidekiq 7.0で入ったCapsuleを使うと、1件ずつしか呼べない外部APIのジョブを、ほかのジョブを止めずに直列化できます。ただし制限はプロセス内だけです。Sidekiqプロセスが複数あると同じキューが並行で処理されるため、全体で1件に絞るならそのキューを1プロセスだけに持たせます。

Sidekiq.configure_server do |config|
  config.concurrency = 5
  config.capsule("serial") do |cap|
    cap.concurrency = 1
    cap.queues = %w[serial]
  end
end

serialキューに3件、defaultキューに1秒かかるジョブを5件積んで実行すると、defaultの5件は同じ時刻(44.369〜44.370秒)にまとめて終わり、serialの3件は44.370秒、45.513秒、46.661秒と約1.1秒おきに1件ずつ終わりました。

Jobクラスの実装とperform_async・perform_in・perform_at

# app/sidekiq/hard_job.rb
class HardJob
  include Sidekiq::Job
  sidekiq_options queue: "default", retry: 3

  def perform(user_id, opts)
    user = User.find(user_id)
    UserMailer.with(user: user, notify: opts["notify"]).welcome.deliver_now
  end
end

HardJob.perform_async(42, { "notify" => true })   # すぐ実行
HardJob.perform_in(300, 1, {})                     # 300秒後
HardJob.perform_at(Time.now + 3600, 2, {})         # 指定時刻

Sidekiq::Jobは6.3で入った名前で、旧来のSidekiq::Workerは同じモジュールの別名として残っています(8.1.7でSidekiq::Worker.equal?(Sidekiq::Job)はtrue)。perform_asyncの戻り値は24文字のジョブID(jid)です。

ジョブ引数のJSON型制約とstrict_args

引数に渡せるのは文字列・数値・真偽値・nil・配列・ハッシュです。Sidekiq 7.0からは、それ以外を渡すと積む時点で例外になります。

HardJob.perform_async(42, { notify: true })
# ArgumentError: Job arguments to HardJob must be native JSON types, but :notify is a Symbol.

HardJob.perform_async(Time.now, {})
# ArgumentError: Job arguments to HardJob must be native JSON types, but 2026-10-02 10:35:46.869222 +0900 is a Time.

シンボルのキーは文字列に、時刻はTime#to_iやiso8601の文字列にして渡します。Active Recordのオブジェクトはidだけを渡してジョブの中で取り直すと、積んだ時点の古い値で処理する事故も防げます。

perform_in・perform_atの実行タイミング

予約したジョブはRedisのscheduled set(ソート済み集合)に入り、Sidekiqプロセスの中のスケジューラが定期的に見回って通常のキューへ移します。見回りは平均5秒(average_scheduled_poll_interval)ごとで、起動直後は10〜15秒待ってから始まるため、秒単位の正確さは保証されません。

リトライの仕組みとsidekiq_retry_in・sidekiq_retries_exhausted

既定のバックオフ(試行回数の4乗+15秒)

ジョブが例外を出すと、Sidekiqはジョブをretry set(ソート済み集合)に入れて後で再実行します。8.1.7のjob_retry.rbでは、待ち時間は(count**4) + 15秒に、0以上10 * (count + 1)未満のランダムな秒数を足したものです。countは0から数えます。

リトライ 基本の待ち時間 ランダム加算
1回目(count=0) 15秒 0〜9秒
2回目(count=1) 16秒 0〜19秒
4回目(count=3) 96秒 0〜39秒
11回目(count=10) 10,015秒(約2.8時間) 0〜109秒
25回目(count=24) 331,791秒(約3.8日) 0〜249秒

25回分の基本待ち時間を合計すると1,763,395秒、約20.4日です。既定のままだと、何度やっても失敗するジョブが3週間近くretry setに残ります。回数はsidekiq_options retry: 3で減らせ、retry: falseなら1回目の失敗でDead setにも入れずに捨てます。8.1.0からはretry_for(失敗し続けてよい秒数)とretryは同時に指定できなくなりました。

例外ごとの制御とDead set

class FlakyJob
  include Sidekiq::Job
  sidekiq_options retry: 2

  # 秒数を返すと待ち時間を上書き、:kill は即Dead set、:discard は破棄
  sidekiq_retry_in do |count, exception, _msg|
    exception.is_a?(ArgumentError) ? :kill : 1
  end

  sidekiq_retries_exhausted do |msg, ex|
    puts "EXHAUSTED #{msg['class']} #{msg['args'].inspect} retry_count=#{msg['retry_count']} #{ex.class}"
  end

  def perform(kind)
    raise ArgumentError, "bad input" if kind == "bad"
    raise "temporary failure"
  end
end

引数「bad」と「tmp」の2件を処理させると、「bad」は1回目の失敗でsidekiq_retries_exhaustedが呼ばれ、EXHAUSTED FlakyJob ["bad"] retry_count=0 ArgumentErrorを出してDead setへ移りました。「tmp」はretry setに入り、約11秒後に再実行されています。1秒を返しても、ランダム加算と見回り間隔の分だけ遅れます。

Dead setは既定で10,000件か6か月を超えると古いものから消えます。Web UIのDeadタブから再実行できますが、大規模な障害では上限で押し出されて失われるため、sidekiq_retries_exhaustedでエラー監視へ通知しておきます。リトライがある以上、ジョブは2回実行されても結果が変わらない(冪等な)作りにします。送信済みフラグの確認だけでは、送信成功の直後にプロセスが落ちた場合の二重送信を防げません。送信先APIが冪等キーに対応していればそれを渡し、対応していなければ二重送信をどこまで許すかを先に決めます。

Sidekiq Web UIのマウントと認証

# config/routes.rb
require "sidekiq/web"

Rails.application.routes.draw do
  authenticate :user, ->(user) { user.admin? } do   # Devise の場合
    mount Sidekiq::Web => "/sidekiq"
  end
end

Web UIは、キューの滞留数、処理中のジョブ、retry・scheduled・deadの一覧、処理件数の推移を表示します。8.0で画面が作り直され、Metricsは最大72時間分を見られます。8.1.2からは、端末で同じ情報を見るbundle exec kiqも付属します。

よく詰まるのはセッションです。Rackセッションの無い状態でSidekiq::Webを呼ぶとRuntimeError: Sidekiq::Web needs a valid Rack session.で落ち、Rack::Session::Cookieを挟むと200が返りました。rails new --apiで作ったAPIモードのアプリはセッションを持たないため、公式wikiの手順でSidekiq::Webにだけセッションを足します。

# config/application.rb
config.session_store :cookie_store, key: "_myapp_session"

# config/routes.rb
require "sidekiq/web"
Sidekiq::Web.use ActionDispatch::Cookies
Sidekiq::Web.use Rails.application.config.session_store, Rails.application.config.session_options

認証なしで公開すると第三者がジョブを削除・再実行できるため、Deviseが無いアプリはBasic認証で保護します。下の構成で、認証ヘッダなしは401、正しい資格情報では200になることを確かめました。比較前にSHA256でダイジェスト化するのは、Rack::Utils.secure_compareが長さの違う文字列をその場で偽と返すため、長さの情報が漏れないようにするためです。&&でなく&でつなぐのも、両方の比較を必ず実行して時間差を出さないためです。

require "digest"

Sidekiq::Web.use Rack::Auth::Basic do |user, password|
  Rack::Utils.secure_compare(Digest::SHA256.hexdigest(user), Digest::SHA256.hexdigest(ENV.fetch("SIDEKIQ_USER"))) &
    Rack::Utils.secure_compare(Digest::SHA256.hexdigest(password), Digest::SHA256.hexdigest(ENV.fetch("SIDEKIQ_PASSWORD")))
end

定期ジョブ:sidekiq-cronとEnterpriseのPeriodic Jobs

Sidekiq本体には「毎朝9時に実行」のような定期実行の機能がありません。無料で済ませるならsidekiq-cron(2026年10月時点で2.4.0)を追加し、config/schedule.ymlに書きます。このファイルはgemが自動で読み込みます。

# config/schedule.yml
daily_report:
  cron: "0 9 * * * Asia/Tokyo"
  class: "DailyReportJob"
  queue: default

every_15min:
  cron: "*/15 * * * *"
  class: "SyncInventoryJob"

cron式の時刻は、RailsならRailsのタイムゾーン設定、それ以外ならUTCで解釈されます。サーバーのタイムゾーンに頼らず、上のように行末へAsia/Tokyoを書いておくと誤解がありません。"0 25 * * *"のような不正な式は登録時に弾かれます。

見落としやすいのがreschedule_grace_periodです。既定は60秒で、デプロイなどでSidekiqが止まっている間に実行時刻を過ぎたジョブは、1分以内の遅れなら再起動後に実行されますが、それを超えると飛ばされます。1日1回の集計ジョブがデプロイと重なって丸1日抜ける、という事故はこれが原因です。停止が10分近く続く環境なら、initializerのSidekiq::Cron.configureブロックでconfig.reschedule_grace_periodを停止時間より余裕を持たせた値(例:900)に広げます。

有償のSidekiq Enterpriseなら同梱のPeriodic Jobsで同じことができますが、定期ジョブのためだけに契約する必要はありません。

テストの書き方:Sidekiq.testing!とrspec-sidekiq

8.1.1から、require "sidekiq/testing"を読み込むだけでプロセス全体がテストモードになる従来の書き方は非推奨になり、Sidekiq.testing!(mode)で明示する形に変わりました。

require "sidekiq"
Sidekiq.testing!(:fake)     # Redisへ積まずに配列へためる

MailJob.perform_async(1)
MailJob.perform_in(60, 2)
MailJob.jobs.size   # => 2
MailJob.drain       # ためたジョブをその場で全部実行

Sidekiq.testing!(:inline)   # perform_async の時点で即実行

RSpecではrspec-sidekiq(2026年10月時点で5.3.0)のマッチャが使えます。Gemfileのtestグループに追加し、spec_helperでrequire "rspec-sidekiq"します。次のspecは、sidekiq_options queue: "mailers", retry: 5を指定したWelcomeMailJobに対して、Sidekiq 8.1.7で3件とも通りました。

RSpec.describe WelcomeMailJob do
  it "1時間後にmailersキューへ積まれる" do
    expect { WelcomeMailJob.perform_in(3600, 42) }
      .to enqueue_sidekiq_job(WelcomeMailJob).with(42).in(3600)
  end

  it "キューとリトライ回数の設定" do
    expect(WelcomeMailJob).to be_processed_in(:mailers)
    expect(WelcomeMailJob).to be_retryable(5)
  end

  it "積まれたジョブを引数で照合する" do
    WelcomeMailJob.perform_async(7)
    expect(WelcomeMailJob).to have_enqueued_sidekiq_job(7)
  end
end

ジョブの中身は、マッチャとは別にWelcomeMailJob.new.perform(42)を直接呼んで検証します。

本番運用で詰まる箇所とPro・Enterpriseの判断

停止とデプロイ:TSTPとTERMの順序

SidekiqはTERMを受けると新しいジョブの取得をやめ、実行中のジョブをtimeout(既定25秒)まで待ちます。終わらなかったジョブは強制終了してRedisへ戻し、次の起動で再実行します。デプロイの開始時にTSTP(取得停止)を送り、終わりにTERMを送ると、実行中のジョブに最大限の時間を与えられます。25秒以上かかるジョブは、途中から2回目が走る前提で冪等に作るか、Sidekiq 7.3以降のSidekiq::IterableJobで区切って再開できる形にします。Rails側の同等機能はActive Job Continuationsとは何か?長時間ジョブを中断・再開可能にする新機能の基本概念で解説しています。

無料版でジョブを失う条件

無料版のSidekiqは、RedisのリストからジョブをBRPOPで取り出します。取り出した時点でジョブはRedisから消えるため、処理中にプロセスがOOM Killerやkill -9で落ちると、そのジョブは失われます。公式wiki(Reliability)も「クラッシュすると永久に失われる」と明記しています。

決済の確定処理や、取りこぼしが監査で問題になるジョブを無料版に載せるのは避けてください。Proのsuper_fetchは完了までジョブをRedisに残すため、クラッシュ後も再実行されます。

版 価格(sidekiq.org) 主な追加機能
Sidekiq(OSS) 無料(LGPL-3.0) 本体・Web UI・Capsule
Sidekiq Pro 月99ドル/年995ドル super_fetch・Batches
Sidekiq Enterprise 月269ドル〜 Rate Limiting・Periodic Jobs・Unique Jobs

価格はsidekiq.orgの製品ページの2026年10月時点の表示です。Enterpriseは本番のワーカースレッド数で価格が上がります。外部APIの呼び出し回数制限(Rate Limiting)や同じジョブの重複投入防止(Unique Jobs)を自前で作る工数が月269ドルを超えそうなら、そこが契約の目安です。同じ設計課題をSQSで解くならShoryuken(Ruby gem)とは?SQSワーカーの設定とActive Job連携・リトライ設計、Node.jsならBullMQとは?v6のPostgreSQL対応・リトライとワーカー運用を実装者目線で解説が比較材料になります。

メモリの増え方と遅いジョブの特定

Sidekiqプロセスのメモリが増え続けるときは、1件のジョブが大量のレコードを一度に読み込んでいないかを先に疑います。User.all.eachをfind_eachに変える、大きな処理を小さなジョブに分けて積む、が基本の対処です。遅いジョブの中身は、8.0で加わったVernierによるプロファイリングとWeb UIのProfilesタブで確認できます。8.1.6で失敗ジョブによるスレッドとメモリのリークが直っているため、8.1.5以前なら先に更新します。

よくある質問

Sidekiqの読み方と、名前の由来は何ですか?

「サイドキック」と読みます。英語のsidekick(相棒・助手)をもじった名前で、Webアプリの横で重い処理を引き受ける役割を表しています。検索ではsidekickと綴られることも多いですが、gem名はsidekiqです。

Solid QueueとSidekiqはどちらを選ぶべきですか?

Sidekiq専用のRedisを運用できるか、Rails以外のRubyでも使うならSidekiq、Redisを持たずにRails 8の既定構成で済ませたいならSolid Queueです。迷ったら、運用するミドルウェアが増えるかどうかで決めます。

Sidekiq Web UIがセッションのエラーで表示されないのはなぜですか?

Rackセッションが無いことが原因です。Railsではroutes.drawのブロックの中でmountし、APIモードのアプリではActionDispatch::Cookiesとsession storeをSidekiq::Webに追加します。

sidekiq_retry_inには何を返せばよいですか?

次のリトライまでの秒数(整数)を返すと、既定のバックオフを上書きします。:killを返すと即座にDead setへ移し、:discardを返すとDead setにも残さず破棄します。nilを返すと既定の式に戻ります。

Sidekiqは無料で商用利用できますか?

本体はLGPL-3.0で、商用アプリでも無料で使えます。処理中のジョブをクラッシュで失わないsuper_fetchやBatchesが必要になった時点で、Pro(月99ドル)の契約を検討します。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2026.10.28 テックブログ リンツ不正アクセス:314万件の対象確認とパスワード強制リセット後の対処
  3. 2026.10.29 テックブログ So-net情報漏洩:258万件の対象確認とメールのパスワード再設定、窓口の番号
  4. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  5. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応

RELATED POSTS 関連記事

目次