Rails Solid Cable の使い方|Action Cable をRedisなしで動かす

  • 作成日 2026.07.01
  • rails
Rails Solid Cable の使い方|Action Cable をRedisなしで動かす

Rails 8 で デフォルトの Action Cable アダプター となった solid_cable は、データベース駆動のWebSocketメッセージブローカーです。Redis pub/sub の代わりにDBポーリングで、リアルタイム通信をシンプルに実現します。

# config/cable.yml
production:
  adapter: solid_cable

これでRedisなしで Hotwire・Turbo Streams・チャット・通知などのリアルタイム機能が動作します。

しかし、Redis pub/sub と仕組みが違うため、いくつか戸惑うポイントがあります:

  • 開発環境では Solid Cable が動かない(asyncアダプタが既定)
  • ポーリングって性能的に大丈夫?
  • Hotwireと組み合わせるベストプラクティスは?
  • 既存の Redis Action Cable からどう移行する?
  • 何万接続まで耐えられる?AnyCable と比較すると?
  • Solid Cable は本番運用に向くのか?

など、判断ポイントが多くあります。

本記事では、Solid Cable のすべての使い方を、セットアップから本番運用、Redis pub/subからの移行まで実用視点で網羅します。config/cable.yml設定、ポーリング機構の理解、Turbo Streams / Hotwire 連携、AnyCable比較、移行手順、本番運用、FAQまで完全網羅。この1本で Solid Cable を実戦投入し、Rails 8 でリアルタイム機能をRedisなしで実装できるようになります。


目次

結論:今すぐ使える3ステップ

時間がない方向けに、最短のセットアップ手順を示します。

ステップ1:インストール(Rails 7.1+の場合)

# Rails 8 では既に含まれている。既存アプリへの追加なら:
bundle add solid_cable
bin/rails solid_cable:install
bin/rails db:migrate

ステップ2:設定(開発環境でも有効化)

# config/cable.yml
development:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable

production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day

ステップ3:使う(通常の Action Cable と同じ)

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_room_1"
  end
end

# どこからでも broadcast 可能
ActionCable.server.broadcast("chat_room_1", { message: "Hello!" })

これでRedisなしでリアルタイム通信が動きます。詳細は以下で解説します。


まず押さえる:Solid Cable とは

概要

Solid Cable は Basecamp が開発し、Rails 8 から標準採用されたデータベース駆動の Action Cable アダプタです。

特徴内容
バックエンドデータベース(PostgreSQL / MySQL / SQLite)
Redis依存なし
Action Cable 統合ネイティブ
メカニズムDBポーリング(100ms間隔がデフォルト)
メッセージ保持1日(autotrim)
対応Rails7.1以上で利用可、8.0以降は標準
対応Ruby3.2以上(Rails 8.1+ は3.3+)

Action Cable の役割を簡単に復習

Action Cable は Rails でWebSocket通信を扱う仕組みです:

[クライアント] ←─WebSocket─→ [Action Cable サーバー]
                                      ↓
                               メッセージブローカー
                              (Redis or Solid Cable)
                                      ↓
                          [他のRailsプロセス] → 他のクライアント配信

複数の Rails プロセスがある場合、プロセス間で同じメッセージをブロードキャストするためのブローカーが必要です。これが従来はRedis pub/sub、Rails 8からSolid Cableで実現できるようになりました。

Solid Cable のしくみ

Redis pub/sub と違い、Solid Cable はポーリング型:

  1. メッセージブロードキャストが発生 → DBテーブルにINSERT
  2. 各 Action Cable インスタンスが 100ms ごとにDBをポーリング
  3. 新しいメッセージを取得して接続クライアントに配信
  4. 24時間経過後、autotrimで削除

Redis pub/sub との比較

観点Redis pub/subSolid Cable
遅延数ms(push方式)〜100ms(ポーリング)
インフラRedis必須DB のみ
スループット中(実用上は十分)
メッセージ永続化なし(即時配信のみ)あり(24時間)
デバッグ性困難(pub/subは消える)容易(DBで履歴確認)
接続上限Redis側で制御DBの接続プールに依存
コストRedis サーバー要追加コストなし

結論: 中小規模なら Solid Cable、超高頻度・低遅延が必要なら Redis や AnyCable を継続。


Solid Trifecta の中での位置づけ

Rails 8 の「Solid Trifecta」3兄弟:

Gem役割代替対象
Solid QueueバックグラウンドジョブSidekiq、Resque
Solid CacheキャッシュストアMemcached、Redis(cache用途)
Solid CableAction Cable WebSocketRedis pub/sub

Solid Cable はRails 8 アップグレードガイドの重要な構成要素です。


インストールとセットアップ

Rails 8 新規プロジェクト

rails new myapp
# Solid Cable は既に設定済み
bin/rails db:prepare

config/cable.ymldb/cable_schema.rb などが自動生成されます。

Rails 7.1+ への追加

# Gemfile に追加
bundle add solid_cable

# セットアップコマンド実行
bin/rails solid_cable:install

生成・変更されるもの:

  • config/cable.yml – Solid Cable 設定で更新
  • db/cable_schema.rb – メッセージテーブルのスキーマ(Rails 7.2+)
  • db/migrate/xxx_install_solid_cable.rb – マイグレーション
bin/rails db:migrate

config/database.yml の設定

Solid Cable 専用のDB接続を追加(マルチDB推奨):

# config/database.yml
production:
  primary:
    <<: *default
    database: myapp_production
  cable:
    <<: *default
    database: myapp_production_cable
    migrations_paths: db/cable_migrate

config/cable.yml の書き方

Solid Cable の挙動を制御する設定ファイルです。

基本構造

# config/cable.yml
default: &default
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day

development:
  <<: *default
  # 開発では同DBを使う設定も可能

test:
  adapter: test

production:
  <<: *default

主要パラメータ

adapter

使用するアダプタを指定:

adapter: solid_cable

選択肢:

  • solid_cable: 本記事の主役
  • redis: 従来の Redis pub/sub
  • postgresql: PostgreSQLのLISTEN/NOTIFY利用
  • async: 単一プロセス内のみ(開発・テスト用)
  • test: テスト専用

connects_to

使用するDB接続を指定:

connects_to:
  database:
    writing: cable    # config/database.yml の "cable" を使う

polling_interval

DBポーリングの頻度(秒):

polling_interval: 0.1.seconds   # デフォルト:100ms
  • 小さい値(例:0.05.seconds = 50ms): 低遅延だがDB負荷増
  • 大きい値(例:0.5.seconds = 500ms): 遅延増だがDB負荷減

message_retention

メッセージの保持期間:

message_retention: 1.day        # デフォルト:1日

これを過ぎたメッセージは autotrim で削除されます。

autotrim

自動削除を有効化するか:

autotrim: true   # デフォルト:true

無効化する場合は手動削除タスクが必要。

silence_polling

ポーリング時の Active Record ログを抑制:

silence_polling: true   # デフォルト:true

false にすると、開発ログにポーリングクエリが大量出力されます。

use_skip_locked

FOR UPDATE SKIP LOCKED を使用するか:

use_skip_locked: true   # デフォルト:true

⚠️ MySQL 8未満、PostgreSQL 9.5未満では false 必須。SQLiteでは無視される。


開発環境のセットアップ(重要な落とし穴)

デフォルトは async

Rails 8 でも開発環境はデフォルトで async アダプタになっています:

# config/cable.yml(デフォルト)
development:
  adapter: async

これは単一プロセス内でしか動作しないため、以下のケースで問題発生:

# rails console で broadcast → ブラウザに届かない!
Turbo::StreamsChannel.broadcast_action_to("stream_name", action: :update, ...)
# [ActionCable] Broadcasting to stream_name: "..."
# ↑ ログには出るが、別プロセスのブラウザには届かない

理由: bin/rails console と Pumaの実行プロセスが別物のため、async では別プロセスへ通知できません。

開発環境で Solid Cable を使う

# config/cable.yml
development:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable

これで bin/rails console 等からの broadcast もブラウザに反映されるようになります。

開発環境用の cable DB

# config/database.yml
development:
  primary:
    <<: *default
    database: storage/development.sqlite3
  cable:
    <<: *default
    database: storage/development_cable.sqlite3
    migrations_paths: db/cable_migrate
bin/rails db:prepare   # 全DB分作成

基本的な使い方

Action Cable の API は通常通り。Solid Cable は裏側で動くだけです。

Channel の定義

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_room_#{params[:room_id]}"
  end
  
  def unsubscribed
    # クリーンアップ処理
  end
  
  def send_message(data)
    ActionCable.server.broadcast(
      "chat_room_#{params[:room_id]}", 
      message: data["message"],
      user: current_user.name
    )
  end
end

サーバー側からのブロードキャスト

# Controller / Model / Job のどこからでも
ActionCable.server.broadcast("chat_room_1", { message: "Hello" })

# Stream名にオブジェクトを使う場合
ActionCable.server.broadcast(
  "user_#{user.id}_notifications",
  { type: "alert", body: "新着メッセージ" }
)

クライアント側 JavaScript

// app/javascript/channels/chat_channel.js
import consumer from "channels/consumer"

consumer.subscriptions.create(
  { channel: "ChatChannel", room_id: 1 },
  {
    received(data) {
      console.log("Received:", data)
      // DOM 更新等
    },
    
    speak(message) {
      this.perform("send_message", { message: message })
    }
  }
)

Turbo Streams との連携(最頻出ユースケース)

Hotwire の Turbo Streams は Action Cable と統合されており、Solid Cable がそのまま動作します。

Model からの Turbo Streams 配信

class Message < ApplicationRecord
  belongs_to :chat_room
  
  # 作成時に自動配信
  after_create_commit -> {
    broadcast_append_to chat_room, target: "messages"
  }
  
  # 更新時に置き換え
  after_update_commit -> {
    broadcast_replace_to chat_room
  }
  
  # 削除時に削除
  after_destroy_commit -> {
    broadcast_remove_to chat_room
  }
end

ワンライナーで設定:

class Message < ApplicationRecord
  belongs_to :chat_room
  broadcasts_to :chat_room   # すべてのCRUD自動配信
end

View側

<%# views/chat_rooms/show.html.erb %>
<%= turbo_stream_from @chat_room %>

<div id="messages">
  <%= render @chat_room.messages %>
</div>

turbo_stream_from で対象 stream を購読開始。Model の変更が自動的に DOM に反映されます。

Controller内での明示的な broadcast

class MessagesController < ApplicationController
  def create
    @message = current_user.messages.create!(message_params)
    
    # 明示的に broadcast(CRUDコールバック以外でも)
    Turbo::StreamsChannel.broadcast_append_to(
      @message.chat_room,
      target: "messages",
      partial: "messages/message",
      locals: { message: @message }
    )
    
    redirect_to chat_room_path(@message.chat_room)
  end
end

Hotwire パターン集

Solid Cable + Turbo Streams で実現できる主要パターン:

1. 新着メッセージのリアルタイム表示

class Message < ApplicationRecord
  belongs_to :chat_room
  broadcasts_to :chat_room
end
<%= turbo_stream_from @chat_room %>
<div id="messages">
  <%= render @chat_room.messages %>
</div>

これだけで、誰かがメッセージを作成すると全クライアントに即時反映。

2. ライブ通知

class Notification < ApplicationRecord
  belongs_to :user
  
  after_create_commit -> {
    broadcast_prepend_later_to(
      "user_#{user.id}_notifications",
      target: "notifications",
      partial: "notifications/notification"
    )
  }
end

broadcast_prepend_later_to でジョブキュー(Solid Queue)経由の非同期配信に。

3. ライブステータス更新

class Order < ApplicationRecord
  after_update_commit -> {
    broadcast_replace_later_to(self)
  } if -> { saved_change_to_status? }
end
<%# orders/_order.html.erb %>
<%= turbo_frame_tag dom_id(order) do %>
  <div class="status"><%= order.status %></div>
<% end %>

注文ステータスが変更されると、関連画面の表示が自動更新。

4. プログレス通知

class ProcessFileJob < ApplicationJob
  def perform(file_id)
    file = UploadedFile.find(file_id)
    total = file.size
    
    (0..total).step(1024).each do |bytes|
      file.process_chunk(bytes)
      
      Turbo::StreamsChannel.broadcast_update_to(
        file.user,
        target: "progress",
        html: "#{(bytes * 100 / total)}%"
      )
    end
  end
end

ジョブ処理進捗をリアルタイムでUIに反映。


マルチDB / Solid Trifecta 全部入り構成

Solid Trifecta すべてを使う典型構成:

# config/database.yml
production:
  primary:
    <<: *default
    database: myapp_production
  cache:
    <<: *default
    database: myapp_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *default
    database: myapp_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *default
    database: myapp_production_cable
    migrations_paths: db/cable_migrate
# config/cable.yml
production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day
# config/environments/production.rb
config.cache_store = :solid_cache_store
config.active_job.queue_adapter = :solid_queue

Rails 8 新規アプリでは自動的にこの構成になります。


本番運用のベストプラクティス

1. 専用 cable DBを用意

メインDBから分離することで、Action Cable の書き込み負荷が他に影響しないようにします。

2. polling_interval を調整

production:
  polling_interval: 0.1.seconds   # デフォルト
  • 低遅延が必要: 0.05.seconds(50ms)
  • DB負荷を抑えたい: 0.5.seconds(500ms)

実際のレイテンシ・DB負荷を計測して決定。

3. message_retention を業務要件に合わせる

production:
  message_retention: 1.day    # デフォルト
  • デバッグで履歴を残したい: 7.days
  • 容量重視: 6.hours

4. PostgreSQL なら LISTEN/NOTIFY 活用

PostgreSQL を使う場合、Solid Cable は内部的に LISTEN/NOTIFY を活用してポーリング間隔より高速な配信を実現できます。デフォルトで有効。

5. WebSocket 接続数の監視

-- アクティブな WebSocket 接続数
SELECT COUNT(*) FROM pg_stat_activity WHERE application_name LIKE '%ActionCable%';

接続数が増えすぎるとDB接続プールを圧迫。

6. DBプール調整

# config/database.yml
production:
  cable:
    pool: <%= ENV.fetch("CABLE_POOL_SIZE") { 25 } %>

WebSocket 接続数 × ピーク並行率 を見積もって設定。

7. Action Cable プロセスの分離

Web ワーカーと Action Cable を別プロセスにする構成も可能:

# config/puma.rb(標準では同居)

# 別プロセスで動かす場合(高負荷時)
# Action Cable 専用の puma 起動

8. Kamal 2 でのデプロイ

# config/deploy.yml
servers:
  web:
    - 192.168.1.100
  cable:
    hosts:
      - 192.168.1.101
    cmd: bin/rails server -p 28080

cable 専用サーバーで Action Cable をスケール。


Redis pub/sub からの移行手順

既存の Redis ベース Action Cable から Solid Cable への移行:

ステップ1:準備

# Gemfile
gem "solid_cable"   # 追加
bundle install
bin/rails solid_cable:install
bin/rails db:migrate

ステップ2:設定変更

# config/cable.yml
# 旧設定
production:
  adapter: redis
  url: <%= ENV["REDIS_URL"] %>

# 新設定
production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds

ステップ3:デプロイ

git commit -am "Migrate to Solid Cable"
git push
kamal deploy   # or your deploy method

⚠️ デプロイ中は既存WebSocket接続が一旦切れます。クライアント側は自動再接続するため大きな問題はありませんが、業務影響を考慮してメンテナンス時間帯に実施。

ステップ4:動作確認

# Solid Cable のメッセージテーブル確認
bin/rails runner "puts SolidCable::Message.count"

ステップ5:Redis 撤去

Action Cable 以外で Redis を使っていなければ:

# Gemfile
# gem "redis"   # 削除(他で使ってなければ)

⚠️ Sidekiq、Action Cable以外のキャッシュ、セッション等で Redis を使っている場合は残してください。


パフォーマンス・限界

Solid Cable の限界

  • DB の書き込み・読み込み負荷がボトルネック
  • 数千接続以上で Action Cable 自体が限界に達する
  • **超低遅延(10ms以下)**は polling 方式では難しい

スケーラビリティ目安

同時WebSocket接続推奨
〜100Solid Cable で問題なし
100〜1,000Solid Cable で動くがDB監視推奨
1,000〜5,000専用 cable DB + チューニング必須
5,000〜10,000Solid Cable では厳しい、AnyCable検討
10,000+AnyCable + Go サーバー

AnyCable との比較

観点Solid CableAnyCable
言語Ruby(Action Cable)Go サーバー
メモリ効率普通非常に良い
並行性スレッド軽量 goroutine
遅延〜100ms数ms
インフラDB のみAnyCable プロセス + Redis
学習コスト中〜高
適性中小規模大規模・低遅延要件

ベンチマーク参考

Solid Cable(PostgreSQL + NVMe SSD、polling_interval=0.1s):
- 100接続: 平均遅延 60ms
- 1,000接続: 平均遅延 100ms  
- 10,000接続: 性能劣化、対応困難

AnyCable(Go サーバー):
- 10,000接続: 平均遅延 5ms
- 100,000接続: 対応可能

トラブルシューティング

ブラウザに broadcast が届かない

最頻出原因: 開発環境が async のまま

# config/cable.yml
development:
  adapter: async   # ← これでは別プロセスからの broadcast が届かない

→ 開発でも Solid Cable に切り替え:

development:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable

接続が頻繁に切れる

WebSocket 接続が安定しない場合:

  1. Pumaのworker_timeout を長く
# config/puma.rb
worker_timeout 60
  1. タイムアウト設定
# config/initializers/action_cable.rb
ActionCable.server.config.worker_pool_size = 5
  1. リバースプロキシ設定(Nginx の場合)
location /cable {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
}

メッセージが遅延する

# polling_interval を短く
production:
  polling_interval: 0.05.seconds   # 50ms に短縮

ただしDB負荷が増えます。

DB容量が増え続ける

SolidCable::Message.count
# 100万件超え!?

# autotrim 確認
Rails.application.config_for(:cable)
# autotrim: true になっているか

autotrim: false の場合は手動削除タスクを設定:

# 古いメッセージ削除
SolidCable::Message.where("created_at < ?", 1.day.ago).delete_all

crontabで定期実行設定推奨。

「Connection failed」エラー

WebSocket接続失敗。確認順:

  1. Action Cable のマウント
# config/routes.rb
mount ActionCable.server => "/cable"
  1. CORSや認証問題
# config/application.rb
config.action_cable.allowed_request_origins = [
  "http://localhost:3000",
  /https:\/\/.*\.example\.com/
]
  1. 本番ではSSL必須
config.action_cable.url = "wss://example.com/cable"

Mac開発時の SSH 関連エラー

リモートデプロイ時のSSH関連エラーはSSH host key verification failed の記事参照。


Action Cable 認証

Solid Cable に直接関係はないですが、よく組み合わせるので解説:

connection.rb での認証

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user
    
    def connect
      self.current_user = find_verified_user
    end
    
    private
    
    def find_verified_user
      if verified_user = User.find_by(id: cookies.encrypted[:user_id])
        verified_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

Channel 単位での認可

class ChatChannel < ApplicationCable::Channel
  def subscribed
    chat_room = ChatRoom.find(params[:room_id])
    
    if chat_room.members.include?(current_user)
      stream_for chat_room
    else
      reject
    end
  end
end

よくある質問(FAQ)

Q1. Rails 7 でも使えますか?

Rails 7.1 以上 で利用可能。Gemfile に gem "solid_cable" を追加すればOK。Rails 8 ではデフォルト。

Q2. 本番環境で Redis pub/sub と比較して遅いですか?

ポーリング間隔分(〜100ms)の遅延があります。チャット・通知などの人間が体感する用途ではほぼ問題なし。ゲームやトレーディングなど低遅延要件では Redis または AnyCable 推奨。

Q3. polling_interval を小さくしすぎるとどうなる?

DB のCPU・I/O負荷が増加します。50ms(0.05.seconds)程度が現実的な下限。

Q4. async と solid_cable の違いは?

アダプタ動作用途
async同一プロセス内のみ開発・テスト
solid_cableDB経由で全プロセス共有本番・複数プロセス

開発で bin/rails console からの broadcast を確認したい場合は solid_cable に切り替えが必要。

Q5. Hotwire / Turbo Streams は Solid Cable で動きますか?

完全に動作します。Turbo Streams は内部で Action Cable を使っているため、Solid Cable に切り替えるだけで Redis 不要のリアルタイム機能が手に入ります。

Q6. AnyCable と併用できますか?

技術的には可能ですが、通常はどちらか一方を選択します。AnyCable を使う規模なら Solid Cable は不要、Solid Cable で十分な規模なら AnyCable は過剰、という関係です。

Q7. message_retention を長くしてもいい?

DB容量と相談。1日(デフォルト)で大半のデバッグ需要は満たせます。長期保持したい場合は別途ログテーブルへ転送が現実的。

Q8. 開発環境で動くがブラウザに届かない

最頻出: config/cable.ymldevelopment セクションが async のまま。solid_cable に変更してください。

Q9. WebSocket 接続が大量にあるアプリで使えますか?

数千接続までは工夫次第で対応可能。それ以上は Action Cable 自体の限界が先に来るため、AnyCable へ移行検討。

Q10. Redis 不要になっても Sidekiq では Redis が必要では?

その通りです。完全に Redis を消したい場合は Solid Queue(ジョブ)と Solid Cache(キャッシュ)も組み合わせて Solid Trifecta フルセットへ移行。

Q11. Solid Cable のメッセージはバックアップすべき?

通常は不要。メッセージは一時的なものでアプリ再起動で再生成される設計。バックアップから除外して容量節約推奨。

Q12. Action Cable の代替として ActionMailer や Polling Ajax と比較すると?

用途推奨
リアルタイム性が必須Action Cable + Solid Cable
メール通知でOKActionMailer
数分遅れOKPolling Ajax で十分
数十ms単位の低遅延AnyCable

UX要件と運用負荷で判断。


参考リンク・関連資料

公式ドキュメント

関連プロジェクト

関連記事(本サイト)


まとめ

Solid Cable はRails 8 のリアルタイム機能を「Redis不要」で実現する重要な機能です。要点を再整理します。

  • 設計思想: DBポーリングで Action Cable をシンプルに
  • シンプル設定: bin/rails solid_cable:install + DB設定で即動く
  • 開発環境注意: デフォルトの async から solid_cable への切り替え推奨
  • 設定の中心: config/cable.yml で polling_interval / message_retention / autotrim を制御
  • マルチDB推奨: cable 専用DBで影響分離
  • Hotwire 完全対応: Turbo Streams が Redis なしで動作
  • 遅延: 〜100ms(ポーリング間隔次第)
  • スケーラビリティ: 数千接続まで実用、それ以上は AnyCable 検討
  • Redis 撤去: Solid Trifecta フルセットで完全に Redis を消せる
  • 適性: 中小規模・MVPなら最適、超大規模・低遅延なら AnyCable

これらの知識は、Rails 8 でリアルタイム機能を設計・実装・運用するあらゆる場面で活用できます。本記事をブックマークしておけば、Solid Cable を実戦投入してRedisなしのモダンなRailsアプリを構築できるようになります。


本記事は2026年6月時点の情報をもとに、Ruby on Rails 8.0.5 / 8.1.3 / 8.2.x、Solid Cable 最新版での動作確認・公式ドキュメントに基づき作成しています。Solid Cable は活発に開発されており、設定方法が更新される可能性があるため、最新の情報は公式リポジトリもあわせてご確認ください。