ブログに戻る

フォロー&ご登録

キャッシュヒット率の真実

Hooman Beheshti

VP of Technology, Fastly

キャッシュは CDN によって提供される主要なサービスの1つであり、CDN のパフォーマンスを評価する最も一般的な指標の1つがキャッシュヒット率 (CHR) です。長年にわたり CDN のお客様は、CDN がどの程度ユーザーに優れたサービスを提供し、トラフィックを処理しているかを判断する主要な指標として CHR を使用してきました。ダッシュボードに「キャッシュヒット率98 %」と表示されることも珍しくなく、その場合、エンドユーザーが CDN のメリットを最大限に得ていると簡単に納得しがちです。

しかし CHR は目に見える以上にはるかに多くの意味を含んでおり、私たちが普段、重視しているこの指標は私たちが考えていることとは別のことを意味している可能性があります。そこで今回は、CHR が実際に測定している中身について解説し、それを計算して評価する新しい方法の必要性について考察したいと思います。

How to Calculate a CHR

長年にわたり、CDN は以下の式を使用して CHR を計算してきました。

requeststotal は CDN によって受信されたクライアントリクエストの合計数で、requestsorigin はそのうちオリジンに到達したリクエストの数です。簡単に言えば、CDN に100件のリクエストを送信し、そのうちの1件が CDN を離れてオリジンに到達した場合、CHR は99 %になります。

「キャッシュヒット率99 %」と聞くと、ユーザーのリクエストの99 % が CDN のエッジ (リクエストを実行したクライアントに最も近いキャッシュ) からコンテンツを提供されていると本能的に考えがちですが、実際には必ずしもそうであるとは限りません。その理由を理解していただくために、CDN アーキテクチャ、キャッシュ階層、ロングテールコンテンツについて説明します。

What is a good CDN Cache Hit Ratio?

A good CDN cache ratio score depends on the website, and a higher score does not necessarily mean "better". A static website could have a cache ratio of 95-99%, but a more dynamic website may see much lower numbers. Additionally, a miss ratio of one to five percent is ideal for any website.

How CDNs are built

I don't want to go into too much detail, and I certainly can't speak for all CDNs, but at the highest level, it goes without saying that a CDN is a distributed network of caching proxies. These proxies, and therefore their caches, are shared. They receive and cache requests across a large number of domains, all of them belonging to the CDN's customers. This means that the storage of each server is also shared. Since storage is finite, there's always some sort of algorithm that usually includes eviction models for management of that storage. While oft-requested objects will likely stay in caches longer (assuming they haven't gone past their freshness lifetime), objects requested less frequently are more likely to get evicted from a cache even if their Cache-Control headers say they should be cached longer.

But just because storage management causes a still-fresh object to be evicted from one cache server doesn't mean that it'll necessarily be evicted from the entire CDN and all its cache servers. CDNs usually have some sort of hierarchical model deployed, where if a server that the client is communicating with doesn't have the object in its [cache](https://www.fastly.com/blog/clearing-cache-browser), it'll likely ask a peer or a parent for the object before trying to fetch it from the origin. The problem is that the peer or the parent isn't always next to the server the client is connected to. So the time it takes to fetch the object and serve it to the client could suffer. This is better illustrated with a diagram:

エッジキャッシュがクライアントからリクエストを受け取ると、クライアントとその特定のマシンの間に TCP 接続が確立されていても、レスポンスは至るところにあるストレージから送信される可能性があり、ストレージはそのサーバー上にあるとは限りません。オブジェクトはそのマシンのメモリ (最良のシナリオ)、そのマシンのディスクストレージ (SSD が望ましいです。そうでない場合、余分な負荷がかかります)、ローカルのピア/親、ローカルでないピア/親から提供される可能性があります。そして、オブジェクトの提供元がネットワークエッジから遠ざかれば遠ざかるほど、チェーン全体を通じてパフォーマンスが低下します。

究極的には通常、オブジェクトの提供元はそのオブジェクトがフェッチされる頻度と直接関係します。リクエストの頻度が低いほど、クライアントが接続されているエッジキャッシュでミスとなる可能性が高くなります。言い換えれば、ロングテールコンテンツ (ソーシャルメディアの投稿、プロフィール画像、e コマースサイトの大きなインベントリなど、あまり頻繁にフェッチされないコンテンツ) も CDN から提供される可能性はありますが、必ずしもクライアントの近くのキャッシュからとは限りません。

しかし、ほとんどの CDN は CHR を計算する際、オリジンに到達していない限り、いずれかのキャッシュから受け取ったレスポンスをすべて「ヒット」と見なします。これが CHR が指標として誤解を招き、CDN が顧客に提供しているパフォーマンスに関して誤った認識をもたらす原因です。また、これは CDN のオブジェクトストレージモデルが重要な理由でもあります。エッジに十分な密度がない、エッジで適切なスケーリングが行われない、最適化された削除アルゴリズムがない環境でデプロイされている場合、CHR は高く見えますが、ロングテールコンテンツの多くが CDN の奥まったところから提供される可能性があります。

CHR のより望ましい計算方法

従来の CHR の計算方法では分からない部分があるため、私たちにとって重要なこの指標の計算方法を再考する必要があります。パフォーマンスのインジケーターとなる指標として、私たちが本当に知りたいのは、ネットワークのエッジにある CDN キャッシュから提供されるオブジェクトの割合です。以下ような計算式になります。

これは実際には以下の計算式と同じです。

hitsedgemissesedge は、それぞれネットワークエッジでのキャッシュヒット数とキャッシュミス数を表します。この CHR の計算式では、ユーザーから最も近い、ネットワークのエッジから提供されたキャッシュ可能なリクエストの割合が正確に分かります。

実際には従来の計算方法はいまだに非常に有用です。オリジンに到達しないリクエストの割合がわかるため、サーバーのオフロードを測定するのに非常に便利な指標です。しかし、パフォーマンスを評価するには、エッジで何が起きているのかに注目する必要があります。最善の方法は、以下のように2つの異なる CHR 指標を考慮することです。ひとつはエッジにおける CHR です。

もう1つは全体的な CHRです。

CHRedge is a performance metric and CHRglobal is one for offload. They’re both valuable and insightful, but they’re telling different stories. You should use  CHRedge to gauge how much of your content is being served from the edge of the network, closer to your users. This translates directly to performance benefits. CHRglobal, on the other hand, will tell you how much traffic is kept off of your origin. This translates directly to processing and infrastructure offload, which can lead to great cost savings.

CHRedge はパフォーマンスを測定する指標で、CHRglobal はオフロードを測定する指標です。両方とも貴重な指標で重要なインサイトをもたらしますが、それぞれ意味する内容は異なります。This はユーザーに近い、ネットワークのエッジから配信されているコンテンツの割合を測定します。これは直接、パフォーマンス上のメリットに換算されます。一方 CHRglobal は、オリジンに到達しないトラフィックの割合を示します。これは直接、プロセスとインフラストラクチャのオフロードに換算され、コストの大幅な節約につながる可能性があります。

Calculating cache hit ratios with Fastly

Fastly のネットワークでは、すべての配信拠点 (POP) をエッジに配置して構築されており、Fastly のネットワークマップですべてのエッジのロケーションを確認できます。拡張性とストレージの密度を高めるため、各 POP 内にお客様やエンドユーザーのリクエストに対して透過的な、キャッシュ階層のレイヤーを設けています。これにより、あるリクエストがクライアントが接続されているサーバーではキャッシュミスであっても、それが通信している POP の内部のキャッシュからコンテンツが提供される可能性が高く、その場合エッジではキャッシュヒットになります。また、キャッシングの追加レイヤーとしてオリジンシールドをデプロイすることで、全体のキャッシュヒット率を高め、オリジンへのトラフィックを削減できます。

コントロールパネルを介して、または Stats API を通じて報告される CHR は以下のように計算されます。

オリジンシールドを使用しないサービスでは、これは _CHRedge_と _CHRglobao_の両方に事実上相当します。すべてのヒットはエッジにおけるヒットであり、すべてのミスはエッジにおけるミス (オリジンへのリクエスト) であるためです。

サービスでオリジンシールドが使用されている場合、つまりシールド POP によって別のレベルのキャッシュが追加される場合、上記の数式は成立しますが、ヒット数/ミス数にはシールドのヒットやミスも含まれます。つまり、一部のリクエストが2回カウントされるハイブリッドな測定基準です。シールドを使用するサービスの CHRedgeCHRglobal を分離し、それぞれ独立して計算する方法はいくつかありますが、私は通常 Fastly のリアルタイムログストリーミングを使用しています。

以下の VCL スニペットで、複数のローカル変数を使用した CHR の計算用のログストリーミングをセットアップします。

sub vcl_log {
#FASTLY log
  declare local var.cache_status STRING;
  declare local var.request_type STRING;
set var.cache_status = if(fastly_info.state ~ "HIT($|-)", "HIT", if(fastly_info.state ~ "PASS($|-)", "PASS", "MISS"));
set var.request_type = if(req.http.fastly-ff, "shield", "edge");

log {"syslog req.service_id logging_endpoint :: "} var.cache_status "," var.request_type "," server.datacenter;
}

ここでは、ローカル変数 cache_statusHITMISSPASS のいずれかになります。PASS はキャッシュ可能なオブジェクトではないため計算から除外します。ローカル変数 request_type は、コンテンツがシールド POP またはエッジ POP (または Fastly の別の POP ではなく、クライアントと直接通信するシールド POPで機能的にはそのリクエストのエッジ POPとなる) のいずれかから提供されたリクエストであることを示します。次に cache_statusrequest_type、3文字の POP コードを示す server_datacenter を記録します。

この VCL では、ログエントリは以下のように表示されます。

Jan  7 23:15:26 cache-sjc3131 logging_endpoint[218999]: HIT,edge,SJC
Jan  7 23:15:33 cache-sjc3638 logging_endpoint[348011]: HIT,edge,SJC
Jan  7 23:15:40 cache-iad2120 logging_endpoint[342933]: MISS,shield,IAD
Jan  7 23:15:40 cache-sjc3644 logging_endpoint[341583]: MISS,edge,SJC
Jan  7 23:16:07 cache-iad2127 logging_endpoint[342933]: HIT,edge,IAD
Jan  7 23:16:24 cache-iad2127 logging_endpoint[14817]: MISS,edge,IAD
Jan  7 23:16:40 cache-iad2120 logging_endpoint[218999]: HIT,shield,IAD
Jan  7 23:16:40 cache-sjc3624 logging_endpoint[438579]: MISS,edge,SJC
Jan  7 23:23:25 cache-iad2122 logging_endpoint[342933]: PASS,shield,IAD
Jan  7 23:23:25 cache-sjc3140 logging_endpoint[348011]: PASS,edge,SJC

これで、これらのログ行から CHRedgeCHRglobal を簡単に計算できます。

以下の意味になります。

  • hitedgecache_status = “HIT” かつ request_type = “edge” のログエントリです。

  • missedgecache_status = “MISS” かつ request_type = “edge” のログエントリです。

同様に

以下の意味になります。

  • missshieldPOP は、cache_status = ”MISS” かつ server.datacenter = <your_shield_pop> のログエントリ

  • hitedgecache_status = “HIT” かつ request_type = “edge” のログエントリです。

  • missedgecache_status = “MISS” かつ request_type = “edge” のログエントリです。

バージニア州ダレス (IAD) の POP をオリジンシールドとして使用する上記のサンプルログエントリを例に以下を計算します。

そして

これは、なぜ2つの指標がそれぞれ異なる内容を意味し、別々に評価される必要があるのかを示すシンプルかつ良い例です。

ログストリーミングを使用することで、シールドを使用するサービスの2つの指標を簡単に分離できますが、今後、Fastly の統計情報システムを通じてこれらの指標を個別に報告し、より簡単に利用できるようにすることに取り組んでいます。

最後に

Fastly では、エッジですべきことや、なぜそれがエンドユーザーにとってメリットがあるのかについてよく話し合います。キャッシュは今後もコア機能の1つであり、CDN がどれほど効率的にエッジでコンテンツをキャッシュしているか、常に注意する必要があります。それには CDN がキャッシュヒット率を報告する方法と、それがコンテンツやエンドユーザーのエクスペリエンスにとってどのような意味があるのかを理解することが重要です。エッジとグローバルの両方でキャッシュヒット率を計算することで、コンテンツがどのように提供されているのかを本当の意味で理解することができます。