新着メッセージのように、いつ届くか分からない変化をクライアントが知るとき、HTTP では問い合わせが先に必要です。
サーバーから先に、その変化を送ることはできません。
知る方法は3つです。1回だけ送る通常のリクエスト、3秒ごとに送り直すショートポーリング、応答を最大30秒保留するロングポーリングです。0秒に問い合わせを始め、8秒にメッセージが届く例で、見える時刻とリクエストの回数を比べます。
通常のリクエスト
問い合わせを受けた時点の状態を、サーバーはすぐ返します。新しいメッセージがなければ「ない」が返り、その1往復は終わります。
応答を待っているリクエストは残りません。
sequenceDiagram participant C as クライアント participant S as サーバー C->>S: 新しいメッセージはありますか S->>C: 今はありません Note over C,S: この往復は終わる
図: 問い合わせた時点の状態がすぐ返り、その往復は終わります。
8秒後にメッセージが届いても、終わった往復には載りません。知るには、クライアントがもう一度問い合わせる必要があります。
ショートポーリング
クライアントは、通常のリクエストを3秒ごとに送ります。時刻は0秒、3秒、6秒、9秒です。サーバーは毎回、問い合わせを受けた時点の状態をすぐ返します。応答を待っているリクエストは残りません。
新しいものがなくても「ない」が返ります。
変化がないあいだも、この往復は3秒ごとに続きます。
sequenceDiagram
participant C as クライアント
participant S as サーバー
C->>S: ① 0秒 要求
S-->>C: ② 即応答
C->>S: ③ 3秒 要求
S-->>C: ④ 即応答
C->>S: ⑤ 6秒 要求
S-->>C: ⑥ 即応答
C->>S: ⑦ 9秒 要求
S-->>C: ⑧ 即応答
図: 3秒ごとに短い要求と即時の返事が並びます。応答は保留しません。
8秒にメッセージが届いたとします。次の問い合わせは9秒です。届いてから見えるまで、最大で約3秒遅れます。
そのあいだ、サーバーに応答待ちのリクエストはありません。
ロングポーリング
クライアントが送るのは1回です。サーバーは、新しいメッセージが届くか、30秒たつまで、その応答を返しません。
30秒以内に届いたら、その時点で中身を返します。届かなければ、30秒の時点で「ない」と返します。クライアントはどちらの応答でも、受け取ったあとすぐに次の1回を送ります。待っているあいだ、応答を保留したリクエストは1件です。
sequenceDiagram
participant C as クライアント
participant S as サーバー
C->>S: 新しいメッセージまで待ってください
Note over S: 結果か30秒まで保留
alt 30秒以内に届いた
S->>C: 届いたメッセージ
else 30秒たっても無い
S->>C: 今はありません
end
C->>S: 応答のあと、すぐ次の1回を送る
図: 1回の問い合わせは、届いた時点か30秒まで応答を保留します。
3つの流れを並べる
| 流れ | 返事 | 8秒のメッセージ | 応答待ち |
|---|---|---|---|
| 通常のリクエスト | 問い合わせた時点の状態 | その1回には載らない | 往復が終わると残らない |
| ショートポーリング | 問い合わせた時点の状態 | 9秒の問い合わせで見える | 待ちは残らない |
| ロングポーリング | 届いた中身か、30秒の「ない」 | 届いた8秒で見える | 結果か30秒まで1件 |
メリットとデメリット
ショートポーリング
メリット
- 実装が単純です。
- 通常の HTTP リクエストとして扱えるので、既存のフレームワークやインフラとそのまま合います。
- 応答待ちのリクエストが残らないので、タイムアウトや再接続の設計が単純です。障害のときの動きも読みやすいです。
デメリット
- 更新がなくても、3秒ごとにリクエストが飛びます。
- 間隔を短くすると、リクエスト数と通信量が増えます。
- 間隔を長くすると、届いてから見えるまでに遅れます。
ロングポーリング
メリット
- イベントが起きた時点で返せるので、見えるのが速いです。
- 更新がないあいだは問い合わせを繰り返さないので、空のリクエストが減ります。
- 常時接続を足さなくても、通常の HTTP で起きた時刻に近い返事を返せます。
デメリット
- 応答を保留したリクエストを、長く維持します。
- クライアントが多いと、ソケットやメモリなど、維持する資源を使います。
- リクエストごとにワーカーを1つ割り当てるサーバーでは、保留しているあいだ、そのワーカーがふさがります。非同期 I/O ならこの占有は避けられます。リクエストを維持するコストは残ります。
- タイムアウト、切断、再接続を、自分で制御する必要があります。
- 応答と次のリクエストのあいだに届いた分は、サーバーが保持しておかないと、次の問い合わせに載りません。
- 保留できる時間は、経路上で最も短いタイムアウトより短くします。ALB の idle timeout と Nginx の
proxy_read_timeoutは、既定で60秒です。API Gateway の REST API は、応答が返るまでの上限が既定で29秒です。 - ショートポーリングより、実装と運用の手順が増えます。
比べると
| 方式 | 強み | 弱み |
|---|---|---|
| ショート | 単純で扱いやすい | リクエストが増えやすく、見える速さは間隔による |
| ロング | 空のリクエストを減らし、届いた時刻に近づけられる | 応答待ちのリクエストを維持する管理が要る |
まとめ
今の状態だけが要るなら、通常のリクエストで足ります。実装を単純に保ったまま変化を待つなら、ショートポーリングです。空のリクエストを減らし、届いた時刻に近づけたいなら、ロングポーリングです。
ロングポーリングでは、応答を保留したリクエストが1件残ります。クライアントは返事を受け取ったあと、すぐに次の1回を送ります。