ポーリングと SSE と WebSocket — どこまで双方向が要るか
5秒ごとの更新確認を入れた翌週、DB の接続が枯れた。1000人が5秒ごとに聞きに来れば毎秒200回。要るのは本当に双方向か、それとも「届く」だけか。
この記事の進み方
What — 3つの方式は「誰が口を開くか」で分かれる
更新を届ける方式は、通信を誰が始めるかで分類できます。
クライアントが定期的に聞きに行く。サーバーは普通の HTTP で答える。
クライアント ──▶ サーバー 「変わった?」
クライアント ◀── サーバー 「いいえ」
(5秒待つ)
クライアント ──▶ サーバー 「変わった?」
クライアント ◀── サーバー 「いいえ」- 接続の保持
- しない
- 無駄な往復
- 多い
- 遅れ
- 最大で間隔ぶん
- 実装の重さ
- 最も軽い
- –既存の HTTP の仕組みがそのまま使える。ロードバランサもプロキシも何もしなくてよい。
- –「変わっていない」を確かめるためのリクエストが大半を占める。冒頭の事故はこれ。
ポーリングは「聞きに行く」、SSE は「送ってもらう」、WebSocket は「互いに話す」。要件が「送ってもらう」で足りるなら、WebSocket は過剰になる。
- ポーリング
- 一定間隔で問い合わせること。変化が無くても往復が発生する。
- ロングポーリング
- 変化があるまでサーバーが応答を保留する方式。往復は減るが接続は保持される。
- SSE (Server-Sent Events)
- HTTP のレスポンスを閉じずにイベントを流し続ける仕組み。サーバーからの一方向。
- WebSocket
- HTTP から切り替えて双方向の通信路を張るプロトコル。
- Upgrade
- HTTP の接続を別のプロトコルに切り替える手続き。WebSocket はこれで始まる。
「リアルタイム」の中身は2つに分かれる
「リアルタイムにしたい」と言われたとき、実際に求められているものはたいてい次のどちらかです。
- 速く届いてほしい(サーバー → クライアントの一方向)
- 速くやり取りしたい(双方向)
前者なら SSE で足ります。通知、進捗の表示、ダッシュボードの数値更新、ビルドのログ。これらは全部、サーバーから送るだけです。
後者が本当に要るのは、チャット、共同編集、ゲーム、カーソルの共有あたりです。クライアント側が頻繁に、かつ低遅延で送る必要がある場合に限られます。
冒頭の事故の要件は「新着の通知を出す」でした。送るのはサーバーだけです。WebSocket も要りませんでした。
Why — 開いたままの接続は、数えられる資源になる
ポーリングと、接続を保持する方式(SSE / WebSocket)の違いは、何を消費するかです。
ポーリングはリクエストの回数を消費します。1回ごとに接続を作って捨てるので、同時に保持しているものは少ない。
SSE と WebSocket は同時接続数を消費します。往復は減りますが、1人1本の接続が開きっぱなしになります。
- 1ブラウザ
- 2ロードバランサ
- 3アプリ
- 4DB
ポーリングは瞬間の同時実行数が少ない代わりに回数が多い。保持型は回数が少ない代わりに、常に接続を持ち続ける。どちらも上限にぶつかる場所が違う。
上限にぶつかる場所が違う
ポーリングが壊すのは、たいてい DB か外部サービスです。1リクエストごとに問い合わせが飛ぶので、間隔と人数を掛けた回数がそのまま負荷になります。
保持型が壊すのは、たいていアプリのプロセスか途中の箱です。1000 本の接続を同時に持つには、それを持てる作りになっている必要があります。プロセスあたりの上限、ファイルディスクリプタ、ロードバランサの接続数。
どちらが軽いかは、人数と更新頻度で逆転します。
- 人数が少なく更新が多い → 保持型が有利
- 人数が多く更新が稀 → ポーリングのほうが安いこともある(ただし間隔を長くすること)
冒頭の事故は「人数が多く更新が稀」なのに、間隔が5秒と短すぎました。1日数十件の更新に対して、毎秒 40 回聞いていた。
保持型には、切れる前提が要る
開いたままの接続は必ず切れます。
- 回線が変わる(Wi-Fi から携帯網へ)
- スマートフォンがスリープする
- ロードバランサのアイドルタイムアウト(無言が続くと切る)
- デプロイでアプリが入れ替わる
SSE はブラウザが自動で再接続します。 Last-Event-ID を送り返す仕組みもあり、取りこぼしの回復も規格に入っています。
WebSocket は自分で書きます。 再接続、その間に発生したイベントの取得、重複の除去。ここを書かずに済ませると、「たまに通知が来ない」という再現しにくい不具合になります。
演習 — まず自分で判断する
社内の管理画面に「処理の進捗バー」を出したい。進捗はサーバー側が5〜10秒ごとに更新する。同時に見る人は最大20人。どれを選びますか?
500人が3秒ごとにポーリングしています。1リクエストで DB に1回、50ms のクエリを投げるとき、DB の接続は常時およそ何本埋まりますか?
読み終わりましたか?
読了にすると、これを前提とする記事がロードマップで開放されます。