ライブダッシュボード向けに WebSockets と Server-Sent Events を解説。選び方の簡単なルール、スケーリングの基本、接続が切れたときの対処法。

ライブダッシュボードは要するに約束です:ページを再読み込みしなくても数字が変わり、表示は現在の状態に近いこと。ユーザーは更新が速く感じること(多くは1〜2秒以内)を期待しますが、同時に画面は落ち着いていてほしい。チラつきやグラフのジャンプ、数分ごとに出る「切断」バナーは避けたいところです。
ほとんどのダッシュボードはチャットアプリではありません。サーバーからブラウザへのプッシュが主で、新しいメトリクスポイント、ステータスの変化、新しい行の束、あるいはアラートが届くことが多い。よく見る形としては、メトリクスボード(CPU、サインアップ数、収益)、アラートパネル(緑/黄/赤)、ログテール(最新イベント)、進捗ビュー(ジョブが63%→64%)などです。
WebSockets と Server-Sent Events(SSE)の選択は単なる技術的嗜好ではありません。書くコード量、扱うべき奇妙なエッジケースの数、50人が5,000人になったときの費用に影響します。ロードバランスしやすい選択肢もあれば、再接続や差分取得のロジックが簡単になる選択肢もあります。
目標は単純です:正確で応答性が高く、成長してもオンコール地獄にならないダッシュボード。
WebSockets と Server-Sent Events はどちらも接続を開いたままにして、ダッシュボードがポーリングせずに更新できるようにします。違いは会話の仕方です。
WebSockets を一言でいうと:ブラウザとサーバーの双方がいつでもメッセージを送れる、単一の長期接続。
SSE を一言でいうと:サーバーがブラウザへ継続的にイベントをプッシュする長期の HTTP 接続で、同じストリーム上でブラウザがメッセージを送ることは基本的にない。
その違いが自然に感じられる方を決めます。
具体例:収益、アクティブトライアル、エラーレートだけを表示する営業KPIウォールボードは SSE で十分動きます。一方、ユーザーが注文を出し、確認を受け、各操作に即時フィードバックが必要なトレーディング画面は WebSocket 向けです。
どちらを選んでも変わらない事項がいくつかあります:
輸送手段は最後の一里。難しい部分はどちらでも似ています。
主な違いは誰がいつ話せるかです。
Server-Sent Events ではブラウザが長期接続を開き、サーバーだけがそのパイプで更新を下ろします。WebSockets では接続が双方向で、ブラウザもサーバーもいつでもメッセージを送れます。
多くのダッシュボードではトラフィックの大部分がサーバー→ブラウザです。「新しい注文が到着」「CPU が 73%」「チケット数が変わった」といった更新が主なので、クライアントは受信中心であり、SSE がその形に合います。
ダッシュボードがコントロールパネルも兼ねる場合、クライアントが頻繁にアクションを送るなら WebSockets の方が自然です。頻繁なアクション(アラートの確認、共有フィルタの変更、共同操作)を都度新しいリクエストで処理するより、一つの双方向チャネルの方が楽なことがあります。
メッセージペイロードはどちらでも通常はシンプルな JSON イベントです。一般的なパターンはクライアントが安全にルーティングできるように小さなエンベロープを送ることです:
{"type":"metric","name":"active_users","value":128,"ts":1737052800}
ファンアウト(1つの更新を多くの視聴者に届ける)はダッシュボードで面白くなる点です。SSE も WebSockets も同じイベントを何千ものオープン接続にブロードキャストできます。違いは運用面で、SSE は長い HTTP レスポンスのように振る舞い、WebSockets はアップグレード後に別プロトコルへ切り替わる点です。
ライブ接続があっても、初回ページロード、過去データ、エクスポート、作成/削除アクション、認証更新、大きなクエリなどは通常の HTTP を使います。
実用的なルール:ライブチャネルは小さく頻繁なイベントに、HTTP はそれ以外に使う。
ダッシュボードがサーバーからブラウザへの一方向プッシュだけで足りるなら、SSE の方がシンプルで勝つことが多いです。SSE は開いたままの HTTP レスポンスで、テキストイベントを順に送るだけなので、動く部品が少なくエッジケースも減ります。
クライアントが頻繁に双方向でやり取りするなら WebSockets が優れますが、その自由は保守しなければならないコードを増やします。
SSE ではブラウザが接続してイベントを受け取り処理します。大抵のブラウザで再接続や基本的なリトライが組み込まれているので、接続状態よりもイベントのペイロードに集中できます。
WebSockets ではすぐにソケットライフサイクルが第一級の扱いになります:接続、open、close、error、再接続、時には ping/pong。メッセージタイプが多くなると(フィルタ、コマンド、確認、プレゼンスのような信号)、クライアントとサーバー双方でエンベロープとルーティングが必要になります。
経験則:
SSE は通常の HTTP のように振る舞うためデバッグが比較的簡単です。ブラウザの開発者ツールでイベントが見え、プロキシや可観測性ツールも HTTP を理解していることが多いです。
WebSockets は気づきにくい形で失敗することがあります。よくある問題はロードバランサーによる無音の切断、アイドルタイムアウト、「ハーフオープン」接続(片方は接続していると思っているが実際は切れている)などです。問題に気づくのはユーザーがステールなダッシュボードを報告してから、ということが多いです。
例:売上ダッシュボードがライブ合計と最近の注文だけ必要なら SSE でシステムは安定し可読性も高いです。同じページで迅速なユーザー操作(共有フィルタ、共同編集)が必要なら WebSockets の複雑さに投資する価値があります。
ダッシュボードが数人から数千人に増えるとき、主な問題は生の帯域幅ではありません。問題は生きた接続数と、遅いまたは不安定なクライアントが発生したときに何が起きるかです。
100人程度なら両者は似ています。1,000人になると接続上限やタイムアウト、再接続頻度が気になり始めます。50,000人では接続依存のシステム運用になります:クライアントごとに余分なキロバイトをバッファするとメモリ圧迫が現実的になります。
差はロードバランサーで現れることが多いです。
WebSockets は長期の双方向接続なので、スティッキーセッションが必要になる構成や、共有の pub/sub 層が無いとどのサーバーでも任意のユーザーを扱えないといった問題が出ます。
SSE も長期接続ですが平文の HTTP なので既存のプロキシと相性が良く、ファンアウトしやすいことが多いです。
ダッシュボードでサーバーをステートレスに保つのは SSE の方が簡単なことが多いです:サーバーは共有ストリームからイベントをプッシュするだけでクライアントごとの状態をあまり覚える必要がありません。WebSockets ではコネクションごとの状態(購読、最後に見た ID、認証コンテキスト)を保存しがちで、水平スケールを考えると早めに設計しておく必要があります。
遅いクライアントはどちらの方式でも静かに問題を引き起こします。見張るべき失敗モード:
人気のあるダッシュボードに対する単純なルール:メッセージは小さく、思っているより頻度を落とし、更新をドロップしたり集約したり(最新の値だけ送るなど)して、遅いクライアントがシステム全体を引きずらないようにする。
ライブダッシュボードは退屈な理由で壊れます:ラップトップがスリープする、Wi‑Fi が切り替わる、モバイルがトンネルを通る、ブラウザがバックグラウンドタブをサスペンドする。接続の選択よりも重要なのは、切れたときにどう回復するかです。
SSE では多くのブラウザに再接続が組み込まれています。ストリームが切れると短い遅延の後に自動で再試行します。多くのサーバーはイベント id を使ったリプレイをサポートしており(Last-Event-ID スタイルのヘッダー)、クライアントが「最後に見たイベントは1042なので、見逃したものを送ってください」と言えるような簡単な回復経路が作れます。
WebSockets は通常クライアント側のロジックが多く必要です。ソケットが閉じたらクライアントはバックオフとジッターで再試行し(何千ものクライアントが同時に再接続するのを避ける)、再接続後は明確な再購読フローが必要です:必要なら再認証して正しいチャンネルに再参加し、見逃した更新を要求します。
より大きなリスクはサイレントなデータギャップです:UI は見た目には問題なさそうだが実際は時代遅れ。ダッシュボードが最新であることを証明するために次のパターンを使うとよいです:
例:1分あたりの注文数を示す売上ダッシュボードは30秒ごとに合計を更新すれば短いギャップを許容できます。トレーディングダッシュボードは許容できません。すべてのイベントが重要なので、再接続時にシーケンス番号とスナップショットが必要です。
ライブダッシュボードは長期接続を開いたままにするので、小さな認証ミスが数分あるいは数時間残ることがあります。セキュリティはトランスポート自体より、認証・認可・セッションの有効期限管理が重要です。
基本から始めましょう:HTTPS を使い、すべての接続を期限付きセッションとして扱うこと。セッションクッキーを使うならスコープとローテーションを正しく設定する。トークン(JWT 等)を使うなら短命にして、クライアントがどう更新するか計画すること。
実用的な注意点:ブラウザの SSE(EventSource)はカスタムヘッダーを設定できないため、クッキー認証に流れがちか、URL にトークンを載せることになります。URL トークンはログやコピーペーストで漏れる可能性があるので、使うなら短命にしてクエリ文字列をログに残さない工夫を。WebSockets はハンドシェイク時に(クッキーやクエリで)認証するか、接続後に最初のメッセージで認証する柔軟性があることが多いです。
マルチテナントダッシュボードでは接続時と購読時の二重認可を行ってください。ユーザーは自分の所有するストリーム(例: org_id=123)だけ購読できるべきで、サーバー側で必ず強制すること。
濫用を減らすために接続使用量を制限・監視してください:
これらのログは監査証跡であり、誰かが空白のダッシュボードや他人のデータを見た理由を説明する最速の方法です。
まず一問:ダッシュボードは主に見るためか、それとも頻繁に送り返す必要があるか?ブラウザが受信中心で(グラフ、カウンタ、ステータスランプ)、ユーザーアクションは時折で通常の HTTP リクエストで扱えるなら、実時間チャネルは一方向で十分です。
次に6か月先を考える:インタラクティブ機能が増える(インライン編集、チャット風の操作、ドラッグ&ドロップ)と予想するなら、双方向をきれいに扱えるチャネルを計画してください。
ビューの正確さも決めます。中間のいくつかの更新を欠落しても問題ない(次の更新が古い状態を置き換える)ならシンプルさを優先できます。すべてのイベントが重要で厳密な再生が必要なら、シーケンシング、バッファリング、再同期ロジックを強化する必要があります。
最後に同時接続と成長を見積もってください。何千もの受動的視聴者が見込めるなら、HTTP インフラと水平スケーリングに素直に馴染む選択肢に傾くことが多いです。
SSE を選ぶと良いとき:
WebSockets を選ぶと良いとき:
迷ったら、読み取り中心の典型的なダッシュボードにはまず SSE を選び、双方向の需要が現実に頻繁になってから切り替えるのが安全です。
最も多い失敗は、ダッシュボードに必要以上に複雑なツールを選ぶことです。UI がサーバー→クライアント更新だけで足りるなら、WebSockets はほとんど利益を生まず、接続状態やメッセージルーティングのデバッグに時間を奪われます。
再接続も罠です。再接続は通常接続を復元するだけで、失われたデータは戻りません。ユーザーのラップトップが30秒スリープするとイベントを見逃し、キャッチアップ手順(最後に見たイベント id やタイムスタンプで再取得)が無いと合計値が間違ったまま表示されます。
高頻度のブロードキャストは静かにサービスを落とします。すべての小さな変化(各行の更新やCPUの毎ティック)を送ると負荷とネットワークチャター、UIのチラつきが増えます。バッチやスロットリングは多くの場合更新感を良くします。
運用で気をつけるポイント:
例:サポートチームのライブチケット数ダッシュボードで各チケット変更を即時プッシュすると、エージェントは数字がチラつき、再接続後に数値が戻ることがあります。1〜2秒毎に更新をまとめて送る方がユーザー体験は良いですし、再接続時に現在の合計を取得してからイベント再開するのが安全です。
SaaS 管理ダッシュボードを想像してください。課金メトリクス(新規サブスクリプション、チャーン、MRR)とインシデントアラート(API エラー、キュー滞留)を表示します。ほとんどの閲覧者は数字を見たいだけで、ページをリロードせずに更新されることを望みます。数名の管理者だけがアクションを実行します。
初期段階では必要最小限のストリームから始めます。SSE で十分なことが多い:メトリクス更新とアラートをサーバー→ブラウザへ一方向でプッシュする。状態管理は少なく、エッジケースも少ない。更新を見逃しても次のメッセージに最新合計を含めれば UI はすぐに回復します。
数か月後、利用が増えてダッシュボードがインタラクティブになることがあります。管理者がライブフィルタ(時間窓変更、リージョン切替)や共同確認を望むようになれば選択が変わるかもしれません。双方向メッセージングは同じチャネルでユーザーアクションを返し共有 UI 状態を保つのが楽になります。
移行するなら一晩で切り替えず安全に行ってください:
本番ユーザーに公開する前に、ネットワークは不安定で一部のクライアントは遅いことを前提にしてください。
すべての更新にユニークなイベントIDとタイムスタンプを付け、順序ルールを書き留める。再接続で古いイベントが再生されたり複数サービスが更新を出すときに、どちらが勝つか明確にする。
再接続は自動かつ礼儀正しく。バックオフ(最初は早く、その後遅く)を使い、ユーザーがサインアウトしたら無限に再試行しないこと。
またデータが古くなったときの UI 動作を決める。例えば30秒更新が来なければチャートをグレイアウトし、アニメーションを止め、明確に「古い」状態を示すなど、古い数字を信頼させない表示にする。
ユーザーごとの制限(接続、分あたりメッセージ数、ペイロードサイズ)を設定して、タブの嵐で全体が落ちないようにする。
接続ごとのメモリを監視し遅いクライアントを処理する。ブラウザが追いつけないなら無制限にバッファを増やさず、接続を切る、更新を小さくする、周期的スナップショットに切り替える等の対処をする。
接続/切断/再接続/エラー理由をログに残し、オープン接続数や再接続率、メッセージバックログの異常なスパイクをアラートする。
ストリーミングを無効にしてポーリングや手動更新にフォールバックする緊急スイッチを用意する。午前2時に問題が起きたとき、簡単なセーフオプションがあると助かる。
主要な数字の近くに「最終更新」を表示し、手動更新ボタンを置く。サポートチケットが減り、ユーザーの信頼を高める。
意図的に小さく始める。まず1つのストリーム(例:CPU とリクエストレート、あるいはアラートだけ)を決めてイベント契約を書き出す:イベント名、フィールド、単位、更新頻度。明確な契約は UI とバックエンドのズレを防ぐ。
振る舞いに注力した使い捨てプロトタイプを作る。UI は接続中、ライブ、再接続後の追いつき中という3つの状態を示すようにして、故障を強制的に発生させる:タブを殺す、機内モードにする、サーバーを再起動してダッシュボードの挙動を見る。
トラフィックをスケールする前にギャップからの回復方法を決める。単純な方法は接続時(または再接続時)にスナップショットを送ってからライブ更新に戻すこと。
本格展開前にやること:
もし素早く進めたいなら、Koder.ai (koder.ai) はプロトタイプを素早く作る手助けになります:React ダッシュボード UI、Go バックエンド、チャットプロンプトから作るデータフロー、ソースコードのエクスポートとデプロイオプションを提供します。
プロトタイプが荒いネットワーク条件に耐えられるようになれば、スケールは繰り返しの作業です:容量を増やし、遅延を計測し続け、再接続経路を単純で信頼できるものに保つ。
Use SSE when the browser mostly listens and the server mostly broadcasts. It’s a great fit for metrics, alerts, status lights, and “latest events” panels where user actions are occasional and can go over normal HTTP requests.
Pick WebSockets when the dashboard is also a control panel and the client needs to send frequent, low-latency actions. If users are constantly sending commands, acknowledgements, collaborative changes, or other real-time inputs, two-way messaging usually stays simpler with WebSockets.
SSE is a long-lived HTTP response where the server pushes events to the browser. WebSockets upgrade the connection to a separate two-way protocol so both sides can send messages any time. For read-heavy dashboards, that extra two-way flexibility is often unnecessary overhead.
Add an event ID (or sequence number) to each update and keep a clear “catch-up” path. On reconnect, the client should either replay missed events (when possible) or fetch a fresh snapshot of the current state, then resume live updates so the UI is correct again.
Treat staleness as a real UI state, not a hidden failure. Show something like “Last updated” near key numbers, and if no events arrive for a while, mark the view as stale so users don’t trust outdated data by accident.
Start by keeping messages small and avoiding sending every tiny change. Coalesce frequent updates (send the latest value instead of every intermediate value), and prefer periodic snapshots for totals. The biggest scaling pain is often open connections and slow clients, not raw bandwidth.
A slow client can cause server buffers to grow and eat memory per connection. Put a cap on queued data per client, drop or throttle updates when a client can’t keep up, and favor “latest state” messages over long backlogs to keep the system stable.
Authenticate and authorize every stream like it’s a session that must expire. SSE in browsers typically pushes you toward cookie-based auth because custom headers aren’t available, while WebSockets often require an explicit handshake or first message auth. In both cases, enforce tenant and stream permissions on the server, not in the UI.
Send small, frequent events on the live channel and keep heavy work on normal HTTP endpoints. Initial page load, historical queries, exports, and large responses are better as regular requests, while the live stream should carry lightweight updates that keep the UI current.
Run both in parallel for a while and mirror the same events into each channel. Move a small slice of users first, test reconnects and server restarts under real conditions, then gradually cut over. Keeping the old path briefly as a fallback makes rollouts much safer.