HTTP/1.1パイプラインの「甘い夢」と、HOLブロッキングという冷酷な現実
インフラエンジニアとして現場を渡り歩いていると、若手から「HTTP/1.1って、パイプライン処理があるから並列通信できるんですよね? なんでHTTP/2やHTTP/3が必要なんですか?」という質問をよく受ける。
教科書的には「パイプライン化=複数のリクエストを同時に送れる」と書かれているかもしれない。だが、ネットワークの深淵を覗き込んできた我々からすれば、それは「渋滞している片側一車線の道路で、前の車が動かないのに後続車がクラクションを鳴らしている状態」に過ぎないのだ。
今日は、HTTP/1.1が抱える「パイプラインの幻想」と、それが引き起こすHOL(Head-of-Line)ブロッキングという呪縛について、実務的な視点で紐解いていこう。
—
1. パイプライン処理のメカニズムと「期待」
HTTP/1.1(RFC 2616, 後にRFC 7230で整理)のパイプライン処理は、本来非常に野心的な機能だった。それまでのHTTP/1.0では「リクエスト→レスポンス→次のリクエスト」という逐次処理が基本で、RTT(往復遅延)のたびに待機時間が発生していた。
パイプライン処理の概念はこうだ。
「レスポンスを待たずに、次のリクエストをTCPバッファに叩き込めば、ネットワークの帯域を無駄なく使えるのではないか?」
通信フローのイメージ(期待値)
1. Client -> Server: GET /image1.jpg
2. Client -> Server: GET /image2.jpg
3. Client -> Server: GET /image3.jpg
4. Server -> Client: 200 OK (image1)
5. Server -> Client: 200 OK (image2)
6. Server -> Client: 200 OK (image3)
理論上はこれでレイテンシが劇的に改善するはずだった。しかし、現実はそう甘くはない。
—
2. 悪夢の始まり:HOLブロッキング
HTTP/1.1のパイプラインには決定的な仕様上の弱点がある。「レスポンスは、リクエストの順序通りに返さなければならない」という制約だ。
もし、1番目のリクエスト(image1.jpg)の処理に時間がかかったらどうなるか? サーバーはimage1の生成が終わるまで、image2やimage3のレスポンスを送信できない。たとえimage2が数ミリ秒で準備完了していたとしても、image1の背後で完全に拘束される。
これがHOLブロッキング(Head-of-Line Blocking)だ。列の先頭(Head)が詰まれば、後ろのすべてが停滞する。現代のWebサイトのように何百ものリソースを読み込む環境では、このブロッキングが致命的なパフォーマンス低下を招く。
—
3. 実践:パイプラインの挙動を確認する
実際にどう動いているのか、`curl`を使って確認してみよう。HTTP/1.1のパイプラインを強制するオプション(`–http1.1`)を使う。
-v で通信の詳細を確認し、パイプラインを試行する
注意: 多くのモダンブラウザやサーバーは、トラブルの原因となるためパイプラインを無効化しています
curl -v –http1.1 http://example.com/api/v1/resource1 http://example.com/api/v1/resource2
デバッグ時の注意点
実務において、パイプラインが有効か否かを確認する際は、パケットキャプチャ(Wireshark)でTCPストリームを追うのが鉄則だ。
- 確認ポイント: 同一のTCPセッション内で、最初のACKを待たずに複数のHTTPリクエストが連続して流れているか?
- 確認ポイント: レスポンスのシーケンス番号がリクエストと完全に一致しているか?
もし現場で「レスポンスが妙に遅い」というトラブルがあったら、まずそのリクエストが巨大なファイル取得の直後に並んでいないか疑え。それがHOLブロッキングの正体だ。
—
4. なぜ現代のインフラでは「無効」なのか
実は、主要なブラウザ(Chrome, Firefox, Safari等)は、すでにHTTP/1.1のパイプラインをデフォルトで無効にしている。なぜか?
1. サーバーの脆弱性: 複雑な処理を強いるパイプラインは、サーバー側の実装バグを誘発しやすい。
2. プロキシの介入: 中間にある透過プロキシがパイプラインに対応していない場合、通信が寸断されるリスクが高い。
3. 結局TCP接続を分けたほうが速い: 結局、ブラウザは「TCP接続を複数(通常6本程度)並列して張る」という力技に回帰した。これなら、1つの接続が詰まっても、他の接続は影響を受けないからだ。
Pythonによるリクエスト設計の教訓
もしあなたがAPIクライアントを書いているなら、標準の`requests`ライブラリなどで安易にパイプラインを期待した設計をしてはいけない。
import requests
複数のリクエストを送る際、HTTP/1.1では逐次処理が安全
接続プール(Session)を使ってTCP接続の再利用はするが、
リクエストを重ねるのではなく、非同期IO(aiohttp)で並列化するのが現代の定石
with requests.Session() as s:
for url in urls:
# これはパイプラインではなく、順次取得
# ネットワーク帯域はフルに使えないが、HOLブロッキングは回避できる
response = s.get(url)
process(response)
—
結論:我々はどう向き合うべきか
HTTP/1.1のパイプラインは、「ネットワークの帯域効率」を求めた結果、「予測不可能なレイテンシ」という代償を支払うことになった。この教訓があったからこそ、HTTP/2の「ストリーム多重化」が生まれたのだ。
インフラエンジニアとして重要なのは、「自分のシステムがどのプロトコルレベルで通信しているか」を常に意識することだ。HTTP/1.1を使い続けるなら、パイプラインの幻想を捨て、接続の並列化やCDNによるオフロードで物理的な距離と時間を稼ぐこと。そして、本気でパフォーマンスを追求するなら、迷わずHTTP/2またはHTTP/3へ移行せよ。
教科書を鵜呑みにせず、パケットの呼吸を感じろ。それが、トラブルに強いエンジニアへの第一歩だ。
コメント