Cloudflare、Workers Cache を発表——キャッシュを各 Worker の手前に前置、ヒット時は CPU 課金なし
- Cloudflare は本日、Workers Cache を発表。リージョン階層キャッシュを各 Worker の手前に直結し、Wrangler の設定を一行加えるだけで有効化できる。
- キャッシュヒット時は Worker が一切実行されず、CPU 課金も発生しない。未ヒット時のみコードが実行され、上下2層のキャッシュを埋め戻す。
- キャッシュはもうドメイン(zone)に紐づかず、Worker についてまわる——workers.dev、プレビュー環境、Workers for Platforms でも使え、キャッシュのパージも自分自身にしか影響しない。
- 最大の突破口は、キャッシュを同一 Worker 内部の任意のエントリーポイント間に差し込めること。ctx.props と組み合わせれば、ログイン済みユーザー向け API でも安全にキャッシュを共有できる。
- 本日よりすべてのプランの Worker で利用可能。単独の製品ではなく、追加料金の項目もない。
Worker 自身もキャッシュを持てるようになった
Cloudflare は本日、Workers Cache をリリースした。Worker の手前に直結する1層のキャッシュだ。有効化は設定に一行加えるだけ、制御はあなたがとっくに知っている標準の HTTP キャッシュヘッダーを使う。
注目すべき理由:Cloudflare の公式見解によれば、キャッシュを単一のデプロイ単位の内部に埋め込み、エントリーポイントごとに個別にオン・オフできるプラットフォームは他に見たことがなく、「ログイン済みユーザーの API が安全にキャッシュを共有する」ことを組み込み機能にしている CDN も他に見たことがないという。
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-05-01",
"cache": {
"enabled": true
}
}
有効化した後、どうキャッシュするかは HTTP がずっとやってきた方法に任せる——レスポンスにヘッダーを設定するだけだ。
return new Response(body, {
headers: {
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
"Cache-Tag": "products,product:123",
},
});
await ctx.cache.purge({ tags: ["product:123"] });
API はこれで全部だ。zone を設定する必要もなく、ルールエンジンを組む必要もなく、別途キャッシュを開通する必要もなく、2つ目の製品にログインする必要もない。Worker のコードがそのまま設定面になる。キャッシュは Worker がどこで動こうとついてくる——カスタムドメイン、workers.dev、service binding の裏側、プレビュー環境、Workers for Platforms のテナント内、どこであっても1つの Worker には1つのキャッシュ、設定は一度きりだ。
Worker はもともとキャッシュの手前に立っていたが、今は自らオリジンになった
2017年に Cloudflare が Workers を初めてリリースしたとき、売りはコードをネットワークのエッジで動かし、リクエストがオリジンへ向かう途中でそれを書き換えられることだった。あの頃、Worker はキャッシュとオリジンの手前に立っていた。
オリジンとは、あなたのサイトのコンテンツを実際に保持し、ページを生成する責任を負うサーバーのことだ。Worker がその手前に立つとき、リクエストヘッダーを追加したり、URL を書き換えたり、A/B テストで振り分けたり、オリジンに到達する前にトラフィックをフィルタリングしたりできる。当時のそうしたユースケースには、この立ち位置は正しかった——何をキャッシュし何をキャッシュしないか、完全にコントロールできる。
その後、状況は変わった。Astro、TanStack Start、Next.js、Remix、SvelteKit といったフレームワークはどれも Cloudflare 向けアダプターを提供し、アプリケーション全体を1つの Worker にパッケージするようになった。これらの裏にはオリジンが存在せず、Worker 自体がそのサーバーになる。
Worker がオリジンそのものになると、その背後にキャッシュを置いていた旧アーキテクチャには、もうキャッシュするものがなくなる。すべてのリクエストでコードを再実行する羽目になる、たとえ今回返す内容が1秒前とまったく同じであってもだ。Workers のランタイムはリクエストのたびに現場でレンダリングしてもなお十分速い——毎秒数千万リクエストでも息切れしない。しかし「毎回リクエストごとにレンダリングしてもなお十分速い」ことには、依然として2つのコストがついてまわる——ページ読み込みごとの遅延、実行ごとの CPU 時間だ。サーバーサイドレンダリングされるアプリケーションでは、ページ読み込みは定義上つねに1回のレンダリングになる。
これまでは、どちらも気持ちよくない選択肢の中から選ぶしかなかった。
Workers Cache は第3の道を用意した。オンデマンドでサーバーサイドレンダリングし、レンダリング結果をキャッシュし、選んだ有効期間に沿って更新する。新しいページの最初のリクエストは通常通りレンダリングされ、その後はキャッシュが期限切れになるまで、すべてのリクエストが静的ページを訪れるかのように配信される。静的サイトの速さを手に入れつつビルド時間を省き、サーバーサイドレンダリングの鮮度を手に入れつつ継続的なコストを省ける。
キャッシュが逆転し、Worker の手前でブロックする
Workers Cache はデフォルトでリージョン階層の2層キャッシュになっており、しかもこのトポロジーは完全自動で有効になる——何も設定する必要はない。リクエストが入ってきたとき、どの層をまず確認し、いつ実際にあなたのコードが呼び出されるかは、次のように進む。
チェーンのコンビニが仕入れをするようなものだ。まず自店の棚を探し(下層キャッシュ)、なければ地域倉庫に発注し(上層キャッシュ)、倉庫にもなければメーカーに現場で作ってもらう(Worker を実行)。メーカーがこのロットを作れば、棚も倉庫も補充される。次に誰が来ても、もうメーカーの手を煩わせる必要はない。
このトポロジーは、今日 zone 向けに提供されている Tiered Cache と同じもので、違いはあなたがそれを設定する必要がないことだ——「私の Worker の階層キャッシュを有効にして」というスイッチはない。Worker がキャッシュを有効にしさえすれば、階層化はおまけでついてくる。あなたの Worker が Smart Placement を使っている場合も、キャッシュはきれいに重なる——まず2層のキャッシュを確認し、両方とも未ヒットのときだけ、Smart Placement が実行をデータに近い場所へスケジューリングする。
キャッシュ期限切れの瞬間、誰も待たされない
stale-while-revalidate は直訳すると「古いまま再検証」だ。これは Cloudflare にこう指示する——キャッシュが期限切れになったら、まず古い方を即座にユーザーに見せて構わない、その裏で Worker を再実行して新しいものに差し替える、と。これこそが「Worker のキャッシュを代行する」を「Worker サイトが静的サイトのように感じられる」に変えた一手だ。
これがなければ、キャッシュ期限切れ後の最初のリクエストは Worker がゼロからレンダリングするのを待つ羽目になり、その遅延はユーザーに感じ取られる。これがあれば、期限切れ後の最初のリクエストは即座に古いページを受け取り(Cf-Cache-Status: UPDATING ヘッダー付き)、Worker は裏でキャッシュを埋め戻すよう動いている。この更新を引き起こした本人を含め、誰もがキャッシュ並みの速さのレスポンスを受け取る。
ウィンドウはあなたが決める。コンテンツがどのくらいの頻度で変わるかに応じて設定すればいい。
stale-while-revalidate=3600
stale-while-revalidate=2592000
まったく新しいページの最初のリクエストだけが、完全なレンダリングコストを払う。それ以降、訪問者にとってこのページは静的出力とまったく同じ振る舞いをする一方で、ページの生成方法はあなたの Worker が引き続き決める。
このキャッシュは Worker についてまわる、ドメインについてまわるのではない
Cloudflare はもともとキャッシュを持っていたが、それは zone(ドメインの設定単位)ごとに設定するものだった。Cache Rules、Page Rules、キャッシュするファイル拡張子のリスト、Cache Reserve、階層キャッシュのトポロジー、カスタムキャッシュキー、すべて zone に設定されていた。これまで、1つの Worker は所属する zone の設定に合わせるか、それを迂回する方法を考えるしかなかった。
Workers Cache は帰属先を変えた——これはあなたのこの Worker のキャッシュであり、Worker が所有するもので、特定の zone に属さない。これによって実用的なメリットがいくつも生まれる。
- 管理すべき zone 設定がない。Cache Rules、キャッシュレベルの設定、ファイル拡張子リスト、Page Rules は、Workers Cache には一切適用されない。Worker の Cache-Control ヘッダーが設定のすべてになる。
- キャッシュは Worker についてまわる、ホスト名についてまわるのではない。1つの Worker が api.example.com と api.example.net の両方にバインドされ、さらに service binding で呼び出されるとしても、この3つの経路は同じキャッシュを共有する。/users/42 へのリクエストは、どこから入ってきても、同じキャッシュエントリーにヒットする。
- workers.dev でも使える。プレビュー環境はそれぞれ独立したキャッシュを持ち、ある変更のテストが本番を汚染することはない。Workers for Platforms でも各テナントの Worker がそれぞれ独立にキャッシュされ、互いに干渉しない。これらの場所は以前キャッシュにおいて二流の存在だったが、もうそうではない。
- キャッシュのパージは自分のこのエントリーポイントにしか作用しない。purge を呼んでクリアしても、あなたのこの Worker エントリーポイントのキャッシュだけがクリアされ、zone 内の他のコンテンツを誤爆することも、ある Worker のデプロイが別の Worker のデータをクリアしてしまうこともない。
設定したいキャッシュの挙動は、すべてコードに書く。パスごとに違う存続時間を与えたければ、パスで分岐して異なる max-age を設定する。特定のリクエストをキャッシュから外したければ、Cache-Control: private を返す。キャッシュキーの計算方法は、ctx.props に何を入れるかをコントロールしたり、ゲートウェイ Worker で URL を先に正規化したりして制御する。すでに書いた Worker が、そのまま設定面になる。
キャッシュは1つの Worker 内部の任意の継ぎ目に差し込める
Workers Cache はすべての Worker のエントリーポイントの手前でブロックする——デフォルトエクスポートのエントリーポイント、各具名エントリーポイント、そして同じ Worker 内部である1つのエントリーポイントが ctx.exports を通じて別のエントリーポイントを呼び出すその呼び出しも含む。最後のこの一文が、あなたが組み立てられるものを変える。
1つの Worker プログラムは内部でいくつかの独立した「サブエントリーポイント」(entrypoint) に分割でき、コードを書くとき直接お互いを呼び出せる——実際にネットワークリクエストを発して戻ってくる必要はない。あるエントリーポイントが ctx.exports を通じて別のエントリーポイントを呼び出すとき、キャッシュはブラウザからのリクエストと同じように、その呼び出しを評価する。
ある会社の中で、受付(ゲートウェイのエントリーポイント)が来訪者の対応を終えたら、その要求をそのまま社内の担当部署(別のエントリーポイント)に回し、来訪者自身が道路の向かいの別のビルへ走る必要はない、というようなものだ。キャッシュは受付と部署の間に挟まっている——部署の前回の回答がまだ残っていれば、受付はそれをそのまま来訪者に渡し、部署はドアすら開けなくていい。
ヒットすればキャッシュがそのまま返され、呼び出された側のエントリーポイントは一度も実行されない。未ヒットのときだけ一度実行され、自分のエントリーポイント、パス、クエリ文字列、ctx.props に応じてキャッシュが保存される。呼び出し側は毎回実行されるが、呼び出し先に投げた仕事は独立してキャッシュされる。どのエントリーポイントをキャッシュするかは個別に決められる——wrangler の exports で各エントリーポイントごとにオン・オフを設定する。認証・正規化・振り分けを行うゲートウェイやルーティング系のエントリーポイントは、キャッシュを無効にして毎回実行されるようにすべきだ。そうすればその出力が誤ってキャッシュとして配信されることも永遠にない。
キャッシュの設定方法は、すべて普通の Worker コードとして表現される——どのエントリーポイントを呼ぶか、何を転送するか、どんな ctx.props を渡すか、どんな Cache-Control を設定するか。以下の Worker は、他のプラットフォームでは一緒にやりにくい3つのことを一気にやってのける——すべてのリクエストで認証を行い、高コストなバックエンドをマルチテナント対応の安全なキャッシュキーの裏に隠し、データが変わればそのキャッシュをクリアする。キャッシュはエントリーポイントごとに設定され、ゲートウェイは毎回実行されなければならない(認証のためでもあり、キャッシュされるとゲートウェイがその認証をスキップしてしまうからでもある)。そのためデフォルトのエントリーポイントはキャッシュを無効にし、内部エントリーポイントだけを有効にする。
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-05-01",
"cache": { "enabled": true },
"exports": {
// ゲートウェイは毎リクエスト実行される必要があるのでキャッシュしない
"default": { "type": "worker", "cache": { "enabled": false } },
// 高コストな内部エントリーポイントをキャッシュ
"CachedBackend": { "type": "worker", "cache": { "enabled": true } }
}
}
この1つの Worker の完全なコードを見る(ゲートウェイ + キャッシュバックエンド + タグによる失効)
import { WorkerEntrypoint } from "cloudflare:workers";
interface Env { API_TOKEN: string; }
interface Props { userId: string; }
// 内部エントリーポイント:コストの高い処理。キャッシュがその手前でブロックし、
// ヒット時はこのコードはまったく動かない。
export class CachedBackend extends WorkerEntrypoint<Env, Props> {
async fetch(request: Request): Promise<Response> {
// ctx.props.userId はキャッシュキーの一部になるので、ユーザーごとに
// 個別にキャッシュされる。
const { userId } = this.ctx.props;
const data = await loadExpensiveData(userId);
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
"Cache-Tag": `user:${userId}`,
},
});
}
// 特定ユーザーのキャッシュをクリアする。purge の作用範囲はそれを呼び出した
// エントリーポイント自身なので、このキャッシュを所有する CachedBackend
// 内で実行しなければならない。
async invalidate(userId: string): Promise<void> {
await this.ctx.cache.purge({ tags: [`user:${userId}`] });
}
}
// 外側のエントリーポイント:毎リクエスト実行され、認証とルーティングを担う。
// wrangler でキャッシュを無効にしているので常に実行され、認証がキャッシュ
// ヒットによってスキップされることはない。
export default {
async fetch(request, env, ctx): Promise<Response> {
const userId = await authenticate(request, env);
if (!userId) return new Response("Unauthorized", { status: 401 });
// 書き込み操作のときは、キャッシュを所有するエントリーポイントから
// このユーザーのキャッシュをクリアする。
if (request.method === "POST") {
await handleWrite(request, userId);
await ctx.exports.CachedBackend.invalidate(userId);
return new Response("OK");
}
// 読み取り操作:まず Authorization を剥がす(そうしないと Cloudflare が
// 自動的にキャッシュを迂回し、何もキャッシュできなくなる)。そのうえで
// 認証済みのユーザー ID を ctx.props に入れてキャッシュバックエンドへ
// 転送する。
const forwarded = new Request(request);
forwarded.headers.delete("Authorization");
return ctx.exports.CachedBackend.fetch(forwarded, {
props: { userId },
});
},
} satisfies ExportedHandler<Env>;
全体としては1つの Worker、1つのソースファイル、1回のデプロイだ。しかし内部には2つの実行段階があり、ほんの数行の exports がゲートウェイのキャッシュを無効に、バックエンドのキャッシュを有効にする。キャッシュはその間に挟まり、ユーザーごとにキーが分かれ、書き込み経路によってタグ単位でクリアされ、バックグラウンド更新時には古い内容を渡す。このキャッシュ段階は外付けされたものではなく、プログラムの1層としてコードに書かれている。
同じ形は他の場所にも応用できる。Durable Object を1つのエントリーポイントの裏に包み、読み取りがヒットすればそれに触れず、書き込みは直接 Durable Object に入りタグでキャッシュをクリアする。ゲートウェイでトラッキングパラメーター(?utm_source=…)を剥がしてから転送し、キャッシュにはきれいな URL だけを見せ、パラメーター付きのさまざまなリンクを同じキャッシュエントリーに収束させる。これらのエントリーポイントは何層にも積み重ねられ、その2層の間にあるキャッシュ段階は、あなたが設定するものではなく、どこに置くかをあなたが決めるだけのものになる。
ログイン済みユーザーの API でも、安全にキャッシュを共有できる
ユーザーごとに異なる内容を返す API をキャッシュする場合——たとえばログイン済みの各ユーザーにそれぞれのデータを返す API——あるユーザーが決して別のユーザーのキャッシュを見てしまわないよう保証しなければならない。旧来のやり方は「認証済みリクエストは一律キャッシュしない」であり、Cloudflare も Authorization ヘッダー付きのリクエストはデフォルトでこれを迂回する。しかし「何もキャッシュしない」ことは、パフォーマンス上のメリットを丸ごと捨てることに等しい。
Workers Cache の解決策は、呼び出し側の ctx.props もキャッシュキーに組み込むことだ。各キャッシュ対象リクエストに「これは誰のものか」という見えないタグを貼り付け、同じ URL でも異なるユーザーはそれぞれ独立したキャッシュコピーを保存する——A のデータが誤って B にキャッシュとして返されることは決してない。
export default class Backend extends WorkerEntrypoint<Env, Props> {
async fetch(request: Request): Promise<Response> {
// ctx.props.userId はキャッシュキーの一部だ。ユーザーAとBが同じURLに
// リクエストしても、それぞれ独立したキャッシュエントリーを得る。
const { userId } = this.ctx.props;
const data = await loadUserData(userId);
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=300",
},
});
}
}
典型的なやり方はこうだ。ゲートウェイ Worker でリクエストを認証し、Authorization ヘッダーを剥がし、認証済みのユーザー ID を ctx.props に書き込んでからキャッシュバックエンド Worker を呼び出す。ゲートウェイは毎リクエスト実行される(認証のために実行されなければならない)が、高コストなバックエンドはこのユーザーにまだキャッシュエントリーがないときだけ実行される。もともと「キャッシュできない」認証付き API が、「ユーザーごとにキャッシュされ、完全なセキュリティ分離もある」ものに変わる——分離という仕事はキャッシュキーが代わりにやってくれる。Cloudflare の主張によれば、他の CDN では「正しさ」と「ヒット率」のどちらかを選ばせられる羽目になるが、これを組み込み機能にしている例は他に見たことがないという。
ついでに「ユーザーに近い + データに近い」の二段構成が組める
Web パフォーマンスには長年の矛盾がある——コードをユーザーに近い場所で動かしたい(ユーザーからサーバーへの往復がクリティカルパスにある)一方で、データにも近い場所で動かしたい(クエリのたびに往復も発生する)。どちらかを選べば、もう一方は遅くなる。Cloudflare のネットワーク自体がすでに約50ms以内で全世界のネット人口の約95%をカバーしており、Smart Placement と組み合わせれば、コードはデータにぴったり寄り添って動ける。欠けていたパズルのピースこそが、このキャッシュ層だ。
特別な設計をしなくても、これは自然に得られる——アプリケーションを2つの Worker として書き、service binding で片方をもう片方に指す。Worker B の wrangler でキャッシュを有効にすれば、それで済む。
コンテンツネゴシエーション、モニタリングダッシュボード、課金の計算方法
実際のアプリケーションでは、すべてのクライアントにまったく同じバイト列を返すことは滅多にない。同じ商品ページでも、ブラウザには HTML、API クライアントには JSON が必要だ。同じ画像でも、対応クライアントには WebP、非対応クライアントには JPEG が必要。同じホームページでも、ユーザーによって中国語、フランス語、日本語で返す必要があるかもしれない。
1つの URL に複数バージョンを保存する:Vary ヘッダー
キャッシュがなければこれは簡単だ——Worker がリクエストヘッダーを読んで対応するものを返せばいい。難しいのはキャッシュを載せたときだ。多くのキャッシュはひどい2択しか用意していない——複数バージョンがある URL はいっそキャッシュしないか、1バージョンだけを全員に配信するかだ。Workers Cache は標準の Vary ヘッダーをサポートする。Vary: Accept を返せば、Cloudflare は Accept の値ごとに個別のバリアントとして保存し、入ってきたリクエストに一致するものだけを返す。1つの URL に2つのキャッシュバリアント、Worker はそれぞれに1回ずつ書き込むだけで、その後は誰に対しても実行回数はゼロになる。Vary に指定できるヘッダーにホワイトリストはなく、何を列挙してもよく、Cloudflare はその値をそのまま使ってバリアントを分ける。
キャッシュと Worker が同じダッシュボードに載る
Workers の Observability ダッシュボードは今、呼び出しごとにキャッシュ情報を表示するようになった。Worker ごとにヒット率の時系列推移や、ヒット(HIT)・未ヒット(MISS)・更新(UPDATING)・迂回(BYPASS)それぞれの内訳を確認できる。ヒット率が低いときは、ここが原因調査の出発点になる——BYPASS が多ければ何かが cookie をセットしているかもしれない。MISS が多ければキャッシュキーが思っていたより細かく分かれているかもしれない。UPDATING が多ければ max-age がトラフィックの間隔より短いのかもしれない。これらは Worker のログ、例外、CPU 時間、リクエスト数と同じダッシュボードに並んでおり、行き来する必要はない。
課金:ヒットは CPU にカウントされないが、2つの項目は標準リクエスト費用の対象になる
キャッシュヒットは Worker を実行せず、CPU 時間も課金されないが、それでも1回のリクエストとしてカウントされ、通常の呼び出しと同じ標準の Workers リクエスト料金で課金される。未ヒットと迂回は通常どおり計算される——リクエスト費用に CPU 時間が加わる。
| ケース | リクエスト費用 | CPU時間費用 |
|---|---|---|
| キャッシュヒット HIT(Worker は動かない) | 標準料金 | 課金なし |
| キャッシュ未ヒット MISS(Worker が動く) | 標準料金 | 課金あり |
| キャッシュ迂回 BYPASS(Worker が動く) | 標準料金 | 課金あり |
| 静的アセットリクエスト | 標準料金 | 課金なし |
| Worker 間呼び出し | 標準料金 | 実行時のみ課金 |
Workers Cache 専用の課金項目はなく、GB単位のキャッシュストレージ費用もない。階層キャッシュ、キャッシュパージ、stale-while-revalidate、上記の分析はすべて込みだ。もともと Worker を実行するはずだったリクエストがヒットとして配信されるとき、標準リクエスト費用は依然として払うが、今回は CPU 時間を払わないので、Worker で現場レンダリングするより安くなる。1点注意が必要なのは、キャッシュを有効にすると、もともと無料だった静的アセットリクエストと、service binding や ctx.exports を経由する Worker 間呼び出しが標準リクエスト料金の対象になることだ。それぞれが今や、その手前のキャッシュをまず確認しなければならなくなるからだ。
今日から使える、しかも公式はまだ手を入れ続けている
Workers Cache は本日よりすべてのプランの各 Worker で利用可能になり、Wrangler で有効化する。始め方はたった3ステップ——wrangler.jsonc に "cache": { "enabled": true } を加え、再デプロイし、レスポンスに Cache-Control ヘッダーを設定し始めるだけだ。
公式はまだ手をつけていない項目もいくつか挙げている。
- Smart Placement とキャッシュのさらなる連携。現状、上層キャッシュと Smart Placement のターゲットは別々に選ばれており、完全な未ヒット時にはリクエストが Cloudflare の拠点間を2往復することがある——1往復は上層キャッシュの確認、もう1往復はデータに近い Worker の実行だ。両者は現在この2つの選択を調整中で、未ヒット時に長距離の往復を1回で済ませられるようにしている。
- サイズ上限の緩和。リリース時点では、すべてのレスポンスが無料プランの512MBキャッシュ可能上限を account を問わず一律で使っている。これは暫定的なもので、ロールアウトが進めばプランごとに区別される予定だ。
- より多くのフレームワーク対応。Astro はすでに Workers Cache のアダプターを組み込んでおり、サーバーサイドレンダリングされるページは自動で「一度レンダリング、キャッシュ、バックグラウンド更新」のフローに乗る。TanStack Start、Next.js(Vinext 経由)なども追随中だ。
- ctx.cache.invalidate() の追加。purge は一致するレスポンスをキャッシュから直接削除するが、invalidate はそれらを「期限切れ」状態にする。こうすれば次のリクエストでも stale-while-revalidate によってまず素早く古い内容を受け取り、Worker が裏で更新できる。
Worker はかつてキャッシュの手前で動いていたが、今はキャッシュの後ろでも動ける。必要な方を使えばいいし、service binding を使えば両方同時に使うこともできる。 、Cloudflare Blog「Your Worker can now have its own cache in front of it」
キャッシュがコードの手前に移動:ウェブページは毎アクセス再計算 → 一度計算したら保存、ヒットすればコードは動かず課金もされない
Cloudflare が Workers Cache を発表。設定を一行加えるだけでエッジサーバーにキャッシュ層を追加できる。このページでは図解でその節時・節約効果と突破口を解説する。
↓ 一枚で読み切る · 動く図が1枚あります
Cloudflare Workers は世界中の拠点に分散し、サイトの代わりにコードを実行する小さなプログラムだ。訪問者に近いので速い。ここ数年、Astro、Next.js といったサイト構築フレームワークはアプリケーション全体を1つの Worker にパッケージしてしまうようになった。もはやサーバー手前のフィルターではなく、自らがそのサーバーになる。
✘ しかし計算済みのウェブページを保存できない:誰かがアクセスするたびに、コードは最初から同じページを生成し直す
Worker 自身が起点であり、その背後にキャッシュできるものがない。ウェブページが1秒前とまったく同じでも、やはり再計算され、CPU 費用がかかる
Cloudflare はキャッシュを Worker の手前に移した。有効化は設定を一行加えるだけ、制御は汎用の HTTP キャッシュヘッダーだけを使う。ヒット時は Worker が一度も動かず CPU 課金も発生しない。未ヒットのときだけ一度実行され、実行後そのままキャッシュに保存される。
→ コードを実行してレンダリング
→ 返却
(内容が変わらなくても再計算)
Cache-Control: max-age=300
ヒットすれば直接キャッシュを配信、Worker は一度も動かない。
さらに大きなメリットは柔軟性だ。このキャッシュは外付けの製品ではなく、コードに書かれた1つの層であり、サイトの任意の箇所に差し込める。ログインが必要な個人向け API さえも安全にキャッシュできる。システムは各人のキャッシュに「これは誰か」というタグを貼るので、あなたはあなたの分を保存し、彼は彼の分を保存する——決してあなたの注文が他人に配信されることはない。
ではキャッシュは「在庫あり」と「在庫なし」の間をどう行き来するのか?次の1枚の図で理解できる。
キャッシュは以前 Worker の背後にいて役に立たなかったが、今回は Worker の手前にゲートとして配置された。小互のネットショップの商品ページを例にとると、5分間に100人の訪問者が同じページを見た場合、
公式は「何倍速い」というベンチマークは出していない。実際に節約されているのは重複計算だ。小互のあの商品ページに落とし込んで体感を見ると、
同じページに殺到。
このページを最初から再計算していた。
CPU 費用がかかる
なんで100回も計算するの?!
Worker の手前に移す。
最前面に立ち、
その背後にキャッシュできるものがない。
Worker の手前でブロック。
一体どう流れるの?!
Worker が1回実行され、
ページをキャッシュに保存。
現場計算。
そのまま配信、即応答、
Worker は一度も動かない。
して保存
ログインが必要な個人向け API さえ、それぞれ独立に保存され、混ざらない。
