【実務・中級編】ドメインシャーディングによるHTTP/1.1の同時接続数制限回避 – HTTPプロトコル・通信規格実践ガイド

HTTP/1.1の「見えない壁」を突き破る:ドメインシャーディングという名の「ハック」

こんにちは。ネットワークの深淵を覗き込み、パケットの呼吸を感じるのが趣味のシニアエンジニアです。

今日は、現代の高速なWebインフラに慣れ親しんだエンジニアが、あえて振り返るべき「古き良き、そして少しだけ泥臭い」最適化手法について話をしよう。テーマは「ドメインシャーディング(Domain Sharding)」だ。

HTTP/2やHTTP/3の多重化(Multiplexing)が当たり前になった今、なぜこの手法を語るのか。それは、レガシーな環境や、特定の条件下で発生する「リソース読み込みの詰まり」を解決するための、最後の一手となり得るからだ。

—

1. なぜ「制限」が存在したのか?:HTTP/1.1の呪縛

HTTP/1.1において、ブラウザは「一つのドメイン(ホスト名)に対して同時に接続できる数は最大6つまで」という暗黙のルールを設けていた。これはRFC 2616(およびその後のRFC 7230系列)の時代、サーバーへの負荷集中を防ぐためのクライアント側の良心のようなものだった。

しかし、現代のWebページは単なるテキストではない。数十、数百の画像、CSS、JSファイルが雪崩のように押し寄せる。この「同時6接続」という制約は、ネットワークの帯域が余っているにもかかわらず、ブラウザ側でリクエストをキューイングさせてしまうという、まさに「ボトルネック」そのものだった。

そこで先人たちが編み出したのが、`static1.example.com`、`static2.example.com`……とサブドメインを乱立させ、ブラウザを「これらは別のホストだから、それぞれ6本ずつ繋いでいいよね?」と錯覚させるドメインシャーディングという荒業だ。

—

2. 通信フロー:物理的に「道」を増やす

この手法の仕組みはシンプルだ。

1. 通常時: `example.com` への全リクエストが、6本のコネクションに集中し、列ができる。
2. シャーディング時: `s1.example.com` と `s2.example.com` にリソースを分散。
3. 結果: ブラウザは `s1` に6本、`s2` に6本、合計12本のコネクションを同時に確立し、並列ダウンロードを強制する。

シーケンスのイメージ(簡略化)

Client (Browser) s1.example.com s2.example.com
| | |
|—-(GET /a.js)———>| |
|—-(GET /b.js)———>| |
|—-(GET /c.js)——————————–>|
|—-(GET /d.js)——————————–>|
| (合計12本まで同時並列が可能に)

—

3. 実践:インフラとコードでの実装Tips

これを実装するには、CDNやロードバランサーの設定、そしてアプリケーション側のURL生成ロジックの修正が必要になる。

Nginxでの受け入れ設定例

サブドメインが増えても、結局同じオリジンサーバーに向かうなら、Nginx側で柔軟に受け取れるようにしておくのが鉄則だ。

server {
listen 80;
# ワイルドカードで複数のサブドメインを一括処理する
server_name ~^(s[1-4])\.example\.com$;

root /var/www/static;

# 接続数制限を回避しても、サーバー側で負荷が高まりすぎては本末転倒
# 適切にkeepaliveを設定し、TCPハンドシェイクのオーバーヘッドを削る
location / {
expires 30d;
add_header Cache-Control “public”;
}
}

PythonでURLを動的に振り分ける実装

フロントエンドでロードするリソースURLを動的に生成する際、以下のようにサブドメインをループさせるのが一般的だ。

def get_sharded_url(asset_path, shard_count=4):
“””
リソースパスからドメインを動的に割り当てる関数
asset_path: ‘images/hero.jpg’
return: ‘https://s1.example.com/images/hero.jpg’
“””
# パスのハッシュ値を使って、特定のファイルが常に同じドメインに行くようにする
# (キャッシュ効率を維持するため)
import hashlib
shard_id = int(hashlib.md5(asset_path.encode()).hexdigest(), 16) % shard_count + 1

return f”https://s{shard_id}.example.com/{asset_path}”

実行例
print(get_sharded_url(“js/app.js”))

—

4. エンジニアが忘れてはならない「代償」

ここまで読んで「よし、明日から全部シャーディングしよう」と思ったなら、少し待ってほしい。この手法には決定的な欠点がある。

  • DNSルックアップの増大: ドメインが増えるたびに、クライアントはDNS解決をやり直す必要がある。
  • TCP/TLSコネクションのオーバーヘッド: HTTP/1.1では、ドメインごとに独立したTCPセッションとTLSハンドシェイクが発生する。これが「RTT(往復遅延時間)」を増大させ、特にモバイル環境では逆に遅くなるケースがある。
  • キャッシュの分断: ブラウザはホスト名単位でキャッシュを管理するため、シャーディングしすぎるとキャッシュ効率が悪化する。

トラブルシューティングの勘所

もしサイトが遅いと感じたら、Chrome DevToolsの「Network」タブを見てほしい。`Waterfall`を見れば、どのリクエストが「Stalled(待機状態)」になっているか一目瞭然だ。「Stalledが長い=同時接続制限に引っかかっている」サインである。

結びに代えて

ドメインシャーディングは、HTTP/1.1という制約の中でエンジニアが絞り出した知恵の結晶だ。しかし、現代のベストプラクティスは「HTTP/2以降への移行」であることは揺るがない。HTTP/2は単一コネクションで多重化を完結させるため、シャーディングは逆にパフォーマンスを低下させる原因にもなる。

自分のネットワーク環境が「今、どのプロトコルで通信しているのか」を常に把握し、技術の歴史と最新の仕様を天秤にかけること。それこそが、シニアエンジニアとして生き残るための唯一の道だ。

現場からは以上だ。何か詰まったら、またパケットキャプチャを広げて相談しに来てくれ。

コメント

タイトルとURLをコピーしました