Rails バックグラウンドジョブ 2026年版:Sidekiq vs Good Job の徹底比較と面接対策

2026年のRailsにおけるバックグラウンドジョブ処理を徹底解説。Sidekiq、Good Job、Solid Queueの特徴を比較し、技術面接でよく出題される質問と回答例を紹介します。

Rails バックグラウンドジョブ:Sidekiq vs Good Job の比較

Railsのバックグラウンドジョブは、Rails 8.xとSolid Queueのデフォルト採用により大きく進化しました。Sidekiq、Good Job、Solid Queueの選択は、スループット要件、インフラ構成、必要な機能によって決まります。

クイック選択ガイド

新規のRails 8プロジェクトでは、まずSolid Queueから始めることを推奨します。1日10万件を超える高スループットワークロードやRedisレベルのレイテンシが必要な場合はSidekiqに切り替えてください。PostgreSQLベースでバッチ処理やユニークジョブ制約が必要な場合はGood Jobが適しています。

Active Jobによるバックグラウンド処理の統一化

Active Jobは、Railsでバックグラウンド処理を宣言、キューイング、実行するための標準インターフェースを提供します。このフレームワークはキューバックエンド間の差異を抽象化し、Sidekiq、Good Job、Solid Queueなど様々なアダプタ間でコードの移植性を維持できます。

ruby
# app/jobs/payment_processor_job.rb
class PaymentProcessorJob < ApplicationJob
  queue_as :critical
  retry_on Stripe::RateLimitError, wait: :polynomially_longer, attempts: 5
  discard_on Stripe::InvalidRequestError

  def perform(order_id)
    order = Order.find(order_id)
    PaymentService.process(order)
    OrderMailer.confirmation(order).deliver_later
  end
end

retry_ondiscard_onコールバックは、バックエンド固有のコードなしで一時的な障害と永続的なエラーを処理します。queue_asディレクティブは優先度を割り当て、各バックエンドは独自のキュー処理戦略に従って解釈します。

Sidekiq 8.x:Redisベースの高スループット

Sidekiqは、Railsバックグラウンドジョブのパフォーマンス基準として位置づけられています。8.xリリースシリーズでは、Ruby 3.2以上、Rails 7.0以上、Redis 7.2以上が必要です(Redisがライセンス変更したため、ValkeyとDragonflyがドロップイン代替として動作します)。

ruby
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch('REDIS_URL') }
  config.concurrency = 10
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch('REDIS_URL') }
end

Sidekiq 8では3つの主要な変更が導入されました。Web UIが完全に書き直され、CSSが160KBから16KBに削減、平均ページレンダリング時間が55msから3msに短縮されました。Vernierを使用したジョブプロファイリングが新しいProfilesタブで利用可能になり、ジョブメトリクスが最大72時間保持されるようになりました。

Sidekiqが適しているケース

Sidekiqは、持続的なスループット、サブミリ秒のジョブ取得レイテンシ、またはレート制限やユニークジョブなどのエンタープライズ機能がRedis運用を正当化するワークロードに適しています。7.0で導入され現在は標準となったCapsulesにより、1つのSidekiqプロセスで独自の並行性とキューを持つ複数の分離されたスレッドプールを実行できます。

ruby
# config/sidekiq.yml
:concurrency: 10
:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]

# 分離処理用のCapsules
:capsules:
  imports:
    :concurrency: 2
    :queues:
      - imports

埋め込みAPIにより、Solid QueueのPumaプラグインと同様に、小規模アプリケーションではSidekiqをPumaプロセス内で実行することも可能です。

Ruby on Railsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Good Job 4.x:PostgreSQLネイティブのバッチ処理とユニーク性

Good Jobは、PostgreSQLのLISTEN/NOTIFYをジョブ取得に、アドバイザリロックを一度だけ実行の保証に使用します。バージョン4.19.x(2026年9月現在)はRails 6.1以上とRuby 3.0以上をサポートしています。

ruby
# config/application.rb
config.active_job.queue_adapter = :good_job

# config/initializers/good_job.rb
Rails.application.configure do
  config.good_job.execution_mode = :async
  config.good_job.max_threads = 5
  config.good_job.poll_interval = 5
  config.good_job.shutdown_timeout = 25
end

Good Jobは、Sidekiqが有料ライセンスで提供しているバッチ処理とユニークジョブ機能をオープンソース版で提供しています。perform_later呼び出しは呼び出し元コードと同じデータベーストランザクション内でgood_jobsテーブルに行を挿入するため、トランザクションがロールバックするとジョブは作成されません。

ライセンス不要のバッチ処理

ruby
# app/jobs/batch_import_job.rb
class BatchImportJob < ApplicationJob
  def perform(batch, params)
    batch.on(:finish, ImportCompletionJob, params)
    
    params[:file_ids].each do |file_id|
      batch.add do
        ProcessFileJob.perform_later(file_id)
      end
    end
  end
end

# バッチをエンキュー
GoodJob::Batch.enqueue do |batch|
  BatchImportJob.perform_later(batch, file_ids: [1, 2, 3])
end

冪等性のためのユニークジョブ

ruby
# app/jobs/sync_inventory_job.rb
class SyncInventoryJob < ApplicationJob
  include GoodJob::ActiveJobExtensions::UniqueJob

  good_job_control_concurrency_with(
    perform_limit: 1,
    key: -> { "sync_inventory_#{arguments.first}" },
    duration: 1.hour
  )

  def perform(warehouse_id)
    InventorySync.run(warehouse_id)
  end
end

perform_limit: 1制約により、各倉庫で同時に実行される同期は1つだけとなり、同じジョブが複数回キューイングされた場合の重複作業を防ぎます。

Solid Queue:Rails 8のデフォルト

Solid Queueは、PostgreSQLとMySQLの機能であるFOR UPDATE SKIP LOCKEDを使用して、通常のテーブルをジョブキューに変換し、アプリケーションデータベースにジョブを保存します。新しいRails 8アプリケーションは、Solid Queueが設定済みの状態で出荷されます。

ruby
# config/solid_queue.yml
production:
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 5
      polling_interval: 0.1

Solid QueueはSOLID_QUEUE_IN_PUMA=trueでPuma Webサーバープロセス内で実行でき、専用のワーカープロセスを必要としないアプリケーションのデプロイを簡素化します。ジョブはデータベーステーブルに永続化され、外部依存関係なしでプロセスの再起動やデプロイを生き延びます。

機能比較表

機能Sidekiq 8.xGood Job 4.xSolid Queue 1.x
バックエンドRedis 7.2以上PostgreSQLPostgreSQL/MySQL/SQLite
バッチ処理Pro/Enterprise含む計画中
ユニークジョブEnterprise含む手動ロック
CronスケジューリングEnterprise含むsolid_queue_cron経由
Web UI含む含む最小限(Mission Control)
スループット上限10万件以上/分1万件/分1万件/分
Pumaへの埋め込み可能(8.x)不可可能
ライセンスLGPL + 商用MITMIT

Railsバックグラウンドジョブに関する面接質問

技術面接では、ジョブの信頼性、リトライ戦略、アーキテクチャ決定に関する理解がよく問われます。以下は、経験豊富な候補者を見分けるための質問です。

Q1:retry_onとバックエンド固有のリトライロジックの違いは何ですか?

retry_onはすべてのバックエンドで動作するActive Jobコールバックです。特定の例外をキャッチし、待機戦略(:polynomially_longerは指数バックオフ)を適用し、ジョブを再キューイングします。Sidekiqのsidekiq_retry_inなどのバックエンド固有のリトライロジックは、そのバックエンドでのみ動作し、Active Jobの動作をオーバーライドする可能性があります。

ruby
# Active Jobリトライ(移植可能)
class ApiSyncJob < ApplicationJob
  retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 8
  
  def perform(record_id)
    ExternalApi.sync(record_id)
  end
end

Q2:ジョブをキューイングしたデータベーストランザクションがロールバックした場合、ジョブはどうなりますか?

データベースベースのキュー(Good Job、Solid Queue)では、ジョブ行はトランザクションの一部であるため、他のすべてと一緒にロールバックされます。Redisベースのキュー(Sidekiq)では、トランザクションが完了する前にジョブはすでにRedisにあり、孤立したジョブが作成されます。回避策はafter_commitコールバックです。

ruby
# Sidekiqの正しいパターン
class Order < ApplicationRecord
  after_commit :enqueue_confirmation, on: :create

  private

  def enqueue_confirmation
    OrderConfirmationJob.perform_later(id)
  end
end

Q3:Good JobがSidekiqを上回るのはどのような場合ですか?

Good Jobは、Redisの運用コストがそのスループット利点を上回る場合にSidekiqを上回ります。1日5万件未満のジョブを処理するアプリケーションでは、PostgreSQLベースのキューにより、別のRedisクラスタのインフラ、監視、フェイルオーバーの複雑さを回避できます。Good Jobはライセンス料なしでバッチ処理とユニークジョブも提供しており、予算内でこれらの機能が必要なチームにとって重要です。

Q4:ジョブの冪等性とリトライにとって重要な理由を説明してください

冪等性とは、同じジョブを複数回実行しても1回実行したのと同じ結果になることを意味します。ネットワーク障害、プロセスクラッシュ、タイムアウト処理により重複実行が発生する可能性があるため、ジョブは冪等でなければなりません。手法には、データベースのユニーク制約、処理前の既存結果の確認、決済APIの外部冪等性キーの使用などがあります。

ruby
# 冪等な決済処理
def perform(order_id, idempotency_key)
  return if Payment.exists?(idempotency_key: idempotency_key)
  
  Payment.create!(
    order_id: order_id,
    idempotency_key: idempotency_key,
    amount: Order.find(order_id).total
  )
end

Q5:分散ワーカー間でジョブを正確に1回だけ実行する方法は?

分散ロックにより同時実行を防ぎます。Good JobはPostgreSQLアドバイザリロックを使用します。Sidekiq Enterpriseはユニークジョブを提供します。手動実装の場合は、RedisのSETNXまたはPostgreSQLのpg_try_advisory_lockを使用します。

ruby
class ExclusiveJob < ApplicationJob
  def perform(resource_id)
    lock_key = "exclusive_job:#{resource_id}"
    
    ActiveRecord::Base.connection.execute(
      "SELECT pg_try_advisory_lock(hashtext('#{lock_key}'))"
    ).first['pg_try_advisory_lock'] or return
    
    begin
      process_resource(resource_id)
    ensure
      ActiveRecord::Base.connection.execute(
        "SELECT pg_advisory_unlock(hashtext('#{lock_key}'))"
      )
    end
  end
end

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

Railsバックグラウンドジョブに適したバックエンドの選択

  • スタックにまだRedisがない新しいRails 8プロジェクトでは、Solid Queueから始める
  • ジョブ量が1日5万件を超える場合や100ms未満の取得レイテンシが重要な場合は、Sidekiqに切り替える
  • ライセンスコストなしでバッチ処理、ユニークジョブ、またはCronスケジューリングが必要なPostgreSQL専用デプロイにはGood Jobを選択する
  • Sidekiqでジョブをキューイングする際はafter_commitコールバックを使用し、トランザクションロールバック時の孤立ジョブを回避する
  • リトライと分散処理により重複実行が発生する可能性があるため、すべてのジョブを冪等にする
  • 面接では、トランザクション内ジョブキューイング、リトライ戦略、分散ロックパターンの理解を示すことが重要
今日のチャレンジ

Ruby on Rails のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月12日 更新

タグ

#ruby-on-rails
#sidekiq
#good-job
#solid-queue
#background-jobs
#interview

共有

関連記事