背景
- レポートを自動生成するジョブのコードリーディングをする機会があった
- Active Job、Redis、Solid Queueなどの用語が出てきたが、それぞれの役割と関係性を正確に理解できていなかった
- 「Active Jobとは何か」「Redisはなぜジョブの文脈で出てくるのか」「バックエンドの選び方」を整理する
ジョブ(バックグラウンドジョブ)とは
Webアプリの処理は大きく2種類に分けられる
- フォアグラウンド処理 — ユーザーのリクエストに即座に応答する処理(画面表示、フォーム送信など)
- バックグラウンド処理 — 時間がかかるため、裏側で非同期に実行したい処理
後者がバックグラウンドジョブだ。具体例を挙げると:
- メール送信 — SMTPサーバーとの通信に数秒かかることがある
- 外部API呼び出し — 相手側のレスポンス時間に依存する
- レポート・PDF生成 — 大量データの集計・変換が必要
- データの一括取り込み(CSV/APIインポート) — 件数次第で数分〜数時間
これらの処理はキュー(待ち行列)に入れられ、ワーカー(実行役のプロセス)が順番に取り出して処理していく
[ユーザーのリクエスト]
↓
[Railsアプリ] → ジョブをキューに投入 → レスポンスを即返す
↓
[ワーカープロセス] → ジョブを取り出して実行
ユーザーにはすぐレスポンスを返しつつ、重い処理は裏で動く。これがジョブの基本的な仕組みだ
Active Jobとは
Active JobはRails標準の「ジョブの書き方を統一するインターフェース」だ
重要なのは、Active Job自体がジョブを実行するわけではないということ。実際にジョブを管理・実行するのは裏側のバックエンド(Sidekiq、Solid Queueなど)で、Active Jobはその間に立つ共通規格・翻訳者のような存在
[ジョブのコード(Active Job)]
↓ 共通のAPI
[アダプター]
↓
[バックエンド(Sidekiq / Solid Queue / Good Job など)]
Active Jobでできること
| メソッド | 動作 | 用途 |
|---|---|---|
perform_later |
キューに入れて非同期実行 | 本番での通常利用 |
perform_now |
その場で即時実行 | テスト・デバッグ |
set(wait: 1.hour).perform_later |
指定時間後に実行 | リマインダー等 |
set(wait_until: Date.tomorrow.noon).perform_later |
指定日時に実行 | 定期処理 |
さらに、以下の機能も備えている:
-
失敗時の自動リトライ —
retry_onで例外ごとにリトライ戦略を定義できる -
バックエンドの自由な切り替え —
config/application.rbの設定1行でバックエンドを変更可能。ジョブのコード自体は変更不要
コード例
# app/jobs/send_weekly_report_job.rb
class SendWeeklyReportJob < ApplicationJob
queue_as :default
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
def perform(user_id)
user = User.find(user_id)
ReportMailer.weekly(user).deliver_now
end
end
# コントローラーなどからの呼び出し
SendWeeklyReportJob.perform_later(current_user.id)
ApplicationJobを継承してperformメソッドを定義するだけでジョブが作れる。呼び出し側はperform_laterでキューに投入するだけなので非常にシンプルだ
Redisとは
Redis(Remote Dictionary Server)は、データをメモリ(RAM)上に保存するインメモリデータベースだ
なぜ速いのか
通常のデータベース(PostgreSQL、MySQLなど)はデータをディスクに書き込むが、Redisはメモリ上で動作する。ディスクI/Oがボトルネックにならないため、読み書きが約100〜1000倍速い
データ構造
Redisはキーと値のペア(Key-Value)でデータを保存する。値には複数のデータ型が使える
| 型 | 用途の例 |
|---|---|
| String | キャッシュ、セッション |
| List | ジョブキュー、タイムライン |
| Hash | ユーザー情報のような構造化データ |
| Set | タグ管理、ユニーク集合 |
| Sorted Set | ランキング、スコアボード |
| Stream | イベントログ、リアルタイム処理 |
Railsでの主な用途
- ジョブキュー(Sidekiqのバックエンド)
- キャッシュストア(フラグメントキャッシュ等)
- セッションストア
- Action Cableのバックエンド(WebSocket通信)
注意点
- メモリは有限かつ高価 → 大量データの永続保存には不向き
- デフォルトでは揮発性(電源断でデータ消失の可能性あり)。RDB/AOFの設定でディスク永続化は可能
- Redisサーバー自体の運用・監視コストが発生する
主要なジョブシステム(バックエンド)の比較
Active Jobの裏側で実際にジョブを管理・実行するバックエンドは複数ある。代表的な4つを比較する
| バックエンド | データ保存先 | 追加インフラ | 速度 | 向いてるケース |
|---|---|---|---|---|
| Solid Queue | DB(PostgreSQL/MySQL/SQLite) | 不要 | 普通 | 小〜中規模、Rails 8デフォルト |
| Sidekiq | Redis | Redis必要 | 非常に速い | 大量ジョブ、大規模サービス |
| Good Job | PostgreSQL | 不要(※PG必須) | 良い | PG環境でRedisを増やしたくない場合 |
| Delayed Job | DB | 不要 | 遅め | レガシーRailsアプリの保守 |
Solid Queue(Rails 8〜のデフォルト)
Rails 8からデフォルトのジョブバックエンドになった。ジョブ情報をRDB(既存のデータベース)に保存するため、Redisのような追加インフラが不要なのが最大の利点
PostgreSQL/MySQLではFOR UPDATE SKIP LOCKEDを活用した効率的なジョブ取得が行われる。SQLiteでも動作するが、本番環境ではPostgreSQLかMySQLの利用が推奨されている
定期実行の設定例:
# config/recurring.yml
send_weekly_reports:
class: SendWeeklyReportJob
schedule: "0 9 * * 1" # 毎週月曜 9:00
Sidekiq
Redisをバックエンドに使い、マルチスレッドでジョブを並行処理する。大量のジョブを高速にさばく必要がある大規模サービスで選ばれることが多い。ただしRedisサーバーの運用コストが加わる
どう選ぶか
- とりあえず始めたい / 小〜中規模 → Solid Queue(Rails 8なら何も設定しなくてもこれ)
- 大量のジョブを高速処理したい → Sidekiq
- PostgreSQL環境でRedisを増やしたくない → Good Job
- 既存のレガシーアプリを保守している → Delayed Job(新規採用は非推奨)
まとめ
| 概念 | 役割 |
|---|---|
| バックグラウンドジョブ | ユーザーを待たせない非同期処理の仕組み |
| Active Job | ジョブの書き方を統一するRails標準インターフェース |
| Redis | インメモリで超高速なデータストア(Sidekiqのバックエンドとして利用) |
| バックエンド(Solid Queue等) | 実際にジョブを管理・実行するエンジン |
Active Jobは「共通規格」、Redisは「高速データストア」で役割がまったく異なるが、Sidekiqを介してセットで語られることが多い。Active Jobの規格に沿ってジョブを書いておけば、将来バックエンドを変えたくなっても移行コストが低いがメリット
感想
- 「Sidekiq = Redis」みたいに漠然と思っていたが、Active Job・バックエンド・データストアの3層構造が分かると全体像がスッキリした
- Rails 8からSolid Queueがデフォルトになったのは、「小〜中規模ならRedis不要」という判断ができるようになった点で大きい変化だと思う