9月1日、Cloudflareが「How we could save petabytes of cache storage with Zstandard and Pingora」と題した記事を公開した。ZstandardとPingoraを組み合わせることで、キャッシュストレージをペタバイト規模で削減できる「Cache Transcoding」の設計と実験結果を紹介したものだ。
驚くべきことに、このプロトタイプはCloudflareのインターンプログラムから生まれた。設計の核心は拍子抜けするほどシンプルだ。圧縮コストはキャッシュへの格納時に1回だけ払えばいい。あとはキャッシュヒットのたびに圧縮済みオブジェクトを読み出して展開するだけで済む。アセットの配信回数はキャッシュへの格納回数を大きく上回るため、この非対称性が大きな節約を生む。初期テストでは対象アセットがディスク上で平均して元サイズの約3分の1(圧縮率2.8倍)に圧縮されており、既存ハードウェアの実効キャッシュ容量を大幅に拡大できる可能性を示している。
背景:大規模CDNにおけるキャッシュ効率化の必然性
Cloudflareは世界120カ国以上でCDNをはじめとする大規模分散インフラを運用しており、ストレージとネットワーク帯域のコスト最適化は常に重要な課題だ。キャッシュ容量を増やすには新しいハードウェアを追加する方法が王道だが、今回の取り組みは既存ハードウェアの実効キャッシュ容量そのものを底上げする別のアプローチを模索したものだ。
キャッシュサーバー上で動作するリバースプロキシフレームワークとしては、CloudflareがRustで開発したオープンソースのプロキシフレームワークPingora(Cloudflare公式ブログによる解説)が使われている。Cache TranscodingはこのPingora上に実装されており、HTTPレスポンスの処理パイプラインにシームレスに組み込まれている。
「圧縮コストは1回だけ払えばいい」という設計の面白さ
Cache Transcodingの仕組みはこうだ。オリジンサーバーからのレスポンスをキャッシュに書き込む際にZstandard(zstd)で圧縮し、クライアントへ配信する際に展開する。圧縮はキャッシュに格納されている間ずっと維持され、データセンター間のTiered Cache転送でも圧縮状態のまま転送される。
具体的な数字は以下のとおりだ。
| 指標 | 値 |
|---|---|
| 圧縮率 | 2.834倍 |
| エンコードコスト | バイトあたり4.31 ns(約232 MB/s)、格納時に1回だけ |
| デコードコスト | バイトあたり1.56 ns(約641 MB/s)、配信のたびに発生 |
エンコードはデコードの約2.8倍重い処理だが、1つのオブジェクトが何十・何百回とキャッシュヒットされることを考えれば、トータルのCPUコストは十分ペイする。使用するzstdのレベルは3(速度と圧縮率のバランスが取れたデフォルト近辺)で、CPUコストは実験条件下で数パーセント増にとどまった。
何を圧縮するか:対象の絞り込みが重要
すべてのコンテンツを対象にするわけではない。画像・動画・フォントはすでに圧縮済みのものが多く、再圧縮してもCPUを無駄遣いするだけだ。
Cloudflareのトラフィックサンプルでは、メディア系がリクエストの21.4%だがバイト数の63.3%を占める。一方、HTML・JSON・CSS・JavaScriptなどのテキスト系はリクエストの67.3%でバイト数の22.3%にとどまる。ただし、このテキスト系の約71%がContent-Encoding未設定の非圧縮状態で届いており、圧縮効果が高い領域として狙い目となる。
プロトタイプの適用条件は次のとおりだ。
- レスポンスが
200 OK Content-Encodingが未設定(非圧縮)Content-Typeが圧縮に適したテキスト系Content-Lengthが既知かつ4 KiB以上
4 KiBの閾値は、小さすぎるオブジェクトへの処理を省きつつ、対象バイト数の約99%をカバーできる設定として導き出された。レンジリクエストや事前圧縮済みレスポンスは対象外となる。
Tiered Cache内での動作
Tiered CacheはCloudflareがデータセンター間に設けるキャッシュ階層で、上位ティアが下位ティアへオブジェクトを転送する仕組みだ。Cache Transcodingとの組み合わせでは、圧縮済みオブジェクトが階層間をそのまま圧縮状態で転送される。デコードはクライアント向けの最終ホップで1回だけ行われるため、バックボーン帯域の節約にも貢献する。
100万リクエストを超えるテストと現時点での限界
プロトタイプは10台のキャッシュサーバーに対して100万件超のリクエストを投じて検証された。Tiered Cacheあり/なしの2条件で実施し、キャッシュミス・ヒット・単一ホップ・Tiered Cache経由の各パスを網羅した。Prometheusメトリクス・Jaegerトレースとリクエストログを突き合わせ、エンコード・デコードが正しい箇所で発生していることも確認した。
ただし、テストコーパスは意図的に圧縮が効きやすいテキストコンテンツ(約195 KiBと272 KiBの2アセット)で構成されており、測定された2.8倍という圧縮率をそのままフリート全体に当てはめることは現時点ではできない。より広範なコンテンツタイプでの検証が次のステップとされている。
今後の課題としては、より高いzstdレベルの評価、対象コンテンツタイプ・オブジェクトサイズの拡張、レンジリクエストへの対応、圧縮済みオブジェクトをデコードせずに下流コンポーネントへそのまま渡す仕組みの実装などが挙げられている。zstdレベルとサイズ閾値はパラメータとして実装されており、フリート全体への展開に向けたチューニングの余地を残している。
詳細はHow we could save petabytes of cache storage with Zstandard and Pingoraを参照していただきたい。