NginxでHTTP/3 (QUIC) を導入:レイテンシとヘッドオブラインブロッキングを解消する

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

午前2時の呼び出し:「速い」だけでは不十分なとき

Slackの通知音が午前2時の静寂を破った。「東南アジアでレイテンシが急増。モバイルユーザーが再びタイムアウトに遭遇している」。私は重い腰を上げてデスクに向かい、モニターの眩しさに目を細めた。Grafanaのダッシュボードは完璧に見えた。CPU使用率は12%と低く、RAMは4GBで安定、データベースのレスポンスタイムも30msと順調だった。しかし、ジャカルタの不安定な4G回線を使っているユーザーにとって、TTFB(Time to First Byte)は1.8秒を超えて膨れ上がっていた。

ハードウェアの問題ではなかった。プロトコルの問題だ。我々はTCP上で標準的なHTTP/2を動かしていた。安定した光回線なら非常に強力だが、混雑した基地局でパケットが1つでも落ちた瞬間、TCPは1974年のロジックに立ち戻り、すべてを停止させて待機する。これが「ヘッドオブライン(HoL)ブロッキング」だ。午前2時、これが私と睡眠の間に立ちはだかる唯一の壁だった。

根本原因:なぜTCPは現代のモバイルウェブに不向きなのか

ラグを解消するために、Nginxが外部とどのように通信しているかを分析する必要があった。HTTP/2はマルチプレクシング(多重化)を導入し、1つの接続で複数のファイルを送信できるようにした大きな進歩だった。しかし、落とし穴がある。依然としてTCPに依存していることだ。

50KBのCSSファイル、200KBのJavaScriptバンドル、そしてヒーロー画像をダウンロードしているところを想像してほしい。CSSファイルのほんの一部を含むパケットが転送中に失われると、TCPはパイプライン全体をフリーズさせる。JSや画像のパケットが完璧に届いていても、ブラウザはそれらに触れることができない。欠落したCSSの断片が再送されるまで、バッファの中でアイドル状態で待機することになる。

さらに「ハンドシェイク税」の問題もある。標準的なHTTPS接続では、TCPの3ウェイ・ハンドシェイクに続いてTLSのネゴシエーションが必要になる。サーバーが1バイトのデータを送信するまでに、3往復(ラウンドトリップ)のやり取りが発生する。RTT(往復遅延時間)が250msの高レイテンシ回線では、ユーザーはサーバーに「こんにちは」と言うだけで1秒近く待たされることになる。

解決策:HTTP/3とQUICプロトコル

トランスポート層でTCPを捨てる必要があると確信した。そこでHTTP/3の出番だ。旧バージョンとは異なり、HTTP/3はQUIC(Quick UDP Internet Connections)を使用する。もともとGoogleによって構築されたもので、UDP上で動作することでTCPの構造的な欠陥を回避する。

QUICがどのように危機を解決するか:

  • 独立したストリーム: QUICはトランスポートレベルでマルチプレクシングを処理する。「ストリームA」でパケットが失われても、「ストリームB」は動き続ける。接続全体が停止することはない。
  • 0-RTTハンドシェイク: QUICは接続と暗号化のハンドシェイクを統合する。以前に訪問したことがあるクライアントなら、最初のパケットでデータを送信できる。
  • コネクションマイグレーション: ユーザーがオフィスのWi-Fiから5Gネットワークに切り替わると、IPアドレスが変わる。TCPなら接続は即座に切断されるが、QUICは固有のコネクションIDを使用するため、再接続なしでセッションを維持できる。

実装:NginxにHTTP/3を導入する

以前は、HTTP/3の設定には試験的なパッチが必要で非常に面倒だった。幸いなことに、現在のNginxはメインラインブランチ(バージョン1.25.0以降)でネイティブにQUICをサポートしている。これをエッジノードに展開したところ、安定性は極めて強固だった。

ステップ1:Nginxメインラインのインストール

ほとんどのディストリビューションのデフォルトのaptリポジトリは古いバージョンを保持している。メインラインのビルドが必要だ。Ubuntuユーザーの場合は、公式のNginxソースから直接取得する:

sudo apt update && sudo apt install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring

# Nginxの署名鍵をインポート
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

# メインラインリポジトリを設定
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" | sudo tee /etc/apt/sources.list.d/nginx.list

sudo apt update
sudo apt install nginx

nginx -vを実行して、バージョンが1.25.0以上であることを確認する。

ステップ2:サーバーブロックの設定

HTTP/3を有効にするには、2つの特定の変更が必要だ。UDPポートをリッスンし、ブラウザがQUICの利用可能性を知ることができるようにAlt-Svcヘッダーをブロードキャストする必要がある。サイト設定(通常は/etc/nginx/conf.d/default.conf)を編集する:

server {
    # TCP (HTTP/2) と UDP (HTTP/3) の両方でポート443をリッスンする
    listen 443 quic reuseport;
    listen 443 ssl;
    http2 on;

    server_name example.com;

    # QUICは厳密にTLS 1.3を要求する
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # ブラウザにHTTP/3のサポートを通知する
    add_header Alt-Svc 'h3=":443"; ma=86400';

    # オプション:ヘッダーでQUICの使用状況を追跡する
    add_header X-Protocol $server_protocol;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

プロのヒント: reuseportパラメータは極めて重要だ。これにより、カーネルが入信UDPパケットを複数のNginxワーカープロセスに分散できるようになり、シングルコアのボトルネックを防ぐことができる。

ステップ3:ファイアウォールの修正

設定が「失敗」している原因を突き止めるのに20分かかったが、犯人はファイアウォールだった。標準のHTTPSはTCPを使用する。HTTP/3はUDPを使用する。UDP 443を開放しないと、ブラウザは黙って HTTP/2にフォールバックしてしまい、パフォーマンス向上の恩恵を受けられない。

# UFWユーザーの場合
sudo ufw allow 443/tcp
sudo ufw allow 443/udp

# AWS/GCPユーザーの場合:セキュリティグループで0.0.0.0/0からのUDP 443を許可しているか確認する

接続の確認

ブラウザは通常、HTTP/3について声高に教えてはくれない。動作しているか確認するには、Chromeのデベロッパーツールを開き、Network(ネットワーク)タブに移動する。ヘッダー行を右クリックしてProtocol(プロトコル)列を有効にする。ページを2回更新する。1回目でAlt-Svcヘッダーを検出し、2回目でプロトコル列にh3が表示されるはずだ。

ターミナルを使うこともできる。curl --http3 -I https://example.comを実行して、プロトコルのハンドシェイクが動作しているか確認しよう。

結論:導入する価値はあったか?

移行後、メトリクスは明確な結果を示した。高レイテンシ地域のユーザーにおいて、初期接続時間は平均で320ms短縮された。さらに重要なのは、モバイルネットワーク上でのウェブアプリの「カクつき」が解消されたことだ。パケット損失率が3%の状態でも、サイトのレスポンスは維持された。

実際の環境でのプロトコル比較は以下の通りだ:

指標 HTTP/2 (TCP) HTTP/3 (QUIC)
ハンドシェイク時間 ~200-500ms ~0-150ms
パケット損失の影響 接続全体の停止 最小限(ストリーム固有)
ネットワークの切り替え 接続が切断される シームレスな移行

HTTP/3へのアップグレードは、単に最新技術を追いかけることではない。それはレジリエンス(回復力)のためだ。モバイルトラフィックが主流の現代において、不安定な接続環境でも耐えられる能力は大きなアドバンテージとなる。もしユーザー全員がギガビット光回線を使っていないのであれば、今すぐ切り替えるべきだ。

Share: