Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Solid Cable README: DBベースのAction Cableアダプタ(翻訳)

概要

MITライセンスに基づいて翻訳・公開いたします。

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を追加できます。

  1. bundle add solid_cable
  2. 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で兼用することも可能です。

  1. db/cable_schema.rbの内容を通常のマイグレーションにコピーしてから、db/cable_schema.rbを削除する
  2. config/cable.ymlに記載されているconnects_toブロックを削除する
  3. 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

  1. k6をインストールする
  2. xk6 build --with
    github.com/anycable/xk6-cable
    を実行してxk6-cableをインストールする。
    これにより、k6のカスタムバイナリが出力されます。
  3. ./k6 run loadtest.jsを実行して負荷テストを行います。
    • このスクリプトにはさまざまな環境変数を渡せます。
      • WS_URL: Websocketコネクションの送信先URL
      • MAX: サーバーにアクセスする仮想ユーザー数
      • 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ライセンスの条項の下でオープンソースとして利用可能です。

関連記事

Solid Cache README: DBベースのキャッシュストア(翻訳)

Solid Queue README -- DBベースのActive Jobバックエンド(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。