現在の絞り込み: 技術ブログすべてクリア

SolidQueue + Solid Cable + Turbo Broadcastの開発環境セットアップ

はじめに

Rails 8の開発環境では、Active JobとAction Cableのデフォルトアダプターがいずれもasyncに設定されている。asyncアダプターはシングルプロセス・メモリベースの簡易的な仕組みであり、本番環境とは挙動が異なる。

Active Jobのretry_onwaitオプションが効かないなど、非同期処理が意図通りに動作しない場合は、本番環境と同じSolid Queueへの切り替えが必要になる。しかし、Solid Queueを導入するとジョブが別プロセスで実行されるため、今度はTurbo Broadcastがブラウザに届かなくなる。これはAction Cableのasyncアダプターが同一プロセス内でしかメッセージを配信できないためである。解決するには、Action CableのアダプターもSolid Cableに変更する必要がある。

本記事では、この一連の問題の背景と設定手順をまとめる。

この記事を書いている人

fjord bootcampで2年1ヶ月プログラミングを学んでいます。現在、最後の個人開発のプラクティスでAIチャットアプリを作っています。

開発環境

  • Rails 8
  • Docker(PostgreSQL)

調べた背景

  1. Active Jobのretry_onメソッドにwaitオプションを設定したが、指定した間隔で動作しなかった
  2. Active JobのアダプターをasyncからSolid Queueに変更したところ、retry_onは正常動作するようになったが、Turbo Broadcastがブラウザに届かなくなった
  3. Action CableのアダプターをasyncからSolid Cableに変更したところ、Turbo Broadcastが正常に届くようになった

基礎知識

Asyncアダプター(Active Job)

Rails 8のdevelopment環境におけるActive Jobのデフォルトアダプター。ジョブをメモリ上のスレッドプールで即座に実行する簡易的な仕組み。以下の制約がある。

  • retry_onwait(遅延実行)が正常に動作しない
  • プロセスが終了すると未実行のジョブが失われる
  • 同一プロセス内でのみ動作する

production環境ではデフォルトでSolid Queueが使用される。

参考: Active Job Basics - Rails Guide

Asyncアダプター(Action Cable)

Rails 8のdevelopment環境におけるAction Cableのデフォルトアダプター。broadcastされたメッセージを同一プロセス内の購読者にメモリ経由で配信する。

  • 別プロセスからのbroadcastは受け取れない
  • Solid Queueのジョブ内からのTurbo Broadcastはブラウザに届かない

※ Active JobのasyncとAction Cableのasyncは同じ名前だが別々の実装。たまたま同じ名前と制約(シングルプロセス・メモリベース)を持っている。

参考: Action Cable Overview - Rails Guide

Solid Queue

Active Jobの本番用キューバックエンド。メモリの代わりにDBでジョブの予約・実行・リトライを管理する。遅延実行やリトライ戦略が正しく動作する。

Rails 8のデフォルトでは、Pumaのpluginとして起動される(plugin :solid_queue)。これにより別途プロセスを立てる手間を省いているが、内部的にはforkされた別プロセスとして動作する。ジョブの負荷が大きくなった場合は、bin/jobsで独立プロセスとして分離できる設計になっている。

参考: Solid Queue - GitHub

Solid Cable

Action Cableのsubscriptionアダプター。メモリの代わりにDBでWebSocketメッセージの受け渡しを行う。DBを仲介するため、プロセスをまたいだbroadcastが可能になる。Solid Queueのジョブ内からのTurbo Broadcastもブラウザに届くようになる。

参考: Solid Cable - GitHub

なぜ両方の変更が必要なのか

[変更前] async + async
Puma (HTTP + ActionCable + ActiveJob) ← 全て同一プロセス、broadcastは届く

[Solid Queueのみ導入]
Puma (HTTP + ActionCable) ←→ SolidQueue (ActiveJob) ← 別プロセス、broadcastが届かない

[両方導入] Solid Queue + Solid Cable
Puma (HTTP + ActionCable) ←→ DB ←→ SolidQueue (ActiveJob) ← DB経由でbroadcastが届く

設定手順

1. [Solid Queue] queue_adapterの設定

# config/environments/development.rb

# デフォルトのasyncからsolid_queueに変更
config.active_job.queue_adapter = :solid_queue

# Solid Queue用のDBを指定(database.ymlのqueueキーに対応)
config.solid_queue.connects_to = { database: { writing: :queue } }

2. [Solid Queue] Pumaプラグインの設定

# config/puma.rb

# development環境でもSolid QueueをPumaから起動する
# 本番ではSOLID_QUEUE_IN_PUMA環境変数で制御される(Kamalのdeploy.ymlで設定済み)
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] || Rails.env.development?

3. [Solid Cable] cable.ymlの設定

# config/cable.yml

development:
  adapter: solid_cable           # asyncからsolid_cableに変更
  connects_to:
    database:
      writing: cable             # database.ymlのcableキーに対応
  polling_interval: 0.1.seconds  # DBへのポーリング間隔
  message_retention: 1.day       # メッセージの保持期間

4. [Solid Queue / Solid Cable] database.ymlにDBを追加

# config/database.yml

default: &default
  adapter: postgresql
  encoding: unicode
  username: monalin
  password: password
  host: db  # Docker環境ではDBサービス名を指定(queue/cableのDB接続に必須)
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  primary:
    <<: *default
    database: monalin_development          # 既存のアプリケーションDB
  queue:
    <<: *default
    database: monalin_development_queue    # Solid Queue用DB
    migrations_paths: db/queue_migrate
  cable:
    <<: *default
    database: monalin_development_cable    # Solid Cable用DB
    migrations_paths: db/cable_migrate

host: dbについて: queue/cableのDBはdatabase.ymlを直接参照するため、defaultセクションにhostの指定が必須。

5. テーブル作成

# サーバーを停止(DB接続を切断するため)
docker compose down

# queue/cableのDBとテーブルを作成
docker compose run --rm web bin/rails db:prepare

# サーバーを再起動
docker compose up

db:prepareはDBが存在しなければ作成してスキーマをロードし、既に存在すれば未実行のマイグレーションを実行する。db/queue_schema.rbdb/cable_schema.rbがプロジェクトに存在することを事前に確認すること(Rails 8でrails newした場合は生成済み)。

参考リンク

Photo by Possessed Photography on Unsplash