【実務・中級編】 HTTP圧縮アルゴリズム(Gzip, Brotli)の選択とオーバーヘッド – Web APIアーキテクチャ・データ連携実践ガイド

「帯域幅は有限であり、そしてCPUサイクルもまたタダではない。」

ネットワークの深淵へようこそ。CCIEを保持し、長年パケットの挙動とサーバーの悲鳴を追いかけてきたシステムアーキテクトの私から、今日はWeb APIのパフォーマンスを極限まで高める「HTTP圧縮」のリアルについて語ろう。

モダンなREST APIを設計する際、美しいエンドポイントの設計に血道を上げる開発者は多い。しかし、いざ本番環境(プロダクション)にデプロイされ、ミリ秒単位のレスポンスタイムとギガビット級のトラフィックが押し寄せたとき、システムの成否を分けるのは「ペイロードをいかに効率よく、かつ低遅延でワイヤー(物理回線)に送り出すか」という泥臭いプロトコルレイヤーの設計だ。

今回は、HTTP圧縮のデファクトスタンダードである gzip と、現代のWebを支配しつつある Brotli(ブロットリ)を比較しながら、そのメカニズム、CPUと帯域のトレードオフ、そしてCDNやリバースプロキシ(Nginx)での具体的な実装設計まで、パケットレベルの視点から徹底的に解説する。

—

1. HTTPネゴシエーションの真実:RFC 9110が定める合意形成

まずは基本に立ち返ろう。クライアントとサーバーが「どの圧縮アルゴリズムを使うか」を決めるプロセスは、RFC 9110(HTTP Semantics)で定義された「コンテンツネゴシエーション(Content Negotiation)」そのものである。

このプロセスは、クライアントが送信する Accept-Encoding リクエストヘッダーから始まる。

通信シーケンス

クライアント (Browser/App)                  サーバー (Nginx/API Gateway)
          |                                              |
          | --- [1] GET /api/v1/users -----------------> |
          |     Accept-Encoding: gzip, br                |
          |                                              |
          |                                    [2] 圧縮アルゴリズムの決定
          |                                        - 両者対応の "br" (Brotli) を選択
          |                                        - ペイロードを圧縮
          |                                              |
          | <--- [3] HTTP/1.1 200 OK ------------------ |
          |     Content-Encoding: br                     |
          |     Content-Type: application/json           |
          |     Vary: Accept-Encoding                    |
          |     (圧縮されたバイナリデータ)               |

各ステップの深層

1. [1] クライアントの宣言 (Accept-Encoding)
クライアント(Webブラウザやモバイルアプリ、curl など)は、「私はこれらの圧縮形式を理解できます」とサーバーに伝える。
Accept-Encoding: gzip, br という指定は、「gzip もしくは br (Brotli) で送ってくれ」という意味になる。

2. [2] サーバー側の選択と圧縮
サーバーは、クライアントの提示したリストと、自身がサポートしているアルゴリズムを照らし合わせる。優先順位(通常はより圧縮率の高い Brotli が優先される)に従ってアルゴリズムを選択し、ボディ(JSONやHTMLなど)を圧縮する。

3. [3] 応答とメタデータ (Content-Encoding / Vary)
サーバーは、実際に適用した圧縮アルゴリズムを Content-Encoding: br ヘッダーでクライアントに示す。
そして、インフラエンジニアとして絶対に忘れてはならないのが Vary: Accept-Encoding ヘッダーの付与だ。これがないと、途中のCDNやリバースプロキシが「圧縮非対応クライアント向けのキャッシュ(未圧縮)」を「圧縮対応クライアント」に返してしまったり、あるいはその逆が発生し、通信が文字化け(データの破損)を起こす原因となる。

—

2. Gzip と Brotli の技術的本質とトレードオフ

なぜ今、gzip だけでなく Brotli なのか。それぞれのアルゴリズムの内部構造と、実務における選択基準を整理しよう。

Gzip (DEFLATEアルゴリズム)

gzip は、1990年代からインターネットを支え続ける偉大な規格だ。内部的には LZ77 アルゴリズムと ハフマン符号化 を組み合わせた DEFLATE を使用している。

  • メリット: 圧倒的な互換性。ほぼ100%のクライアント(レガシーなデバイスや古いIoT端末含む)が解凍(Decompress)できる。
  • デメリット: 辞書サイズが32KBに制限されており、後述する Brotli に比べると、現代の複雑なデータ構造(冗長なJSONや巨大なSPAのJavaScript)に対する圧縮率で劣る。

Brotli (RFC 7932)

Googleが開発した Brotli (br)は、現代のWeb/API通信のために最適化された次世代アルゴリズムだ。こちらも LZ77 と ハフマン符号化 をベースにしているが、決定的な違いは「120KBを超える巨大な『静的辞書(Static Dictionary)』を最初からエンジン内に持っている」という点にある。

この静的辞書には、Webで頻出するHTMLタグ、CSSプロパティ、共通のヘッダー文字列、そしてJSONで頻出するキーや値のパターンがあらかじめ定義されている。これにより、通信の初期段階から極めて高い圧縮率を叩き出すことができるのだ。

  • メリット: gzip と比較して、テキストデータ(JSON, XML, CSS, JS)のファイルサイズを15%〜30%近く削減できる。
  • デメリット: 圧縮時(Compress)のCPU負荷が gzip より高い(特に高圧縮レベル設定時)。

圧縮レベルとCPU負荷のトレードオフ

ここで、現場でよく発生するトラブルを紹介しよう。
「Brotliが良いと聞いたので、圧縮レベルを最大(11)に設定したら、APIサーバーのCPU使用率が100%に張り付き、レスポンスタイムが劇的に悪化した」という事例だ。

以下の比較表を見てほしい。

| アルゴリズム | 推奨圧縮レベル | 特徴 | 用途 |
| :— | :— | :— | :— |
| Gzip | 5 〜 6 | CPU負荷と圧縮率のバランスが最も良い。デフォルト値付近。 | 動的なAPIレスポンス(オンザフライ圧縮) |
| Brotli | 4 〜 6 | gzipと同等以下のCPU負荷でありながら、gzip以上の圧縮率を達成できる。 | 動的なAPIレスポンス(オンザフライ圧縮) |
| Brotli | 10 〜 11 | 極めて高い圧縮率だが、CPU負荷が指数関数的に増大する。 | 静的ファイルの事前圧縮(ビルド時に.brを生成) |

鉄則:APIサーバーなど、リクエストごとにリアルタイムでデータを圧縮する「オンザフライ(On-the-fly)圧縮」の場合、Brotliのレベルは 4 から 6 に設定しなければならない。レベル 11 は静的アセット(JS/CSSファイルなど)をビルド時に事前圧縮(Pre-compression)しておくためのものであり、動的なAPIで使ってはならない。

また、数百バイト程度の極めて小さなJSONレスポンスの場合、圧縮処理のCPUオーバーヘッドの方が、パケット削減の恩恵を上回る。一般的には 1KB(1024バイト)未満のデータは圧縮対象から除外する のがプロトコルのセオリーだ。

—

3. インフラでの実践:Nginxでのハイブリッド設定

では、実際に我々のインフラにこれを組み込もう。以下は、動的APIサーバーのフロントに配置するリバースプロキシとして、Nginxで gzip と Brotli を安全かつ効率的に両立させる設定例である。

> 注意: Brotli をNginxで有効にするには、Googleが提供する ngx_brotli モジュールがNginxに組み込まれている必要がある。

# /etc/nginx/nginx.conf 内の http ブロック、または各 server ブロックに記述

# ==========================================
# 1. Gzip 圧縮の設定
# ==========================================
gzip on;
gzip_vary on; # Vary: Accept-Encoding ヘッダーを自動付与
gzip_proxied any; # プロキシされたリクエスト(CDN経由など)も圧縮対象とする
gzip_comp_level 5; # CPU負荷と圧縮率のバランスが良い「5」を選択
gzip_min_length 1024; # 1KB未満の小さなJSONは圧縮せず、CPUを節約する

# 圧縮対象とするMIMEタイプ(JSONやXMLなどのテキストデータ)
gzip_types
    application/json
    application/javascript
    application/xml
    text/css
    text/plain
    text/javascript;

# ==========================================
# 2. Brotli 圧縮の設定 (ngx_brotli モジュールが必要)
# ==========================================
brotli on;
brotli_static on; # 静的な「.br」ファイルが存在する場合は、圧縮処理をバイパスしてそれを直接返す
brotli_comp_level 4; # 動的圧縮(オンザフライ)に最適な「4」を選択(最大11は絶対避ける)
brotli_min_length 1024; # Gzip同様、1KB未満は除外

# 圧縮対象とするMIMEタイプ
brotli_types
    application/json
    application/javascript
    application/xml
    text/css
    text/plain
    text/javascript;

この設定の美しさ

この設定を施しておくと、クライアントが Accept-Encoding: gzip, br を送ってきた場合、Nginxは自動的に優先度の高い Brotli (br) を選択して圧縮レベル 4 で高速に処理する。もし古いクライアントが Accept-Encoding: gzip しか送ってこなければ、自動的に gzip レベル 5 でフォールバックする。完璧な協調動作だ。

—

4. 検証とデバッグ:パケットの挙動を覗き見る

設定が完了したら、それが本当に期待通りに動いているか検証しなければならない。シニアエンジニアたるもの、推測ではなく「ファクト(証拠)」で語るべきだ。

コマンドライン(curl)での検証

curl コマンドを使い、手動で Accept-Encoding ヘッダーを操作して、サーバーの挙動を揺さぶってみよう。

パターン1: Brotliを要求する場合

curl -I -H "Accept-Encoding: br" https://api.yourdomain.com/v1/users

期待されるレスポンスヘッダー:

HTTP/2 200
content-type: application/json; charset=utf-8
content-encoding: br
vary: Accept-Encoding

content-encoding: br が返ってきていれば、Brotliが正しく作動している。

パターン2: Gzipを要求する場合(古いクライアントのシミュレート)

curl -I -H "Accept-Encoding: gzip" https://api.yourdomain.com/v1/users

期待されるレスポンスヘッダー:

HTTP/2 200
content-type: application/json; charset=utf-8
content-encoding: gzip
vary: Accept-Encoding

サーバーが適切にフォールバック処理を行い、gzip で応答していることが確認できる。

—

Pythonクライアントにおける透過的なデコンプレッション

APIクライアントを開発する際、手動で解凍コードを書く必要はあるだろうか?
答えは「使用するライブラリによる」だ。モダンな requests ライブラリなどは、バックグラウンドで自動的にデコード(解凍)を行ってくれる。

しかし、低レイヤーのデバッグや、超高速な処理が求められるGo言語やRust、あるいはPythonのソケット通信などで生データを扱う場合は、自ら解凍処理を呼び出す必要がある。

以下に、Pythonを用いた「明示的なデコンプレッション(解凍)」のコード例を示す。挙動の理解を深めるのに最適だ。

import urllib.request
import urllib.error
import gzip
# brotliライブラリがインストールされている前提 (pip install brotli)
import brotli 

url = "https://api.yourdomain.com/v1/users"

# クライアントがBrotliとGzipに対応していることを伝えるリクエストを作成
req = urllib.request.Request(url)
req.add_header("Accept-Encoding", "br, gzip")

try:
    with urllib.request.urlopen(req) as response:
        # レスポンスヘッダーから圧縮方式を取得
        content_encoding = response.headers.get("Content-Encoding")
        raw_data = response.read()
        
        print(f"Content-Length (圧縮後の受信サイズ): {len(raw_data)} bytes")
        print(f"適用された圧縮形式: {content_encoding}")

        # 圧縮形式に応じて明示的に解凍処理を分岐する
        if content_encoding == "br":
            decompressed_data = brotli.decompress(raw_data)
            print("Brotliで正常に解凍しました。")
        elif content_encoding == "gzip":
            decompressed_data = gzip.decompress(raw_data)
            print("Gzipで正常に解凍しました。")
        else:
            decompressed_data = raw_data
            print("未圧縮データ、または未対応の圧縮形式です。")

        # デコードしてJSONデータを表示
        json_string = decompressed_data.decode("utf-8")
        print(f"解凍後のデータサイズ: {len(decompressed_data)} bytes")
        print(json_string[:150] + "...") # 先頭部分だけ表示

except urllib.error.URLError as e:
    print(f"通信エラーが発生しました: {e.reason}")

このコードを実行すると、ネットワーク上を流れる「圧縮されたフットプリントの小さなバイナリ」が、手元で元の美しいJSON文字列へと復元されるプロセスが手に取るように理解できるはずだ。

—

5. まとめ:シニアアーキテクトからのアドバイス

最後に、あなたが設計するAPIをスケールさせるための黄金律を3つ、授けよう。

1. 「一律圧縮」の罠に嵌るな
画像(JPEG, PNG)や動画(MP4)、既に圧縮済みのファイル(ZIP)をAPIで返却する場合、それらをさらに gzip や Brotli で圧縮しようとしてはならない。すでにエントロピーが極限まで高まっているデータを再圧縮しても、サイズは一切減らず、ただサーバーのCPU資源を無駄に浪費するだけだ。必ず brotli_types や gzip_types で対象をテキストデータ(application/json 等)に限定すること。

2. CDNとの協調設計を忘れるな
CloudflareやAWS CloudFront、AkamaiといったCDNをフロントに置く場合、エッジサーバー側での圧縮処理(Edge Compression)が有効になっているか必ず確認しよう。オリジン(あなたのAPIサーバー)とCDN間は未圧縮(あるいは高速な gzip レベル1)で通信し、ユーザーに近いCDNエッジで Brotli に高効率圧縮して配信する、という設計もインフラ全体のレイテンシ短縮において非常に有効だ。

3. 「Vary: Accept-Encoding」は絶対の掟
キャッシュの不整合による文字化けやAPIエラーの半分は、このヘッダーの欠落、あるいは中間プロキシの乱暴な挙動が原因だ。レスポンスヘッダーに必ずこれが含まれていることを、監視項目に加えておくといい。

ネットワークプロトコルは、ただの「ルール」ではない。それは、限られた物理リソースの中で、いかにエレガントにデータを届けるかという先人たちの知恵の結晶だ。

この記事が、あなたの設計するWeb APIをより高速に、そして頑強にする一助となることを願っている。さあ、ターミナルを開いて、あなたのサーバーのヘッダーを確認しに行こう。

コメント

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