- Rails 8 は標準スタックから Redis 依存を排除し、SolidQueue・SolidCache・SolidCable を通じて、すべての処理を リレーショナルデータベース(RDB) 上で扱う構成へ移行
- Redis は高速で安定している一方、設定・セキュリティ・クラスタ管理・バックアップなど運用の複雑さを招く
- SolidQueue は PostgreSQL の
FOR UPDATE SKIP LOCKED 機能を活用し、競合のない並列ジョブ処理を実現
- 定期ジョブ、同時実行制御、監視ダッシュボード(Mission Control) など、Redis+Sidekiq の有料機能を 無料で提供
- ほとんどの Rails アプリケーションでは SolidQueue で十分であり、超高速・リアルタイム処理が必要な一部のケースでのみ Redis を維持する必要がある
Redisの隠れたコスト
- Redis には単純なホスティング費用以外にも、導入・保守・セキュリティ設定・HAクラスタ管理 など継続的な運用負担がある
- Rails と Redis 間の ネットワーク接続とファイアウォール設定、クライアント認証、Sidekiq プロセスのオーケストレーション が必要
- 障害発生時には Redis と RDBMS の2システムを同時にデバッグしなければならず、二重のバックアップ戦略も求められる
- 一方、Redis を使わない Rails スタックでは PostgreSQL だけを管理すればよく、構成を単純化できる
SolidQueueの動作原理
- PostgreSQL の
FOR UPDATE SKIP LOCKED 機能を使い、ロック競合(lock contention) なしで複数のワーカーが同時にジョブを取得する
- 主なテーブル構造
solid_queue_jobs: ジョブのメタデータを保存
solid_queue_scheduled_executions: 予約済みジョブを待機
solid_queue_ready_executions: 実行準備ができたジョブのキュー
- ワーカー・ディスパッチャ・スケジューラ・スーパーバイザ の各プロセスが異なるテーブルを定期的にポーリングしながら連携
- PostgreSQL の MVCC 設計と autovacuum により、大量の挿入・削除処理も安定してさばける
繰り返しジョブのスケジューリング
- SolidQueue は cron スタイルの繰り返しジョブ を標準で提供し、
config/recurring.yml ファイルで設定する
- スケジューラは実行時刻になったジョブをキューに入れ、次回実行時刻を自動で予約する
- Fugit ライブラリで自然言語スケジュールを解析し、Concurrent::ScheduledTask でスレッドを生成
- GoodJob の決定的(deterministic)スケジューリング方式を取り入れ、プロセス再起動後もスケジュールを維持する
同時実行制御機能
- SolidQueue は POSIX セマフォパターン を使って、ジョブ単位ごとの同時実行制限をサポート
- 例:
limits_concurrency to: 1, key: ->(user) { user.id } を設定すると、ユーザーごとに1ジョブだけ実行される
- セマフォの有効期限(
duration)を指定し、ジョブ衝突やデッドロックを防止
- 関連テーブル
solid_queue_semaphores: 同時実行制限を追跡
solid_queue_blocked_executions: 待機中のジョブを保存
Mission Controlによる監視
- Mission Control Jobs は Rails 8 向けの無料オープンソースダッシュボードで、
/jobs パスに簡単にマウントできる
- 主な機能
- リアルタイムのキュー状態、失敗ジョブの追跡、再試行/破棄の制御
- 予約・繰り返しジョブのタイムライン可視化
- キューごとの処理量とメトリクスのグラフ
- SQL ベースのクエリに対応しており、追加ツールなしでデータベースから直接分析可能
SidekiqからSolidQueueへの移行
- ステップ1:
config.active_job.queue_adapter = :solid_queue を設定
- ステップ2:
bundle add solid_queue の後、rails solid_queue:install と db:migrate を実行
- ステップ3:
sidekiq.yml の cron スケジュールを recurring.yml に変換
- ステップ4:
Procfile に jobs: bundle exec rake solid_queue:start を追加
- ステップ5: Redis・Sidekiq 関連の gem を削除
- 既存の ActiveJob コードは修正なしでそのまま動作する
Redisが依然として必要な場合
- 毎秒数千件を超える継続的なジョブ処理
- 1ms 未満のレイテンシ が必須のリアルタイムシステム
- 複雑な pub/sub 構造 や 高度なレート制限・カウンタ演算 が必要
- 例として Shopify は毎秒 833 リクエスト、1,172 のワーカープロセスを運用し、Redis インフラを利用している
実際の実装ガイド
- Rails 8 の新規アプリ作成時には SolidQueue・SolidCache・SolidCable が自動構成される
config/database.yml に 専用の queue データベース接続 を設定することが推奨される
- Mission Control の認証を追加し、
/jobs ルートをマウント
Procfile.dev に jobs: bundle exec rake solid_queue:start を追加し、bin/dev 実行で全体を起動
- テストジョブを作成したあと、Mission Control で状態を確認できる
よくある問題と解決策
- 単一データベース構成 も可能だが、運用の柔軟性は下がる
- 本番環境の Mission Control には必ず認証の追加が必要
- ポーリング間隔 のデフォルト値は予約ジョブ 1 秒、即時ジョブ 0.2 秒で、大半のアプリに適している
- ActionCable/Turbo Streams を使う場合は、
SolidCable を別DB接続として設定する必要がある
拡張性と性能
- SolidQueue は大半の Rails アプリで十分にスケール可能
- PostgreSQL ベースで 毎秒 200〜300 ジョブ処理 が可能で、37signals は Redis なしで1日2,000万ジョブ を処理している
- 比較表
| 項目 |
Redis + Sidekiq |
SolidQueue |
| 設定の複雑さ |
別サービスが必要 |
内蔵DBを利用 |
| クエリ言語 |
Redis コマンド |
SQL |
| 監視 |
別ダッシュボード |
Mission Control |
| 障害シナリオ |
6個以上 |
2個 |
| 処理量 |
数千件/秒 |
200〜300件/秒 |
| 適した対象 |
99.9% のアプリ |
95% のアプリ |
結論
- Redis と Sidekiq は優れた技術だが、大半の Rails アプリケーションには過剰な複雑さとコストをもたらす
- SolidQueue は単一データベースを基盤に、運用の単純化・コスト削減・保守効率の向上を実現する
- Rails 8 時代の標準的な選択肢として、SolidQueue への移行が推奨される
2件のコメント
Redisはいいけど。
Hacker Newsの意見
すべてのオープンソース作者には、自分のプロジェクトの範囲をコントロールする権利があると思う
ただ、うちのチームがgood_jobからSolidQueueに切り替えたのは後悔している
BasecampはMySQL中心なので、RDBMSエンジン特化クエリを受け入れていない。GitHubのIssueを見ると、MySQL性能にばかり注力しているのがわかる
それに、まだバッチジョブ対応もない(関連PR)
複雑なJOINでMySQLがクエリプランを誤ることが多いので、私はSTRAIGHT_JOINで順序を強制している。将来への備えでもある
私はresqueから移行する候補としてこの2つを比較中だ。GoodJobはpg専用機能のため、pgbouncerのtransactionモードと互換性がない
セッション維持が必要で面倒だが、性能向上はたいていの規模では大きな意味がない
それでも、GoodJobの開発モデルとコードの可読性にはずっと信頼感がある
本番環境を単純化できるなら、いつでも良いことだ
Railsで理想的なのは、Redisへ簡単に切り替えられる構成だと思う
SolidQueueで始めて、スケーラビリティの限界にぶつかったらRedisへ移行できると良い
ほとんどのRailsアプリはトラフィックが大きくないので、2つのシステムを維持するほうがむしろ複雑だ
もちろん特定のキュー実装に依存するアプリもあるが、一般的には設定を変えるだけで済む
ログが大きくなりすぎないようにスナップショットも併用するのか、そしてこれが分散モードでも動くのか知りたい
特にジョブ生成が他のDB変更と一緒に起こる場合、その保証を失うのが問題だ
Redisはこの点で、軽量で独立した状態ストアとして有利だった
SolidQueueはこうした分離を明確にしていないように思える(riverqueue.com)
自分のサイドプロジェクトでSolidQueueを試してみた
結論として、Sidekiqに問題がないならわざわざ変える理由はない
Redisインフラをなくしたいときだけ検討する価値がある
新規プロジェクトなら、GoodJobのほうがより成熟していてコミュニティも良い
SolidQueueはUIがあまりに簡素で不便だった。インデックス最適化がされておらず、データが増えるとページが固まった
RDBMSを使うと、コネクションプール管理コストが追加される点も考慮すべきだ
スケーラビリティを心配する人向けに言うと、ElixirのObanのベンチマークでは
単一ノードで毎分100万件のジョブを処理している。ほとんどのアプリのジョブ量はそれよりずっと少ない
5000件のジョブを一度にバッチ投入する構造なので、TPSは実際には200程度だ
バッチなしで個別ジョブを投入すると、SQLトランザクション負荷はずっと大きくなる
私たちはSolidQueue以前からDBにジョブを保存していた
利点は、本番状態をそのまま開発環境へスナップショットできることだ
ただしrate limiterはRedisに置いている。DB負荷を防ぐためだ
DBベースのキューの限界は大容量payloadだ
大きなJSONをキューに入れると、DB書き込みオーバーヘッドのせいで非効率になる
Redis(Sidekiq)はこういう場合はるかに速い
SolidQueue+SQLiteは、単にprimary keyを渡す用途なら悪くない
ただし複数のワーカーが同じDBをポーリングすると、すぐにボトルネックになる
大きなデータはS3のような外部ストレージに置き、参照だけ渡すほうがよいと思う
ベンチマーク結果をまとめた資料があるのか気になる
SolidQueueではSKIP LOCKEDに触れているが、15分かかるジョブをトランザクションで維持するのは危険だ
長時間開いたトランザクションはDB性能を損ない、ネットワーク切断にも弱い
こうした構造はアンチパターンにつながりかねない。後で見たら、lease方式のようだ
Postgres for everythingという哲学に共感する
シンプルにPostgreSQL一つへ統合するのがよいと思う
このたとえにどう反論すべきかわからない
複雑さを増やしてまでRedisを使う理由があるのかと思う
「1ms未満のレイテンシが重要なビジネス」って、RailsでHFTをやるという話か?
Postgresが世界を食う