ZSTD、Brotli、GZIP を API 圧縮で比較:圧縮率、速度、CPU/メモリコスト、JSONやバイナリの実運用上のデフォルトとチューニング。

APIのレスポンス圧縮とは、サーバーがレスポンスボディ(多くはJSON)をネットワーク送信前に小さなバイト列にエンコードし、クライアント(ブラウザ、モバイルアプリ、SDK、別サービスなど)がそれを復元することです。HTTP上では、これは Accept-Encoding(クライアントが対応するもの)と Content-Encoding(サーバーが選んだもの)といったヘッダーでネゴシエートされます。
圧縮で得られる主な利点は3つです:
トレードオフは明確で、圧縮は帯域を節約しますがCPU(圧縮/復元)と場合によってはメモリ(バッファ)を消費します。メリットはどのリソースがボトルネックかによります。
圧縮は次のようなレスポンスで力を発揮します:
大きなJSONリスト(カタログ、検索結果、分析データ等)を返すなら、圧縮は簡単に得られる改善策の一つです。
圧縮はCPUを消費するため、次のような場合は効率が悪いです:
ZSTD vs Brotli vs GZIP を選ぶ際の実用的な判断は通常次の3つに集約されます:
以下は、これらをトレードオフしてあなたのAPIとトラフィックに最適化するための実践的な説明です。
3つともペイロードを小さくしますが、速度・圧縮率・互換性のどれを重視するかで得意分野が異なります。
ZSTDの速度: エンドポイントの尾部レイテンシに敏感なときやサーバーがCPUに制約があるときに有利。中〜大サイズのJSONでは、圧縮が十分に速くネットワーク時間に比べて無視できることが多い。
Brotliの圧縮率: 帯域が主要な制約(モバイル、egressコスト、CDN配信)でテキスト中心のレスポンスが多い場合に最も有効。小さなペイロードでも長めの圧縮時間に耐えられるなら価値がある。
GZIPの互換性: 最大限のクライアント互換性が必要な場合(古いSDK、組み込みクライアント、レガシープロキシ)に最適。性能面でトップではないが安全なベースラインとなる。
圧縮の“レベル”はCPU時間と出力サイズのトレードオフを示すプリセットです:
復号は通常どれも圧縮よりずっと安いですが、非常に高いレベルはクライアント側のCPU/バッテリーにも影響します。
圧縮は「レスポンスが小さければAPIは速くなる」と言われますが、それは常に正しいわけではありません。圧縮処理がサーバーのCPU時間を増やすと、実際には応答が遅くなることがあります。
2つのコストに分けると分かりやすいです:
高い圧縮率は転送時間を短縮しますが、圧縮が1レスポンスあたり15–30msを追加するなどすると、高速接続では節約分を上回って遅くなることがあります。
負荷が高いと、圧縮はp95/p99レイテンシを悪化させることが多いです。CPU使用率が上がるとリクエストが待たされ、キューイングは小さな追加コストを大きな遅延に増幅します。平均レイテンシは悪化しない場合でも、最悪の影響を受けるユーザーが発生します。
推測で判断せず、A/Bテストや段階的ロールアウトで次を比較してください:
実際のトラフィックとペイロードでテストし、最終的に選ぶべき圧縮レベルは「合計時間が短くなる」ものです。単にバイト数が減るだけでは不十分です。
圧縮は“無料”ではありません — 両端でCPUとメモリが増えます。APIでは、リクエスト処理時間の増加、ピークメモリの増加、クライアント側の低速化として現れます。
多くのCPUはレスポンスの圧縮に使われます。圧縮はパターン探索、状態や辞書の生成、出力のエンコードを書き出す作業を含みます。
復号は通常安価ですが重要です:
アプリサーバーが既にCPUで逼迫しているなら、高い圧縮レベルはp95/p99を悪化させることがあります。
圧縮は次のようにメモリ消費を増やすことがあります:
コンテナ環境ではピークメモリの増加がOOMやデプロイ密度の低下につながることがあるので注意してください。
圧縮はレスポンスごとにCPUを消費するため、インスタンス当たりのスループットが下がり、オートスケールが早く発動してコストが上がることがあります。帯域が減ってもCPU消費が増えるため、どのリソースが不足しているかにより選択が変わります。
モバイルや低消費電力デバイスでは、復号がレンダリングやJS実行、バッテリー消費と競合します。数KBを節約しても復号が長時間かかるフォーマットは「体感上遅い」と感じられることがあります。
ZSTDは、良好な圧縮率を保ちながらAPIの速度を著しく損なわないように設計されたモダンなフォーマットです。多くのJSON中心のAPIでは、GZIPより小さなレスポンスを同等または低いレイテンシで提供できるため「デフォルト」として有力です。
エンドツーエンド時間を重視する場合に最も有用です。圧縮が速く、復号は非常に高速で、リクエスト処理時間の競合が激しい環境で役立ちます。
小〜中サイズのJSONでも有意な改善が期待でき、大きなレスポンスではさらに効果的です。
ほとんどのAPIでは低めのレベル(一般的に1–3)から始めてください。これらはレイテンシとサイズのバランスが良いことが多いです。
次のような場合に高レベルを検討します:
実務的にはグローバルな低いデフォルトを採り、特定の“大きなレスポンス”エンドポイントだけレベルを上げるのが良いアプローチです。
ZSTDはストリーミングをサポートしており、大きなレスポンスでピークメモリを抑えつつ早くデータを送れる利点があります。
辞書モードは、似たオブジェクトを頻繁に返すAPI(繰り返すキー、安定したスキーマ)に対して大きな利得をもたらすことがあります。小さくて頻繁に送られるペイロードに対して、バージョン管理された辞書を運用できれば効果的です。
多くのサーバースタックでサポートは容易ですが、クライアント互換性が決定要因になることがあります。いまだに Content-Encoding: zstd をデフォルトで広告しないHTTPクライアント、プロキシ、ゲートウェイもあります。
サードパーティのコンシューマがいる場合は、フォールバック(通常はGZIP)を用意し、Accept-Encoding に zstd が含まれている場合にのみ有効にしてください。
Brotliはテキストを非常に効率よく縮めるよう設計されています。JSONやHTMLのような“語彙的”なペイロードではGZIPより良い圧縮率を出すことが多く、特に高いレベルで顕著です。
テキスト中心のレスポンスがBrotliの得意分野です。大きなJSONドキュメント(カタログ、検索結果、設定塊)を送る場合、Brotliはバイト数を大幅に削減でき、遅いネットワーク上やegressコスト削減に寄与します。
また、一度圧縮して多数回配信する(キャッシュ可能なレスポンス、バージョン付きリソース)のような場面では、高いBrotliレベルがCPUコストを複数リクエストで償却できるため有効です。
動的なAPIレスポンス(リクエストごとに生成される)では、Brotliの最良の圧縮率を得るには高いレベルが必要で、それがCPUコストとレイテンシ増加を招きます。現実の運用では、ZSTDや適切にチューニングされたGZIPとの差は思ったほど大きくないことがあります。
すでに圧縮されているペイロードや多くのバイナリ形式では有効性が低く、CPUを無駄にするだけです。
ブラウザはHTTPS経由でBrotliを比較的良くサポートするため、ウェブトラフィックでは人気があります。非ブラウザのAPIクライアント(モバイルSDK、IoT、古いHTTPスタック)では対応が不安定な場合があるので、Accept-Encoding によるネゴシエーションと gzip のフォールバックを忘れないでください。
GZIPは、ほとんどすべてのHTTPクライアント、ブラウザ、プロキシ、ゲートウェイが理解するため、今もなおデフォルト回答として残っています。完全にコントロールできない経路やクライアントがいる場合、予測可能性が重要です。
GZIPの優位性は“最良”であることではなく、“間違いになりにくい”点です。多くの組織が長年の運用経験を持ち、ウェブサーバーに合理的なデフォルトがあり、中間装置が新しいエンコーディングを壊すリスクが低いことも利点です。
APIペイロード(多くはJSON)では、中〜低レベルが最適です。レベル1–6は多くの場合サイズ削減の大部分を提供しつつCPUも抑えます。非常に高いレベル(8–9)はわずかなサイズ改善しかもたらさないため、動的なリクエスト/レスポンスでは通常割に合いません。
最新のハードウェア上では、GZIPは同等の圧縮率に対してZSTDより遅いことが多く、テキストに対してBrotliの最高圧縮率にも届かないことがよくあります。実ワークロードでは:
古いクライアント、組み込みデバイス、企業プロキシ、レガシーゲートウェイが相手だと、GZIPが最も安全です。一部の中間装置は未知のエンコーディングを剥がしたりパススルーしなかったりするため、環境が混在している場合はまずGZIPで開始し、完全にパスが管理できる部分でZSTDやBrotliを追加するのが良い戦略です。
アルゴリズムだけでなく、送るデータの種類が圧縮効果の最大要因です。あるペイロードは劇的に縮み、あるものはほとんど変わりません。
テキスト中心で繰り返しが多いものは非常によく縮みます:
繰り返しと構造が多いほど圧縮率は良くなります。
ProtobufやMessagePackのようなバイナリ形式はJSONよりコンパクトですが“ランダム”というわけではありません。繰り返しタグや類似のレコード構造を含むことが多く、大きめのレスポンスやリスト中心のエンドポイントでは圧縮効果があることが多いです。確実なのは実データでテストすることです。
内部的に圧縮を使っている形式に対してHTTP圧縮を上書きしてもほとんど削減効果がなく、レスポンス時間を悪化させることがあります:
これらはContent-Typeベースで圧縮を無効にするのが一般的です。
単純に次を適用するのが有効です:
こうすることでCPUを実際に効果があるペイロードに集中させられます。
圧縮はクライアントとサーバーがエンコーディングで合意して初めてスムーズに動きます。合意は Accept-Encoding(クライアント送信)と Content-Encoding(サーバー送信)で行われます。
クライアントがデコード可能なものを宣言します:
GET /v1/orders HTTP/1.1
Host: api.example
Accept-Encoding: zstd, br, gzip
サーバーはひとつを選んで宣言します:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: zstd
もしクライアントが Accept-Encoding: gzip と送っているのにサーバーが Content-Encoding: br を返すと、そのクライアントはボディをパースできない可能性があります。クライアントが Accept-Encoding を送っていない場合、最も安全なのは圧縮を行わないことです。
実用的な順序の一例:
zstd(速度と比率のバランスが良い)br(テキストでより小さくなることが多い)gzip(最も互換性が高い)つまり一般には zstd > br > gzip の順です。ただしトラフィックがブラウザ中心なら br を上位にする、古いモバイルクライアントが多ければ gzip を優先する、といった調整が必要です。
複数のエンコーディングで同じレスポンスを提供しうる場合は、次を追加してください:
Vary: Accept-Encoding
これがないとCDNやプロキシがあるエンコーディングをキャッシュして、別のデコーダしか持たないクライアントに誤って返してしまう恐れがあります。
一部のクライアントはサポートを主張していてもデコーダがバグっていることがあります。堅牢にするために:
zstd 関連のデコードエラーが増えたら一時的に gzip にフォールバックするネゴシエーションはバイトを最大限削ることより「クライアントを壊さない」ことが重要です。
圧縮は単独では動作しません。トランスポートプロトコル、TLSのオーバーヘッド、CDNやゲートウェイの動作は実際の挙動を変え、誤設定だと動作を壊すことすらあります。
HTTP/2では複数のリクエストが単一のTCP接続を共有します。接続オーバーヘッドは減りますが、パケットロスが発生するとTCPのヘッドオブラインブロッキングで複数ストリームが停滞することがあります。圧縮はレスポンスボディを小さくしてこの影響を減らすのに役立ちます。
HTTP/3はQUIC上で動き、ストリーム間でTCPレベルのヘッドオブライン問題がないため、損失時の影響が少ない傾向にあります。それでもペイロードサイズは重要で、圧縮は帯域削減や「最後のバイトまでの時間」短縮として効果を発揮します。
TLS自体もCPUを消費します(ハンドシェイク、暗号/復号)。高レベル圧縮を加えるとピーク時にCPU予算を超えることがあるため、実運用では「速い圧縮でまずは良好な比率を得る」設定が有利なことが多いです。
CDNやゲートウェイの中には特定のMIMEタイプに自動で圧縮を適用するもの、オリジンからの Content-Encoding をそのまま通すもの、ヘッダーを変更してしまうものがあります。ルートごとの挙動を確認し、Vary: Accept-Encoding が保持されることを確認してください。
エッジでキャッシュする場合はエンコーディングごとに別バリアントを保存する(gzip/br/zstd)方がよいです。オリジンでキャッシュする場合でも、エッジがネゴシエートして複数エンコーディングをキャッシュする構成を検討してください。重要なのは整合性です:正しい Content-Encoding、Vary、圧縮の責務の明確化。
Accept-Encoding: br を送るなら Brotli を優先。ブラウザはBrotliの復号を効率的に行い、テキストでのサイズ削減が期待できる。まずは安全側のレベルから始める:
より強い圧縮が必要なら、プロダクションに近いサンプルでp95/p99を観測してから上げてください。
小さいレスポンスを圧縮するとCPUが節約分を上回ることがあります。実用的な開始点:
比較対象は (1) 削減バイト数、(2) 追加されたサーバー処理時間、(3) エンドツーエンドのレイテンシ変化です。
圧縮を機能フラグの背後に置き、段階的に有効化します。ルート単位の設定(/v1/search は有効、既に小さいエンドポイントは無効)を用意し、クライアントのトラブルシュート用に Accept-Encoding: identity でオプトアウトできるようにします。キャッシュを正しくするために常に Vary: Accept-Encoding を含めてください。
素早くAPIを生成してデプロイするワークフローでは(例えばReactフロントエンド+Goバックエンドのような)、圧縮は「小さな設定で大きな影響」を与えるチューニングポイントです。プロダクトが安定してきたら(ペイロード形状が見えてきたら)圧縮とキャッシュヘッダーをチューニングするのが現実的アプローチです。
圧縮は簡単にデプロイできますが、間違えると簡単に壊れます。運用機能として段階的に展開し、影響を測定し、ロールバックを容易にしておいてください。
Content-Encoding(例: zstd)を返す簡単にロールバックできる手段を用意します:
gzip にフォールバック)よくある原因:
Content-Encoding があるがボディが圧縮されていない(またはその逆)Accept-Encoding を無視している/対応していないエンコーディングを返しているContent-Length、プロキシ/CDNによる破壊デバッグ時は生のヘッダー/ボディを取得して既知のツールで復号できるか確認してください。
サポートするエンコーディングをドキュメントで明記し、例を示してください:
Accept-Encoding: zstd, br, gzipContent-Encoding: zstd(またはフォールバック)SDKを配布しているなら、小さなコピペ可能な復号例と、BrotliやZstandardをサポートする最小バージョンを明記してください。
レスポンスがテキスト中心(JSON/GraphQL/XML/HTML)で、中〜大サイズであり、ユーザーが遅い/高コストのネットワークを使っている、あるいはあなたが送信データ量に対して課金されている場合に有効です。小さすぎるレスポンス、すでに圧縮されているメディア(JPEG/MP4/ZIP/PDF)や、追加の処理がp95/p99レイテンシを悪化させるCPUがボトルネックのサービスではスキップするか、高い閾値を使ってください。
圧縮は帯域をCPU(と場合によってはメモリ)に交換します。圧縮処理にかかる時間がサーバーが最初のバイトを送れる時点(TTFB)を遅らせるため、特に高帯域・低遅延の接続ではレスポンスが小さくなっても全体では遅くなることがあります。負荷時にはキューイングが発生して尾部レイテンシ(p95/p99)が悪化することがよくあるため、バイト数だけで判断せず、エンドツーエンドの時間で評価してください。
多くのAPIで実用的な優先順位は次の通りです:
zstd を最優先(高速で良好な比率)br(テキストで最小になりやすいがCPUコストが高い場合あり)gzip(互換性が最も広い)最終的にはクライアントが で何を送ってくるかに基づいて選び、常に安全なフォールバック(通常は や )を用意してください。
まずは低めで測定することを推奨します。
最小サイズの閾値を設定して、小さいペイロードでCPUを浪費しないようにします。
エンドポイントごとにバイト削減量、追加されたサーバー処理時間、p50/p95/p99への影響を比べてチューニングしてください。
構造化され繰り返しが多いコンテンツはよく縮みます:
HTTPの交渉に従ってください:
Accept-Encoding を送る(例: zstd, br, gzip)Content-Encoding を返すクライアントが を送らなければ、通常はのが最も安全です。クライアントが広告していない を返すとデコードに失敗する恐れがあります。
必ず応答に次を含めてください:
Vary: Accept-Encodingこれがないと CDN やプロキシがあるエンコーディング(例: gzip)をキャッシュしてしまい、そのキャッシュをデコードできないクライアントに誤って返してしまうことがあります。複数のエンコーディングをサポートする場合、このヘッダーは必須です。
よくある失敗モードは以下です:
パフォーマンス機能として段階的にロールアウトしてください:
Accept-Encodinggzipidentity高いレベルはわずかなサイズ改善のためにCPUを大きく消費し、p95/p99を悪化させることが多いので注意してください。
一般的にはテキスト系の Content-Type にのみ圧縮を有効にし、既に圧縮されている形式は無効にするのが簡単で安全です。
Accept-EncodingContent-EncodingContent-EncodingAccept-Encoding を無視して返す)Content-Lengthデバッグ時は生のレスポンスヘッダーをキャプチャして、既知のツールでデコードができるかを確認してください。
負荷時に尾部レイテンシが上がるなら、レベルを下げる、閾値を上げる、あるいはより高速なコーデック(多くの場合 ZSTD)に切り替えてください。