WebSocket機能が不安定な時のスケール改善策
リリース直後は問題なかったチャットや通知機能が、ユーザーが増えるにつれて「メッセージが届かない」「頻繁に切断される」「サーバーのメモリが徐々に膨らんでいく」と崩れていく——WebSocketを使ったリアルタイム機能でよく聞く相談です。API のレスポンスタイムのようにわかりやすい指標がなく、原因も「なんとなく不安定」としか報告されないまま放置されがちな領域でもあります。
WebSocketは一度接続すると張りっぱなしになる性質上、通常のHTTPリクエストとはスケールの考え方がまったく異なります。この記事では、接続数増加で不安定になる典型的な原因と、規模に応じた改善の選択肢を、発注者が判断できる粒度で整理します。
なぜ接続数が増えると壊れるのか
WebSocketは「リクエストが来て、処理して、返す」ではなく「接続を張ったまま、双方向にメッセージをやり取りし続ける」仕組みです。この違いが、増えたときに壊れる理由の根っこになっています。
1台あたりの接続数に上限がある
WebSocket接続は1本ごとにファイルディスクリプタとメモリを消費し続けます。サーバーのOS設定(ulimit -n)やアプリケーションサーバーのワーカー数によっては、数千接続を超えたあたりから新規接続が EMFILE(ファイルディスクリプタ不足)で拒否されるようになります。Rails の ActionCable であれば、Pumaのスレッド数・ワーカー数と接続数の関係を見落としているケースが典型です。1ワーカーが同時に処理できる接続には現実的な上限があり、「CPU使用率は低いのに新規接続が失敗する」という一見矛盾した症状が出たら、まずこれを疑います。
スティッキーセッションが前提になっている
WebSocketはコネクション確立時に1台のサーバーと接続を固定します。ロードバランサーの設定がラウンドロビンのままだと、再接続のたびに別サーバーに割り振られて認証・状態の再構築が走り、接続が不安定に見えます。逆にスティッキーセッションを強く効かせすぎると、特定のサーバーだけ接続が偏り、そのサーバーだけメモリ・CPUが逼迫する「ホットスポット」が生まれます。スケールする構成では、特定サーバーへの固定に依存しない設計への転換が必要になる、という点が単純なHTTPのスケールと大きく違うところです。
サーバー間でメッセージが届かない
サーバーを2台以上に増やした瞬間に踏む典型的な罠がこれです。ユーザーAがサーバー1に接続し、ユーザーBがサーバー2に接続している状態で、Aが送ったメッセージをBに届けるには、サーバー1とサーバー2の間でメッセージを中継する仕組みが要ります。単純にサーバー台数を増やしただけでは、同じサーバーに接続しているユーザー同士にしかメッセージが届かないという不具合が起きます。1台構成で問題なく動いていた機能が、スケールした瞬間に壊れる最大の原因はこれです。
再接続処理が雑で「幽霊接続」が溜まる
クライアント側のネットワーク切断(スマホのスリープ、Wi-Fi切り替えなど)は日常的に起きますが、サーバー側がそれを検知できないと、実際には切れている接続を「生きている」ものとしてメモリ上に持ち続けます。これが積み重なると、実接続数以上にメモリを消費し、最終的にサーバーがOOM(メモリ不足)で落ちます。「起動直後は快適だが数時間〜数日でメモリが増え続けて再起動が必要になる」という症状は、この幽霊接続が原因であることがほとんどです。
スケール改善の選択肢
原因が特定できたら、規模と予算に応じて打ち手を選びます。すべてを一度にやる必要はなく、今のボトルネックがどれかを先に見極めるのが遠回りしないコツです。
水平スケール + Redis pub/sub
複数サーバー間でメッセージを配信する定番構成です。Railsの ActionCable であれば cable.yml のアダプタを redis に切り替え、各サーバーがRedisのpub/subチャンネルを購読することで、どのサーバーに接続しているユーザーにもメッセージが届くようになります。Go や FastAPI で自前実装している場合も考え方は同じで、Redis(またはNATSなどのメッセージブローカー)を「サーバー間の掲示板」として挟みます。
- 効果が出やすい症状: サーバーを増やしたらメッセージが届かなくなった、特定ユーザー同士だけ通知が来ない
- 注意点: Redis自体が単一障害点にならないよう、Redis Sentinel やマネージドRedis(AWS ElastiCache等)の冗長構成を前提にする。Redisがボトルネックになるほどの規模ではさらに別の設計が必要
接続管理をアプリケーションサーバーから切り離す
接続数がさらに増えてくると、「HTTPリクエストを処理するサーバー」と「WebSocket接続を保持するサーバー」を分離する設計に移行します。接続保持専用のプロセス(Node.jsのソケットサーバーや、Go の goroutine ベースの実装は1台で数万接続を扱える)を別に立て、ビジネスロジックはAPIサーバー側に残す構成です。これによりWebSocketサーバーのスケールとAPIサーバーのスケールを独立して行えるようになります。
- 効果が出やすい症状: 1台あたりの接続上限に頻繁に当たる、WebSocket機能のためだけにAPIサーバー全体をスケールさせていて非効率
- 注意点: 構成が複雑になる分、運用・監視の手間が増える。数千接続程度ならオーバーエンジニアリングになりがち
ハートビートで幽霊接続を機械的に排除する
クライアントから一定間隔(例: 30秒)でping、サーバーがpongを返す仕組みを入れ、一定時間応答がない接続を強制的に切断します。実装コスト自体は小さいわりに、メモリリークとして蓄積していた幽霊接続を確実に減らせるため、費用対効果が最も高い改善策であることが多いです。既存のWebSocket機能にハートビートが入っていない場合、まずここから着手することを勧めます。
- 効果が出やすい症状: 稼働時間が長くなるほどメモリ使用量が増える、再起動すると一時的に安定する
- 注意点: ハートビート間隔を短くしすぎるとモバイル回線でのバッテリー消費・通信量が増える。3〜5分程度のタイムアウトが現実的な落とし所になることが多い
フォールバック(ロングポーリング等)を用意する
企業のプロキシやファイアウォールでWebSocketがブロックされる環境は依然として存在します。Socket.IOのような抽象化ライブラリを使う、あるいは独自実装でもロングポーリングへのフォールバックを持たせることで、「一部のユーザーだけリアルタイム機能が使えない」という個別問い合わせの対応工数を減らせます。BtoB向けプロダクトで顧客企業のネットワーク制約が読めない場合は、優先度を上げて検討する価値があります。
Managed(Pusher等)への移行を検討すべき判断基準
自前でスケール対応を積み上げるより、Pusher・Ably・AWS AppSync(GraphQL Subscriptions)といったマネージドサービスに乗せ換えたほうが早い場合もあります。判断基準は次の通りです。
- エンジニアリソースが薄い: 接続管理・スケール・監視を自前で運用し続ける人的リソースが確保できないなら、月額費用を払ってでもManagedに寄せたほうがトータルコストは低くなることが多い
- 接続数の伸びが読めない: バズった場合の急激な接続増に、自前インフラのオートスケール設定だけで追従するのはリスクが高い。Managedサービスはこの吸収を前提に作られている
- リアルタイム機能がコア機能ではない: 通知バッジの更新程度であれば、自前で複雑な構成を持つより、Managedで素早く安定させて開発リソースをコア機能に回すほうが合理的
逆に、メッセージ内容にセンシティブな情報が含まれる、接続数がすでに数万〜数十万規模で従量課金がコストを圧迫する、といった場合は自前構成を磨き込むほうが長期的に有利です。移行は「全面置き換え」でなくても、新規のリアルタイム機能だけManagedを使い、既存部分は段階的に改善するというハイブリッドも現実的な選択肢です。
外注時の進め方
外部チームに依頼する場合、いきなり大規模な再設計から入るのはリスクが高いです。私たちが実際に進めるときは、まず現状の接続数・切断率・メモリ推移をログやモニタリングから可視化し、上記のどの原因が支配的かを1〜2週間の診断で切り分けます。その上で、ハートビート導入のような低コストな改善から着手し、効果を見ながら水平スケールやManaged移行の要否を判断する順番を基本にしています。原因を特定せずに「とりあえずサーバーを増やす」対応は、Redis pub/subのようなサーバー間配信の仕組みがなければ症状を悪化させるだけになるため、最初の切り分けが最も重要な工程です。
既存プロダクトの改善全体をどう進めるかについては、既存プロダクトの改善、外部チームが最初に見る観点で詳しく解説しています。またキャンペーンやイベントで急激なアクセス増が見込まれる場合の備えは、大型キャンペーン前に確認すべき負荷対策も参考にしてください。
まとめ
- WebSocketが不安定になる原因は、接続数の上限・スティッキーセッションの設計・サーバー間のメッセージ配信・再接続処理の甘さのいずれかに集約されることが多く、まず切り分けが必要
- 改善はハートビート導入のような低コスト施策から始め、必要に応じてRedis pub/subによる水平スケール、接続管理の分離へと段階的に進めるのが合理的
- エンジニアリソースが薄い、または接続数の伸びが読めない場合はPusher等のManagedサービスへの移行も有力な選択肢になる
torcheees では Ruby on Rails の ActionCable から Go・FastAPI での自前実装まで、リアルタイム機能のスケール改善を手がけています。インフラ全体の構成についてはインフラ・AWS、改善のご依頼はモダナイゼーションのサービスとして、1〜4週間の開発診断からご相談いただけます。WebSocket機能の不安定さでお困りの場合は、お問い合わせフォームからお気軽にご連絡ください。