Solid Cable README: DBベースのAction Cableアダプタ(翻訳)
Solid Cableは、データベースを用いるAction Cableアダプタです。メッセージをデータベースのテーブルに保存して、更新があるかどうかを定期的にポーリングします。
これにより、それ以外の目的でRedisが不要な場合に、Redisへの一般的な依存を削除できます。Action Cableの機能でRedisが必須でなくなれば、Railsアプリをデプロイしやすくなります。
注
Solid Cableは、MySQL、SQLite、PostgreSQLで動作することがテストされています。
Action Cableには既に専用のPostgreSQLアダプタが存在しており、組み込みのNOTIFY
コマンドでパフォーマンスを強化しています。ただし、このアダプタにはペイロードが8KBまでという制限があります。ブロードキャストするペイロードが大きい場合や、NOTIFY
コマンドを使いたくない場合は、Solid Cableが最適な代替手段となります。
🔗 インストール
新しいRails 8では、Solid Cableがデフォルトで設定されます。以前のバージョンのRailsを使っている場合は、以下の手順に沿って手動でSolid Cableを追加できます。
bundle add solid_cable
bin/rails solid_cable:install
上を実行すると、config/cable.yml
を上書きしてdb/cable_schema.rb
を作成し、Solid Cableがproduction向けのケーブルアダプタとして使われるようになります。
次に、ケーブルデータベースの設定をconfig/database.yml
に追加する必要があります。SQLiteを使っている場合は、以下のように設定します。
production:
primary:
<<: *default
database: storage/production.sqlite3
cable:
<<: *default
database: storage/production_cable.sqlite3
migrations_paths: db/cable_migrate
MySQL/PostgreSQL/Trilogyを使っている場合は、以下のように設定します。
production:
primary: &primary_production
<<: *default
database: app_production
username: app
password: <%= ENV["APP_DATABASE_PASSWORD"] %>
cable:
<<: *primary_production
database: app_production_cable
migrations_paths: db/cable_migrate
注
config/cable.yml
ファイルは、bin/rails solid_cable:install
を呼び出せば自動的にセットアップされるので、設定の追加は不要です(ただし、 database.yml
内のcable
名が一致するようにしておかなければなりません)。
ただし、Solid Cableを別の環境(staging環境やdevelopment環境など)で利用したい場合は、config/cable.yml
ファイル内で対応する環境に手動でconnects_to
ブロックを追加しなければなりません。この場合も同様に、 config/cable.yml
で使われるデータベース名がconfig/database.yml
で定義されているデータベース名と一致させること。
続いて、production環境で db:prepare
を実行して、データベースを作成してスキーマが読み込まれるようにします。
🔗 単一データベースでの利用について
Solid Cableは、別のデータベースで実行することが推奨されますが、単一のデータベースをアプリとAction Cableで兼用することも可能です。
db/cable_schema.rb
の内容を通常のマイグレーションにコピーしてから、db/cable_schema.rb
を削除するconfig/cable.yml
に記載されているconnects_to
ブロックを削除するbin/rails db:migrate
を実行する
この場合はマルチデータベースではないので、database.yml
にprimaryデータベースとcableデータベースを持つ必要はありません。
🔗 設定方法
Solid Cableのすべての設定は、config/cable.yml
ファイルで管理されます。デフォルトでは以下のように設定されます。
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 1.day
利用可能なオプションは以下のとおりです。
connects_to
- Active Recordで使うデータベースをSolid Cableモデル用に設定します。ここでは、Active Recordで利用可能な全オプションを利用できます。
polling_interval
- ポーリング間隔の頻度を設定します(デフォルトは0.1秒)。
message_retention
- メッセージをデータベースに保持する期間を設定します。メッセージのトリミングを実行するときのカットオフ値として使われます(デフォルトは1日)。
autotrim
- Solid Cableでメッセージの自動トリミングを有効にするかどうかを設定します(デフォルトは
true
)。 silence_polling
- ポーリング時のActive Recordログ出力を抑制するかどうかを指定します(デフォルトは
true
)。 use_skip_locked
- トリミング実行中に
FOR UPDATE SKIP LOCKED
を使うかどうかを指定します(デフォルトはtrue
)。これは今後自動検出されるようになる予定ですが、現時点では、この機能をサポートしていないデータベース(MySQL < 8、PostgreSQL < 9.5)を使う場合はfalse
を指定する必要があります。SQLiteはシーケンシャル書き込みなので影響しません。 trim_batch_size
- 古いレコードを削除するときのバッチサイズを指定します(デフォルトは
100
)。
🔗 メッセージのトリミング機能
Solid Cableのメッセージは、message_retention
設定(メッセージを保持する期間を決定する)に基づいて自動トリミング(削除)されます。message_retention
が指定されていない場合や、設定の解析に失敗した場合は、デフォルトの1.day
が設定されます。メッセージがブロードキャストされると、メッセージがトリミングされます。
自動トリミングは、ブロードキャスト時に削除を実行する可能性があるため、負荷によってはパフォーマンスに悪影響が生じることもあります。必要であれば、autotrim: false
で自動トリミングを無効にして、後でトリミング用のジョブ(SolidCable::TrimJob.perform_later
)を手動でエンキューすることも、負荷の小さい時間帯に定期実行することも可能です。
🔗 アップグレードについて
Solid Cableバージョン3未満を既にインストールしていて、Solid Cable 3にアップグレードする場合は、solid_cable:update
を実行して新しいマイグレーションをインストールしてください。
🔗 ベンチマーク
Solid Cableのbench/
ディレクトリには、ベンチマーク用の最小限のRailsアプリが置かれています。
このアプリを独自サーバーにデプロイしてベンチマークを実行したい場合は、config/deploy.yml
ファイルを更新して独自サーバーを指すようにしてください。
ベンチマークにはk6を使っています。セットアップのほとんどは以下の記事から取得しています。
参考: Real-time stress: AnyCable, k6, WebSockets, and Yabeda—Martian Chronicles, Evil Martians’ team blog
- k6をインストールする
xk6 build --with
を実行してxk6-cableをインストールする。
github.com/anycable/xk6-cable
これにより、k6のカスタムバイナリが出力されます。./k6 run loadtest.js
を実行して負荷テストを行います。- このスクリプトにはさまざまな環境変数を渡せます。
WS_URL
: Websocketコネクションの送信先URLMAX
: サーバーにアクセスする仮想ユーザー数TIME
: 負荷テストの実行期間MESSAGES_NUM
: 個別のVU(virtual users)がサーバーに送信するメッセージの件数
- このスクリプトにはさまざまな環境変数を渡せます。
🔗 結果
以下の負荷テストはHetzner CCX13で実行され、MESSAGES_NUM
には5、TIME
には90を指定しました。
🔗 SQLite
- ポーリング間隔: 0.1秒
- 自動トリミング: オン
100 VUの場合:
rtt..................: avg=135.82ms min=50ms med=138ms max=357ms p(90)=174ms p(95)=195ms
ws_connecting........: avg=205.81ms min=149.35ms med=199.01ms max=509.48ms p(90)=254.04ms p(95)=261.77ms
250 VUの場合:
rtt..................: avg=146.24ms min=50ms med=144ms max=435ms p(90)=209ms p(95)=234.04ms
ws_connecting........: avg=222.15ms min=146.47ms med=208.57ms max=1.3s p(90)=263.6ms p(95)=284.18ms
500 VUの場合:
rtt..................: avg=271.79ms min=48ms med=205ms max=1.15s p(90)=558ms p(95)=660ms
ws_connecting........: avg=248.81ms min=145.89ms med=221.89ms max=1.38s p(90)=290.41ms p(95)=322.2ms
750 VUの場合:
rtt..................: avg=548.27ms min=51ms med=438ms max=5.19s p(90)=1.18s p(95)=1.29s
ws_connecting........: avg=266.37ms min=144.06ms med=224.93ms max=2.33s p(90)=298ms p(95)=342.87ms
- 自動トリミング: オフ
250 VUの場合:
rtt..................: avg=139.47ms min=48ms med=142ms max=807ms p(90)=189ms p(95)=214ms
ws_connecting........: avg=212.58ms min=146.19ms med=196.25ms max=1.25s p(90)=255.74ms p(95)=272.44ms
- ポーリング間隔: 0.01秒(Radisに匹敵する値)
250 VUの場合:
rtt..................: avg=84.22ms min=43ms med=69ms max=416ms p(90)=137ms p(95)=150ms
ws_connecting........: avg=219.37ms min=144.71ms med=200.77ms max=2.17s p(90)=265.23ms p(95)=290.83ms
🔗 Redis
- インスタンス: 同じマシン上にホスティング
100 VUの場合:
rtt..................: avg=68.95ms min=41ms med=56ms max=6.23s p(90)=114ms p(95)=129ms
ws_connecting........: avg=211.09ms min=153.23ms med=195.69ms max=1.44s p(90)=258.1ms p(95)=272.23ms
250 VUの場合:
rtt..................: avg=69.32ms min=40ms med=56ms max=645ms p(90)=119ms p(95)=135ms
ws_connecting........: avg=212.95ms min=142.92ms med=196.31ms max=1.25s p(90)=260.25ms p(95)=273.49ms
500 VUの場合:
rtt..................: avg=87.5ms min=40ms med=67ms max=839ms p(90)=149ms p(95)=176ms
ws_connecting........: avg=242.62ms min=142.03ms med=213.76ms max=2.34s p(90)=291.25ms p(95)=324.04ms
750 VUの場合:
rtt..................: avg=162.54ms min=39ms med=123ms max=2.26s p(90)=343.1ms p(95)=438ms
ws_connecting........: avg=353.08ms min=143ms med=264.15ms max=2.73s p(90)=541.36ms p(95)=1.15s
🔗 MySQL
- ポーリング間隔: 0.1秒
- 自動トリミング: オン
- インスタンス: 同じマシン上にホスティング
100 VUの場合:
rtt..................: avg=136.02ms min=51ms med=137ms max=877ms p(90)=168.1ms p(95)=198ms
ws_connecting........: avg=207.76ms min=151.93ms med=196.74ms max=1.21s p(90)=249.91ms p(95)=260.37ms
250 VUの場合:
rtt..................: avg=159.33ms min=51ms med=149ms max=559ms p(90)=236ms p(95)=263ms
ws_connecting........: avg=232.38ms min=151.6ms med=218.09ms max=1.38s p(90)=287.99ms p(95)=324.6ms
500 VUの場合:
rtt..................: avg=441.07ms min=51ms med=312ms max=2.29s p(90)=931ms p(95)=1.07s
ws_connecting........: avg=256.73ms min=152.23ms med=231.02ms max=2.31s p(90)=305.69ms p(95)=340.83ms
750 VUの場合:
rtt..................: avg=822.08ms min=51ms med=732ms max=5.05s p(90)=1.76s p(95)=1.97s
ws_connecting........: avg=278.08ms min=146.66ms med=236.35ms max=2.37s p(90)=318.17ms p(95)=374.98ms
🔗 PostgreSQLをSolid Cableと併用した場合
- ポーリング間隔: 0.1秒
- 自動トリミング: オン
- インスタンス: 同じマシン上にホスティング
100 VUの場合:
rtt..................: avg=137.45ms min=48ms med=139ms max=439ms p(90)=179.1ms p(95)=204ms
ws_connecting........: avg=207.13ms min=150.29ms med=197.76ms max=443.67ms p(90)=254.44ms p(95)=263.29ms
250 VUの場合:
rtt..................: avg=151.63ms min=49ms med=146ms max=538ms p(90)=222ms p(95)=248.04ms
ws_connecting........: avg=245.89ms min=147.18ms med=205.57ms max=30s p(90)=265.08ms p(95)=281.15ms
500 VUの場合:
rtt..................: avg=362.79ms min=50ms med=249ms max=1.21s p(90)=757ms p(95)=844ms
ws_connecting........: avg=257.02ms min=146.13ms med=227.65ms max=2.39s p(90)=303.22ms p(95)=344.39ms
🔗 PostgreSQLを専用アダプタで使った場合
100 VUの場合:
rtt..................: avg=69.76ms min=41ms med=57ms max=622ms p(90)=116ms p(95)=133ms
ws_connecting........: avg=210.97ms min=149.68ms med=196.06ms max=1.27s p(90)=259.67ms p(95)=273.17ms
250 VUの場合:
rtt..................: avg=73.43ms min=40ms med=58ms max=698ms p(90)=126ms p(95)=141ms
ws_connecting........: avg=210.83ms min=143.01ms med=195.22ms max=1.27s p(90)=259.27ms p(95)=272.6ms
🔗 ライセンス
このgemは、MITライセンスの条項の下でオープンソースとして利用可能です。
概要
MITライセンスに基づいて翻訳・公開いたします。