インフラ・DevOps
Zstandard と Pingora でペタバイト級のキャッシュストレージを節約できる可能性
We could save petabytes of cache storage with Zstandard and Pingora (blog.cloudflare.com)
要約
Cloudflare は、Zstandard (zstd) を使用してキャッシュ内のアセットを圧縮し、ストレージ容量とデータセンター間の帯域幅を大幅に節約する「Cache Transcoding」というシステムを開発しました。このアプローチは、CPU 使用率のわずかな増加と引き換えに、キャッシュ効率を向上させ、インフラストラクチャコストを削減します。
全文翻訳
メモリコストが劇的に増加しています。
RAM とハードディスクドライブの両方の価格が過去1年間で急騰しました。
Cloudflare では、お客様すべてにサービスを提供し続けるために、展開しているメモリを効率的に使用することに依存する、いくつかの大規模分散ストレージ製品(有名な CDN を含む)を運用しています。
これを念頭に置いて、効果的なキャッシュ容量を拡張する方法のプロトタイプを作成しました。
Pingora 内で Zstandard を使用して適格なアセットをエンコードすることにより、アーキテクチャはわずかな CPU 増加と引き換えに、ストレージとデータセンター間の帯域幅の大幅な節約を実現します。
私たちは Cache Transcoding と呼ばれるシステムのプロトタイプを作成しており、これは Cloudflare での 1.1.1.1 Intern Program の一環としてのインターンシップ中に私が構築したものです。
適格なレスポンスがキャッシュに入ると、ディスクに書き込む前に Zstandard、または zstd を使用してエンコードします。
アセットがキャッシュ内に存在し、Tiered Cache を介してデータセンター間を移動する間、この圧縮形式を保持し、クライアントにレスポンスを配信する前にデコードします。
初期テストでは、このエンコーディングにより、適格なアセットはディスク上のサイズが平均で ⅓ に縮小されました。
オリジン向きプロキシでの推定追加 CPU コストはわずかでしたが、それがトレードオフです。
CPU のわずかな増加は、Cloudflare にペタバイト級の効果的なキャッシュ容量をもたらし、データセンター間のデータ転送を削減します。
エンコーディングコストは、アセットがキャッシュに入るときに一度だけ支払われます。
ストレージと帯域幅の節約は、アセットが再利用されるたびに継続します。
Zstandard とは何か?
Zstandard、または zstd は、Facebook の Yann Collet によって開発され、2016 年にオープンソース化されたロスレス圧縮アルゴリズムです。
ロスレスとは、圧縮データがデコードされた後、すべてのバイトがオリジナルと同一であることを意味します。
アセット自体を変更することなく、ディスク上のアセットの表現を変更できます。
Zstd は、圧縮率と速度のバランスを取るように設計されています。
以前のブラウザ圧縮テストでは、Brotli よりも 42% 高速にデータを圧縮し、ほぼ同じファイルサイズを生成し、gzip よりも 11.3% 小さいファイルを同等の速度で生成しました。
Cache Transcoding は大量のトラフィックを処理するため、エンコードとデコードの両方が高速である必要があるため、このバランスが重要です。
プロトタイプでは zstd レベル 3 を使用しており、キャッシュフィルが CPU ボトルネックにならないように、ほとんどの圧縮メリットを得ています。
Cloudflare は伝統的に、オリジンから提供されたコンテンツエンコーディングを使用してアセットを保存しています。
オリジンが非圧縮レスポンスを送信する場合、ディスクに非圧縮バイトを保存し、同じ形式でデータセンター間で転送します。
Cache Transcoding は、キャッシュ自体の中に圧縮を追加します。
すべてをトランスコードする価値があるわけではない
トランスコーディングは、すべてを圧縮することを意味しません。
画像、ビデオ、フォントは通常すでに圧縮されています。
トラフィックサンプルの場合、このメディアスライスはリクエストの 21.4% を占めましたが、バイトの 63.3% を占めました。
これを再度圧縮しても、CPU を無駄に消費するだけです。
圧縮可能なテキストは異なります。
HTML、JSON、CSS、JavaScript はリクエストの 67.3% を占めましたが、バイトの 22.3% を占めました。
このテキストスライス内では、約 71% が Content-Encoding が未設定の状態で非圧縮で到着し、よく圧縮されます。
制御されたテストコーパスでは、適格なアセットは約 2.8 倍に圧縮されました。
測定値
値
圧縮率
2.834x
エンコードコスト
4.31 ns/バイト、約 232 MB/s、フィルごとに一度支払い
デコードコスト
1.56 ns/バイト、約 641 MB/s、サーブごとに一度支払い
エンコーディングはバイトあたりのコストが高いですが、アセットはフィルされるよりもはるかに頻繁にサーブされます。
アセットの表現を変更することで、既存のハードウェアでより多くの顧客コンテンツを保存できるようになります。
ディスク上のバイトが少ないということは、各サーバーがより多くのオブジェクトを保持できることを意味します。
これによりキャッシュ密度が増加し、非圧縮表現が必要以上にスペースを消費したために、有用なコンテンツがエビクションされる可能性が低くなります。
より小さい表現は、アセットが Tiered Cache を通過する際にも役立ちます。これは、Cloudflare データセンター間のデータ転送を削減し、バックボーンの使用をより効率的にするためです。
圧縮コストを一度支払う
圧縮は決して無料ではありません。
エンコードとデコードは両方とも CPU を使用するため、重要な質問は、バイトの節約が処理コストに見合うかどうかということです。
zstd レベル 3 (多くの場合、速度と圧縮サイズ出力のデフォルトのバランス) では、テストしたトラフィックと再利用の仮定の下で、CPU の追加コストを数パーセントに抑えました。
再利用が多いホットアセットにのみトランスコーディングを限定することを当初検討しましたが、うまくいきませんでした。
デコードはアセットがサーブされるたびに発生するため、この機能を最もホットなコンテンツのみに限定すると、CPU を同じ量削減せずにストレージの節約が減少しました。
より単純なポリシーの方がパフォーマンスが向上しました。
適格な圧縮可能なテキストを 4 キビバイト (KiB) 以上でトランスコードすると、測定されたストレージメリットのほぼすべてを捉えながら、CPU 予算内に収まりました。
Cache Transcoding の仕組み
キャッシュミスの場合、Pingora ベースのプロキシはディスクに書き込む前に zstd を使用してボディをエンコードします。
キャッシュメタデータは、保存された表現が圧縮されていることを記録し、元のコンテンツ長を保持します。
レスポンスがプロキシを離れる前に、ボディは元のアイデンティティ表現にデコードされます。
キャッシュヒットの場合、保存された zstd オブジェクトはディスクから読み込まれ、デコードされます。
Tiered Cache では、圧縮された表現が圧縮形式で上位ティアから下位ティアに転送されます。
デコードはクライアント側のホップでのみ発生します。
完全なキャッシュミスの場合、上位ティアはオリジンからアイデンティティバイトを取得します。
これらのバイトは一度エンコードされ、zstd として保存され、圧縮形式で下位ティアに転送されます。
下位ティアも zstd 表現を保存し、その後リクエストパスのためにデコードします。
下位ティアがミスしても上位ティアがオブジェクトをすでに持っている場合、オリジンは関与しません。
圧縮オブジェクトはキャッシュティア間で直接移動します。
ワイヤ上およびディスク上では圧縮されたままで、その後下位ティアで一度デコードされます。
下位ティアがすでにオブジェクトを持っている場合、ネットワーク転送やエンコーディングは必要ありません。
下位ティアはディスクから zstd バイトを読み込み、デコードして、元のアセットを転送します。
ストレージエンコーディングマーカーは、オブジェクトが複数回エンコードされるのを防ぎます。
別のティアからオブジェクトを受信するキャッシュレイヤーは、それがすでに zstd として保存されていることを確認し、その形式で保持できます。
なぜ特定のテキストのみをトランスコードするのか
最も高速な圧縮操作は、実行する必要のない操作です。
したがって、Cache Transcoding は、メリットが期待できないコンテンツを回避するために、一連の適格性チェックを使用します。
プロトタイプは、Content-Encoding が未設定で、Content-Type が圧縮可能なテキストであり、レスポンスが 4 KiB 以上の既知の Content-Length を持つ場合にのみ、200 OK レスポンスをトランスコードします。
スライスサブリクエスト、アクティブなアップストリーム圧縮を使用するレスポンス、レンジリクエスト、プリコンプレスされたレスポンス、不明な長さのボディ、バイナリコンテンツは変更されません。
4 KiB のしきい値は、多数の小さなリクエストを削除しましたが、それ以外で適格なバイトの約 1% しか除外しませんでした。
これを下げると、オブジェクトあたりのオーバーヘッドが増加しますが、ストレージの節約はあまり増えません。
しきい値と zstd レベルは両方とも、永続的な制限ではなくパラメータです。
私たちは、レベル 3 と 4 KiB の最小値から始めました。これは、アーキテクチャを測定するための保守的な方法を提供したためです。
初期の CPU 予算が理解されたら、より高い圧縮レベルが追加コストを正当化するのに十分な比率を改善するかどうかをテストできます。
キャッシュを介した 100 万件以上のリクエストのテスト
制御されたテストゾーンでプロトタイプを実行し、リクエストログ、Prometheus メトリクス、Jaeger トレース全体で各リクエストを相関させました。
正確性キャンペーンでは、キャッシュミス、キャッシュヒット、シングルホップフィル、Tiered Cache フィルなどをカバーしました。
各リクエストが特定のパスをたどるようにキャッシュキーを変更し、トレースを使用してエンコードとデコードが発生した場所を確認しました。
パフォーマンスキャンペーンでは、10 台のキャッシュサーバー全体で 100 万件以上のリクエストを送信しました。
キャンペーンの半分は Tiered Cache を無効にして実行され、もう半分は...