
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Fri, 10 Jul 2026 16:24:53 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Your Worker can now have its own cache in front of it]]></title>
            <link>https://blog.cloudflare.com/workers-cache/</link>
            <pubDate>Mon, 06 Jul 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[ We are launching Workers Cache, a regionally tiered cache that sits directly in front of your Worker entrypoints. Infinitely composable, configured via standard HTTP headers ]]></description>
            <content:encoded><![CDATA[ <p>Today we are launching <b>Workers Cache</b>: a <a href="https://developers.cloudflare.com/cache/how-to/tiered-cache/"><u>tiered cache</u></a> that sits in front of your Worker, configured by a single line of Wrangler config and the same <code>Cache-Control</code> headers you already know.</p><p>When Workers Cache is enabled, every <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.3"><u>cacheable</u></a> request to your Worker hits Cloudflare's cache first. If there's a fresh cached response, Cloudflare returns it directly — your Worker doesn't run, and you don't pay CPU time for it. On a miss, your Worker runs, and if your response is cacheable, Cloudflare stores it for the next request. The next request from anywhere on Earth can be served straight from cache.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Xyk1rkPl9p44y48mdoPk/2178d252281c0165e03e7cb9245a4c3a/BLOG-3262_image1.png" />
          </figure><p>The whole thing is one config block:</p>
            <pre><code>{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": {
    "enabled": true
  }
}</code></pre>
            <p>After that, you control caching the way HTTP has always wanted you to — by setting headers on your responses:</p>
            <pre><code>return new Response(body, {
  headers: {
    "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
    "Cache-Tag": "products,product:123",
  },
});</code></pre>
            <p>And when content changes, your Worker purges its own cache:</p>
            <pre><code>await ctx.cache.purge({ tags: ["product:123"] });</code></pre>
            <p>That's the whole API. There is no zone to configure, no rules engine to set up, no separate cache to provision, and no second product to log into. The Worker's code is the configuration surface, and the cache follows the Worker wherever it runs — on a custom domain, on <code>workers.dev</code>, behind a service binding, in a preview, in a <a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/"><u>Workers for Platforms</u></a> tenant. One Worker, one cache, configured once.</p><p>That's the surface area. There’s a lot underneath: tiered caching across our entire network, full support for <a href="https://developers.cloudflare.com/changelog/post/2026-02-26-async-stale-while-revalidate/"><code><u>stale-while-revalidate</u></code></a> so stale responses never block a user, content negotiation via <a href="https://developers.cloudflare.com/workers/cache/#content-negotiation-with-vary"><code><u>Vary</u></code></a>, multi-tenant-safe cache keys via <a href="https://developers.cloudflare.com/workers/runtime-apis/context/#props"><code><u>ctx.props</u></code></a>, programmatic purges by tag or path prefix, and — the part we think is the biggest unlock — a cache that sits in front of every Worker entrypoint, not just the public one, with per-entrypoint control over which ones cache and which don't. That last piece means you can compose caching directly into the structure of your app: a chain of entrypoints with cache stages slotted in wherever you want them, configured by the code on either side. We'll walk through all of it below.</p><p>Workers Cache is available today to every Worker on any plan, enabled in Wrangler.</p><p>This is the caching API we've always wanted Workers to have. Here's why it took us this long, what becomes possible because of it, and what's coming next.</p>
    <div>
      <h2>Why server-rendered apps need a cache in front</h2>
      <a href="#why-server-rendered-apps-need-a-cache-in-front">
        
      </a>
    </div>
    <p>When we <a href="https://blog.cloudflare.com/introducing-cloudflare-workers/"><u>introduced Workers in 2017</u></a>, the pitch was that you could run code on Cloudflare's network to transform requests on their way to your origin. The Worker sat <i>in front of</i> the cache and the origin:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1MoDb2Es6lrS9KoNqnakGF/1b67e2672e892c6313c186e3b9c708af/BLOG-3262_image5.png" />
          </figure><p>This was the right model for the use cases we were targeting. If you wanted to add a header to every request, rewrite a URL, do an A/B split, or filter traffic before it reached your origin, putting the Worker in front of the cache and the origin gave you full control over what got cached and what didn't. Customers built incredible things with it.</p><p>But the world changed. Workers stopped being a thing you bolted onto an origin and started being <i>the</i> origin. Frameworks like <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/astro/"><u>Astro</u></a>, <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack-start/"><u>TanStack Start</u></a>, <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/"><u>Next.js</u></a>, <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/remix/"><u>Remix</u></a>, and <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/svelte/"><u>SvelteKit</u></a> all ship a Cloudflare adapter that builds your app as a Worker. There's no origin behind them. The Worker <i>is</i> the server.</p><p>When the Worker is the origin, the original architecture has nothing to cache. Every request runs your code, even when the response would be byte-for-byte identical to the one you returned a second ago. The <a href="https://x.com/KentonVarda/status/1783996343813652801"><u>Workers runtime is fast enough that this works</u></a> — it routinely handles tens of millions of requests per second without breaking a sweat — but "fast enough to render every request" still costs you latency on every page load and CPU time on every invocation. And on a server-rendered app, every page load is, by definition, a render.</p><p>Workers Cache flips the architecture. Cloudflare's cache now sits in front of the Worker:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/19tIUZXwZR2RQKIsT39JBR/1587def81b84c1d0b5f9bee005b3c88f/BLOG-3262_image9.png" />
          </figure><p>On a cache hit, your Worker doesn't run at all. Cloudflare returns the cached response and your CPU billing stays at zero. On a miss, your Worker runs once, populates the cache, and the next request — from anywhere — gets served from cache without invoking your code.</p><p>This is what was missing for server-side rendering on Workers. You used to have to choose between two unsatisfying options:</p><ul><li><p><b>Prerender everything at build time</b> ("static site generation"). Fast page loads, but every change requires a full rebuild and redeploy. For a docs site with a few thousand pages, that's 5–10 minutes. For a large e-commerce site, it's worse — and the build runs every single time you touch anything.</p></li><li><p><b>Render every page on every request.</b> Up-to-date content, but every page load pays the rendering cost and every visitor pays the latency.</p></li></ul><p>Workers Cache gives you a third option: server-render on demand, cache the rendered response, refresh it on a time-to-live (<a href="https://developers.cloudflare.com/cache/how-to/edge-browser-cache-ttl/"><u>TTL</u></a>) you choose. The first request to a new page still renders. Every subsequent request, until the cache expires, is served as if the page were static. When the cache expires, the next request triggers a re-render — and with <code>stale-while-revalidate</code>, even that one doesn't wait.</p><p>You get the speed of a static site without the build time, and the freshness of server rendering without the cost. No framework-specific machinery like Incremental Static Regeneration. Just HTTP caching, working the way it was designed to work, in front of code that was designed to be the origin.</p>
    <div>
      <h2><code>stale-while-revalidate</code> is the part that makes it feel instant</h2>
      <a href="#stale-while-revalidate-is-the-part-that-makes-it-feel-instant">
        
      </a>
    </div>
    <p>The <a href="https://datatracker.ietf.org/doc/html/rfc5861#section-3"><code><u>stale-while-revalidate</u></code></a> directive tells Cloudflare that when a cached response expires, it's allowed to serve the stale copy immediately <i>while it refreshes the response in the background</i>. Cloudflare <a href="https://developers.cloudflare.com/changelog/post/2026-02-26-async-stale-while-revalidate/"><u>shipped full support for </u><code><u>stale-while-revalidate</u></code><u> earlier this year</u></a>, and it's the directive that turns "we cache your Worker" into "your Worker's site feels static."</p><p>Without it, the first request after a cache entry expires has to wait for the Worker to render the page from scratch. The user sees that latency. With it, the first request after expiration gets the stale page immediately (with a <code>Cf-Cache-Status: UPDATING</code> header), and the Worker runs in the background to refill the cache. Every user, including the one who triggered the refresh, gets a cache-speed response.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5xWY9m7E5NKrcecXkJXNpi/2a9fc007577d0131c5e9efabc00bd24e/BLOG-3262_image3.png" />
          </figure><p>In practice, this looks like:</p>
            <pre><code> export default {
  async fetch(request) {
    const html = await renderPage(request);
    return new Response(html, {
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        // Treat as fresh for 5 minutes; serve stale for up to an hour
        // while a background refresh runs.
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
      },
    });
  },
};</code></pre>
            <p>The mental model that makes this click:</p><ul><li><p><b>Fresh window</b> (<code>max-age</code>): Cloudflare serves the cached response. Your Worker doesn't run.</p></li><li><p><b>Stale window</b> (<code>stale-while-revalidate</code>): Cloudflare serves the cached response. Your Worker runs in the background to refresh it. No user waits.</p></li><li><p><b>Outside both windows</b>: Cloudflare runs your Worker to generate a fresh response, and the user waits for that one render.</p></li></ul><p>You pick the windows. For a product catalog that updates every few minutes, <code>max-age=300</code>, <code>stale-while-revalidate=3600</code> means visitors basically never wait, and your Worker still runs often enough to keep content fresh. For a blog archive that almost never changes, <code>max-age=86400</code>, <code>stale-while-revalidate=2592000</code> means your Worker runs once a day per page.</p><p>The first request to a brand-new page is the only one that pays the full render cost. After that, the page behaves like static output for visitors, while your Worker still owns how the page gets generated.</p>
    <div>
      <h2>One URL, many representations: <code>Vary</code> works</h2>
      <a href="#one-url-many-representations-vary-works">
        
      </a>
    </div>
    <p>Real apps rarely return the same bytes to every client. The same product page might be HTML for a browser and JSON for an API client. The same image might be WebP for clients that support it and JPEG for the ones that don't. The same homepage might come back in English, French, or Japanese depending on the user.</p><p>Doing this without a cache is easy — your Worker just reads the request header and returns the right thing. Doing it <i>with</i> a cache is where it usually gets ugly. Most caches give you two bad options: cache nothing on URLs that have multiple representations, or cache one representation and serve it to everyone.</p><p>Workers Cache supports the standard HTTP <code>Vary</code> header, which is the right way to solve this. When your Worker returns a response with <code>Vary: Accept-Encoding</code> (or <code>Accept</code>, or <code>Accept-Language</code>, or any other request header), Cloudflare stores a separate cached variant per distinct combination of those headers — and only returns a variant whose stored values match the incoming request.</p>
            <pre><code>export default {
  async fetch(request) {
    const accept = request.headers.get("Accept") ?? "";
    const wantsWebp = accept.includes("image/webp");

    const body = wantsWebp ? await fetchWebpImage() : await fetchJpegImage();

    return new Response(body, {
      headers: {
        "Content-Type": wantsWebp ? "image/webp" : "image/jpeg",
        "Cache-Control": "public, max-age=3600",
        // Cache a separate variant per distinct Accept header value.
        Vary: "Accept",
      },
    });
  },
};</code></pre>
            <p>One URL, two cached variants. A browser that sends <code>Accept: image/webp,*/*</code> gets the WebP. A browser that sends <code>Accept: image/jpeg</code> gets the JPEG. Both come from cache. Your Worker writes both variants on the first request to each, and then runs zero times for either after that.</p><p>This is the well-trodden HTTP standard for content negotiation, and Workers Cache implements it the way <a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-vary"><u>RFC 9110</u></a> and <a href="https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-cache-keys-with"><u>RFC 9111</u></a> describe. There's no allowlist of what headers you can <code>Vary</code> on. You list whatever you need, and Cloudflare keys variants on the verbatim values. The docs go through the <a href="https://developers.cloudflare.com/workers/cache/configuration/#vary"><u>edge cases</u></a> — how to keep variant fan-out under control by normalizing headers in a gateway Worker, why purges invalidate all variants of a URL together, and the one case (<code>Vary: *</code>) that disables caching entirely.</p>
    <div>
      <h2>This is your Worker's cache, not your zone's</h2>
      <a href="#this-is-your-workers-cache-not-your-zones">
        
      </a>
    </div>
    <p>Before we get to what becomes possible with all this, there's a conceptual shift worth naming.</p><p>Cloudflare has had a cache forever. It's configured at the zone level: Cache Rules, Page Rules, the cached-file-extensions list, Cache Reserve, Tiered Cache topology, custom cache keys. All of it is set per zone, and historically a Worker had to either fit into that zone's configuration or work around it.</p><p>Workers Cache is different. It's <b>your Worker's cache</b> — it belongs to the Worker, not to a zone. This has a bunch of consequences that turn out to matter:</p><ul><li><p><b>There is no zone configuration to manage.</b> Cache Rules, cache level settings, the file-extensions list, Page Rules — none of them apply to Workers Cache. The Worker's <code>Cache-Control</code> headers are the configuration.</p></li><li><p><b>The cache follows the Worker, not the hostname.</b> A Worker that's bound to <code>api.example.com</code>, <code>api.example.net</code>, and invoked over a service binding shares one cache across all three. A request to <code>/users/42</code> hits the same cached entry regardless of which way in it came.</p></li><li><p><b>The cache works on </b><code><b>workers.dev</b></code><b>.</b> It works in <a href="https://developers.cloudflare.com/workers/configuration/previews/"><u>preview URLs</u></a> (each preview gets its own cache, so testing a change doesn't poison production). It works in <a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/"><u>Workers for Platforms</u></a> (each user Worker has its own cache, isolated from the dispatcher and from other tenants). All of these used to be second-class citizens for caching. They aren't anymore.</p></li><li><p><b>Purges are scoped to the Worker’s entrypoint.</b> When you call <code>ctx.cache.purge({ purgeEverything: true })</code>, you're only purging your Worker entrypoint's cache. No risk of nuking your zone's other content. No risk of one Worker's deploy invalidating another's data.</p></li></ul><p>What you configure about caching, you configure in code: which paths get longer TTLs (branch on the path and set a different <code>max-age</code>), which requests bypass the cache (return <code>Cache-Control: private</code>), how the cache key is shaped (control what gets into <code>ctx.props</code>, normalize the URL in a gateway Worker before dispatching). The Worker you already wrote is the configuration surface.</p><p>The full docs go deep on this in <a href="https://developers.cloudflare.com/workers/cache/"><u>Workers Cache: your Worker's cache</u></a>.</p>
    <div>
      <h2>Two tiers, every Worker, no configuration</h2>
      <a href="#two-tiers-every-worker-no-configuration">
        
      </a>
    </div>
    <p>Workers Cache is <b>regionally tiered by default</b>. There are two layers:</p><ul><li><p><b>A lower tier</b> in the Cloudflare data center closest to the user. Every data center that receives traffic for your Worker has its own lower-tier cache.</p></li><li><p><b>An upper tier</b> that aggregates fills across the whole network. There are fewer of these, and every lower tier consults the upper tier on a miss.</p></li></ul><p>A request hits the lower tier first. On a hit, the response is served and that's the end of it. On a miss, the lower tier asks the upper tier. On a hit there, the response is returned and also stored in the lower tier on the way back. Only if both tiers miss does your Worker actually run — and the response from that run gets stored in both tiers.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/Qn6sTGmQebZz5fQZKo5us/37c11406ad6844a5131d78f628acd2f0/BLOG-3262_image2.png" />
          </figure><p>The reason this matters is that the <b>first request anywhere in the world</b> populates the upper tier. Every subsequent request, from any data center, can be served from the upper tier without your Worker running — even if the lower tier at that data center has never seen the request before. Cache hit ratios are dramatically higher than they would be with a single flat cache layer, which is exactly what you want when your Worker is the origin.</p><p>This is the same topology that powers <a href="https://developers.cloudflare.com/cache/how-to/tiered-cache/"><u>Tiered Cache</u></a> for zones today, except you don't configure it. There is no dialog for "turn on tiered cache for my Worker." Every Worker that has caching enabled gets tiering for free.</p><p>If your Worker uses <a href="https://developers.cloudflare.com/workers/configuration/placement/"><u>Smart Placement</u></a>, the cache composes cleanly with it: tiers are consulted first, and only if both miss does Smart Placement route execution close to your origin. We have more to say about how those layers interact, including a few rough edges we're <a href="https://developers.cloudflare.com/workers/cache/#smart-placement-and-the-cache"><u>planning to smooth out</u></a>, in the docs.</p>
    <div>
      <h2>Run your app near the user <i>and</i> near the data</h2>
      <a href="#run-your-app-near-the-user-and-near-the-data">
        
      </a>
    </div>
    <p>There's a recurring tension in web performance that nobody has fully resolved: you want your code to run close to the user (because the round-trip between user and server is on the critical path), and you want your code to run close to the data (because every database query is also a round-trip). Pick one, and the other gets slow.</p><p>We've spent years chasing both. Our network puts us <a href="https://www.cloudflare.com/network/"><u>within ~50ms</u></a> of about 95% of the world's Internet users. <a href="https://blog.cloudflare.com/announcing-workers-smart-placement/"><u>Smart Placement</u></a> and <a href="https://developers.cloudflare.com/changelog/post/2026-01-22-explicit-placement-hints/"><u>Placement Hints</u></a> let you keep your code close to your data without ever having to think about cloud regions. But until now, the two pieces didn't fully compose. You could do "near the user" or "near the data," and if you wanted both halves of your app to be in the right place at the same time, you had to be a Cloudflare expert. <a href="https://sunilpai.dev/posts/spatial-compute/"><u>We knew we could do better.</u></a></p><p>Workers Cache is the piece that closes the gap. Because the cache belongs to the Worker (not the zone), and because <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/"><u>service bindings</u></a> and <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/"><code><u>ctx.exports</u></code></a> calls between Workers go through the cache, you can build an app as a chain of Workers — each one running where it should run — with the cache as the seam between them.</p><p>The architecture looks like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5vOhRNLQF3RWl2u6gTdpWm/60ee01a9b099357564769de3b9354d3b/BLOG-3262_image8.png" />
          </figure><ul><li><p><b>Worker A</b> runs near the user. It handles the cheap, latency-sensitive parts of every request: authentication, rate limiting, routing, header normalization, rendering the <a href="https://developers.cloudflare.com/workers/examples/spa-shell/"><u>outer "shell"</u></a> of an HTML page that doesn't depend on data.</p></li><li><p><b>Worker B</b> runs near the data, courtesy of Smart Placement or an explicit Placement Hint. It does the heavy work: server-rendering pages that fetch data, reading product catalogs, generating search results, aggregating APIs, expensive transforms.</p></li><li><p><b>Workers Cache sits in front of Worker B.</b> When Worker A calls Worker B over a service binding, Cloudflare checks Worker B's cache first. On a hit, Worker A receives the response and Worker B doesn't run at all — no data-center hop, no database query, no rendering work.</p></li></ul><p>The cache hit path becomes: user → Worker A near the user → cache hit for Worker B → response. The data hop is paid only on a miss. Your hot pages run at the speed of code-in-front-of-the-user, and your cold pages still benefit from running near the data when they do execute.</p><p>You don't have to architect anything special to get this. Write your app as two Workers, point one at the other with a service binding, turn caching on in Worker B’s <code>wrangler.jsonc</code> file, and you're done.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4Xh7r02F9CCFAbrMuuRRpn/901c4c1cf8d24f515485e3810fa9aa4d/BLOG-3262_image7.png" />
          </figure>
    <div>
      <h2>Multi-tenant by default, with <code>ctx.props</code></h2>
      <a href="#multi-tenant-by-default-with-ctx-props">
        
      </a>
    </div>
    <p>If you're caching a Worker that returns user-specific data — say, an API that serves different content per logged-in user — you need a way to make sure one user can never see another user's cached response. The standard solution is "don't cache authenticated requests," and Cloudflare's <a href="https://developers.cloudflare.com/cache/concepts/cache-responses/#bypass"><u>automatic bypass</u></a> for Authorization headers does exactly that. But "don't cache anything" gives up the entire performance win.</p><p>Workers Cache solves this by making the caller's <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#ctxprops"><code><u>ctx.props</u></code></a> part of the cache key. When one Worker calls another over a service binding and passes <code>ctx.props</code> with a user ID, tenant ID, or any other identifier, callers with different props get separate cache entries. One user's response can never leak into another user's cache.</p>
            <pre><code>import { WorkerEntrypoint } from "cloudflare:workers";

interface Props { userId: string; }

export default class Backend extends WorkerEntrypoint&lt;Env, Props&gt; {
  async fetch(request: Request): Promise&lt;Response&gt; {
    // ctx.props.userId is part of the cache key. User A and User B
    // requesting the same URL get separate cached entries.
    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",
      },
    });
  }
}</code></pre>
            <p>The typical pattern is to authenticate the request in a gateway Worker, strip the <code>Authorization</code> header, set the authenticated user's ID into <code>ctx.props</code>, and then call the cached backend Worker. The gateway runs on every request (it has to, to authenticate), but the expensive backend only runs when there's no cache entry for that user yet. Auth'd APIs go from "uncacheable" to "cached per user with full safety," and the cache key does the isolation for you. The docs walk through this in detail in <a href="https://developers.cloudflare.com/workers/cache/cache-keys/#multi-tenant-safety-with-ctxprops"><u>Multi-tenant safety with ctx.props</u></a> and the example in <a href="https://developers.cloudflare.com/workers/cache/examples/#per-user-authenticated-responses"><u>Per-user authenticated responses</u></a>.</p><p>Other CDNs make you choose between correctness and hit ratio: key the cache by each user’s token, or send every request back to origin for authorization. Workers Cache lets you share cached API responses at the edge while preserving per-request authorization boundaries. We don’t know of another CDN that offers this as a built-in model for authenticated, multi-tenant APIs. We’re pretty proud of it.</p>
    <div>
      <h2>A cache between every Worker entrypoint</h2>
      <a href="#a-cache-between-every-worker-entrypoint">
        
      </a>
    </div>
    <p>Here is the part of Workers Cache that we think is the biggest unlock, and it's the part that's hardest to see if you're thinking about it as "a CDN cache that happens to work in front of Workers."</p><p><b>Workers Cache sits in front of every Worker entrypoint</b> — the default export, every named <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#named-entrypoints"><code><u>WorkerEntrypoint</u></code></a>, and every call between entrypoints in the same Worker via <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/"><code><u>ctx.exports</u></code></a>. That last clause is the one that changes what you can build.</p><p>When one entrypoint calls another via <code>ctx.exports</code>, the cache evaluates that call the same way it would evaluate a request from a browser. A hit returns the cached response and the callee never runs. A miss runs the callee and stores its response under its own cache key — keyed by the callee's entrypoint, path, query string, and <code>ctx.props</code>. The caller still runs on every request, but anything it hands off to the callee is memoized independently.</p><p>You decide, per entrypoint, which ones cache. In your Wrangler config, the <code>exports</code> map lets you turn caching on or off for each entrypoint by name (<code>"default"</code> is the default export). Opt an entrypoint <b>in</b> to cache the responses it produces; opt one <b>out</b> to keep it running on every request. A gateway or router entrypoint — anything that authenticates, normalizes, or dispatches — should be opted out, so it always runs, and its own output is never served from cache.</p><p>That gives you a primitive you can compose. You can author a Worker as a chain of small entrypoints — auth, normalization, routing, the expensive read, the data layer — and let Workers Cache slot in wherever you want it. Each cached entrypoint is a unit of memoization with its own key, its own TTL, and its own tag namespace for purging. Anything you would want to configure about caching — when it runs, what it keys on, when it invalidates — is expressed as ordinary Worker code: which entrypoint you call, what request you forward, what <code>ctx.props</code> you pass, what <code>Cache-Control</code> you set.</p><p>To make this concrete, here's a single Worker that does three things you couldn't easily do together on any other platform: it authenticates every request, caches the expensive backend behind a multi-tenant-safe cache key, and invalidates that cache when data changes.</p><p>Caching is configured per entrypoint. The gateway must run on every request — both to authenticate and because a cached gateway response would skip that auth check — so we disable caching on the default entrypoint and enable it only on the inner one:</p>
            <pre><code>{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": { "enabled": true },
  "exports": {
    // The gateway runs on every request — don't cache it.
    "default": { "type": "worker", "cache": { "enabled": false } },
    // Cache the expensive inner entrypoint.
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}</code></pre>
            
            <pre><code>import { WorkerEntrypoint } from "cloudflare:workers";

interface Env { API_TOKEN: string; }
interface Props { userId: string; }

// Inner entrypoint: the expensive work. Workers Cache sits in front
// of this — on a hit, this code never runs.
export class CachedBackend extends WorkerEntrypoint&lt;Env, Props&gt; {
  async fetch(request: Request): Promise&lt;Response&gt; {
    // ctx.props.userId is part of the cache key, so this is cached
    // separately for every user.
    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}`,
      },
    });
  }

  // Invalidate a user's cached response. purge() is scoped to the
  // entrypoint that calls it, so it must run inside CachedBackend —
  // the entrypoint that owns the cached response.
  async invalidate(userId: string): Promise&lt;void&gt; {
    await this.ctx.cache.purge({ tags: [`user:${userId}`] });
  }
}

// Outer entrypoint: runs on every request to authenticate and route.
// Caching is disabled for it in Wrangler config (above), so it always
// runs and the auth check is never skipped by a cache hit.
export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {
    const userId = await authenticate(request, env);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // Invalidate this user's cache on writes, from the entrypoint that
    // owns it.
    if (request.method === "POST") {
      await handleWrite(request, userId);
      await ctx.exports.CachedBackend.invalidate(userId);
      return new Response("OK");
    }

    // For reads: strip Authorization (otherwise Cloudflare's automatic
    // bypass fires and nothing caches), then dispatch to the cached
    // backend with the authenticated user's identity in ctx.props.
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");

    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler&lt;Env&gt;;</code></pre>
            <p>The whole thing is one Worker. One source file. One deploy. But there are two execution stages — caching is turned off for the gateway and on for the backend in one small <code>exports</code> block — and a cache sits between them, keyed per user, invalidated by the write path, and serving stale during background refreshes. The cache stage isn't something you bolted on. It's a layer of the program, written in code.</p><p>The patterns this composes into are open-ended. The same shape works for:</p><ul><li><p><b>Caching a Durable Object.</b> Wrap the Durable Object behind an entrypoint, set <code>Cache-Control</code> on the response, and reads stop touching the Durable Object on a hit. Writes go to the DO directly and purge the cache by tag. The DO stays unaware that caching is happening.</p></li><li><p><b>Normalizing </b><code><b>Accept-Encoding</b></code><b> before </b><code><b>Vary</b></code><b>.</b> The outer entrypoint restores the original encoding from <code>request.cf.clientAcceptEncoding</code> (Cloudflare's front line normalizes it for cache efficiency) and forwards to a cached entrypoint that varies on the real value. Hit ratios stay high; clients get the right encoding.</p></li><li><p><b>Stripping tracking parameters before caching.</b> The outer entrypoint canonicalizes the URL — or sets a <a href="https://developers.cloudflare.com/workers/cache/cache-keys/#custom-cache-keys"><u>custom cache key</u></a> with <code>cf.cacheKey</code> on the <code>ctx.exports</code> call — so the cached inner entrypoint sees only the canonical form, and <code>?utm_source=anything</code> collapses to a single cache entry.</p></li></ul><p>Stack them. A single Worker can have an outer entrypoint that authenticates and routes, a normalization entrypoint that strips tracking parameters and restores encoding headers, a cached entrypoint that fronts a Durable Object, and a separate cached entrypoint for an unauthenticated public API — each connected by a cache stage you didn't configure, just decided where to put. The <a href="https://developers.cloudflare.com/workers/cache/examples/"><u>Examples page in the docs</u></a> walks through several of these end-to-end.</p><p>We don't know of another platform where you can do this. CDN caches sit in front of an origin. Function platforms run functions. We don't know of another platform that gives you a cache that sits inside a single deployable unit, between the parts of your application, with each cache stage configured by the code on either side of it. That's what Workers Cache is. And because it composes with everything else the platform already gives you — Smart Placement, Durable Objects, service bindings, <code>ctx.props</code>, <code>ctx.exports</code> — the patterns you can build are open-ended. We've barely scratched the surface in this post.</p>
    <div>
      <h2>First-class support in your framework</h2>
      <a href="#first-class-support-in-your-framework">
        
      </a>
    </div>
    <p>If you're building with Astro, the Cloudflare adapter wires up Workers Cache for you. Just add the <a href="https://docs.astro.build/en/guides/caching/#cloudflare"><u>cacheCloudflare provider</u></a> to your configuration:</p>
            <pre><code>// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
  adapter: cloudflare(),
  output: "server",
  experimental: {
    cache: { provider: cacheCloudflare() },
    routeRules: {
      "/products/*": { maxAge: 300, swr: 3600, tags: ["products"] },
      "/blog/*":     { maxAge: 60,  swr: 86400, tags: ["blog"] },
    },
  },
});</code></pre>
            <p>The adapter enables the cache, sets the right headers on the responses Astro generates, attaches <code>Cache-Tag</code> values for invalidation, and gives you a <code>cache.invalidate()</code> helper for purging tags when content changes. Astro pages that opt into server rendering automatically get the "render once, cache, refresh in the background" flow described above — no per-route configuration required, no framework-specific runtime layer to learn.</p><p>We're working with the maintainers of other frameworks to ship the same integration. If you build a framework adapter for Cloudflare, the <a href="https://developers.cloudflare.com/workers/cache/"><u>Workers Cache APIs</u></a> are exactly what you'd want them to be — header-driven configuration, programmatic purges, no platform-specific concepts to model.</p>
    <div>
      <h2>See your cache on the same dashboard as your Worker</h2>
      <a href="#see-your-cache-on-the-same-dashboard-as-your-worker">
        
      </a>
    </div>
    <p>Caching is only useful if you can see what it's doing. The <a href="https://developers.cloudflare.com/workers/observability/"><u>Workers Observability dashboard</u></a> now surfaces cache hit information per invocation:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3EkUcGZeoQ5chRJFYLaJz/78cd1fbac2d8cb18152f85e97ccb27d0/BLOG-3262_image6.png" />
          </figure><p>You can see, per Worker:</p><ul><li><p><b>Cache hit ratio</b> over time. The number you want trending up after you enable caching.</p></li><li><p><b>Hits, misses, updates, bypasses</b> broken down. If your hit ratio is low, this is where you find out why — too many <code>BYPASS</code> responses (because something is setting a cookie?), too many <code>MISS</code> responses (because the cache key is partitioning more than you thought?), too many <code>UPDATING</code> responses (because <code>max-age</code> is shorter than your traffic interval?).</p></li></ul><p>Because all of this lives on the same dashboard as your Worker's other observability — logs, exceptions, CPU time, request counts — you don't have to context-switch between looking at your zone and your Worker to understand what's happening.</p>
    <div>
      <h2>Billing</h2>
      <a href="#billing">
        
      </a>
    </div>
    <p>Cache hits don't run your Worker, and they don't bill CPU time. They do count as a request at the standard <a href="https://developers.cloudflare.com/workers/platform/pricing/"><u>Workers request rate</u></a>, the same as any other invocation. Cache misses and bypasses bill normally — request + CPU time, exactly as they would without caching.</p><table><tr><td><p><b>Outcome</b></p></td><td><p><b>Request charge</b></p></td><td><p><b>CPU time charge</b></p></td></tr><tr><td><p>Cache <code>HIT</code> (Worker does not run)</p></td><td><p>Standard rate</p></td><td><p>Not billed</p></td></tr><tr><td><p>Cache <code>MISS</code> (Worker runs)</p></td><td><p>Standard rate</p></td><td><p>Billed</p></td></tr><tr><td><p>Cache <code>BYPASS</code> (Worker runs)</p></td><td><p>Standard rate</p></td><td><p>Billed</p></td></tr><tr><td><p>Static asset request</p></td><td><p>Standard rate</p></td><td><p>Not billed</p></td></tr><tr><td><p>Worker-to-worker invocation</p></td><td><p>Standard rate</p></td><td><p>Billed if the Worker runs</p></td></tr></table><p>There's no separate Workers Cache SKU and no per-GB cache storage fee. Tiered caching, purges, <code>stale-while-revalidate</code>, and the analytics described above are all included.  If a request would have run your Worker and Workers Cache serves it as a hit instead, you still pay the standard request rate, but you pay no CPU time for that request. Because of this, that cache hit costs less than rendering the same response in your Worker.</p><p>One thing to watch: when caching is enabled, requests that are normally free — <a href="https://developers.cloudflare.com/workers/static-assets/billing-and-limitations/"><u>static asset requests</u></a> and <a href="https://developers.cloudflare.com/workers/platform/pricing/#service-bindings"><u>worker-to-worker invocations</u></a> through service bindings or <code>ctx.exports</code> — are billed at the standard request rate, because each one now consults the cache in front of your Worker.</p>
    <div>
      <h2>What's next</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Things we know we want to do next:</p><ul><li><p><b>Smarter co-location with Smart Placement.</b> Today, Cloudflare chooses the upper-tier cache and Smart Placement target separately. On a full miss, the request may travel between Cloudflare locations twice: once to check the upper tier, and again to run your Worker near its data. We're working to coordinate those choices, so a miss only makes that long-distance trip once.</p></li><li><p><b>Larger response size limits.</b> At launch, all responses follow the <a href="https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#cacheable-size-limits"><u>Free plan’s cacheable size limit</u></a> (512 MB), regardless of your account. That’s temporary — the standard per-plan cache limits will apply once we finish a few rollout steps.</p></li><li><p><b>More framework integrations.</b> Astro has <a href="https://docs.astro.build/en/guides/caching/#cloudflare"><u>built-in integration with Workers Cache</u></a>. We’re working with maintainers to add similar integrations to other frameworks, including <a href="https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack-start/"><u>TanStack Start</u></a> and Next.js via <a href="https://vinext.dev/"><u>Vinext</u></a>.</p></li><li><p><b>An API to mark cached responses stale. </b><code>ctx.cache.purge()</code> removes matching responses from cache. We’re looking at a <code>ctx.cache.invalidate()</code> API that makes matching responses behave as expired, so the next request can still get a fast stale response with stale-while-revalidate while your Worker refreshes the cache in the background.</p></li></ul>
    <div>
      <h2>Try it</h2>
      <a href="#try-it">
        
      </a>
    </div>
    <p>Workers Cache is available today to every Worker on any plan.</p><p>To get started, add <code>"cache": { "enabled": true }</code> to your <code>wrangler.jsonc</code>, redeploy, and start setting <code>Cache-Control</code> headers. The <a href="https://developers.cloudflare.com/workers/cache/"><u>Workers Cache documentation</u></a> walks through the full feature surface — including the <a href="https://developers.cloudflare.com/workers/cache/#quickstart"><u>quickstart</u></a>, <a href="https://developers.cloudflare.com/workers/cache/cache-keys/"><u>cache keys</u></a>, <a href="https://developers.cloudflare.com/workers/cache/purge/"><u>purging</u></a>, <a href="https://developers.cloudflare.com/workers/cache/examples/"><u>composition patterns and examples</u></a>, and <a href="https://developers.cloudflare.com/workers/cache/debugging/"><u>debugging</u></a>.</p><p>Workers used to run in front of the cache. Now they can also run behind it. Use whichever side you need — or, with service bindings, both at once.</p><p>We can't wait to see what you build.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Tiered Cache]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Serverless]]></category>
            <guid isPermaLink="false">1WYEYtVwQSo4H3jKHTcKmO</guid>
            <dc:creator>Dan Lapid</dc:creator>
            <dc:creator> Connor Harwood</dc:creator>
        </item>
        <item>
            <title><![CDATA[“You get Instant Purge, and you get Instant Purge!” — all purge methods now available to all customers]]></title>
            <link>https://blog.cloudflare.com/instant-purge-for-all/</link>
            <pubDate>Tue, 01 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Following up on having the fastest purge in the industry, we have now increased Instant Purge quotas across all Cloudflare plans.  ]]></description>
            <content:encoded><![CDATA[ <p>There's a tradition at Cloudflare of launching real products on April 1, instead of the usual joke product announcements circulating online today. In previous years, we've introduced impactful products like <a href="https://blog.cloudflare.com/announcing-1111/"><u>1.1.1.1</u></a> and <a href="https://blog.cloudflare.com/introducing-1-1-1-1-for-families/"><u>1.1.1.1 for Families</u></a>. Today, we're excited to continue this tradition by <b>making every purge method available to all customers, regardless of plan type.</b></p><p>During Birthday Week 2024, we <a href="https://blog.cloudflare.com/instant-purge/"><u>announced our intention</u></a> to bring the full suite of purge methods — including purge by URL, purge by hostname, purge by tag, purge by prefix, and purge everything — to all Cloudflare plans. Historically, methods other than "purge by URL" and "purge everything" were exclusive to Enterprise customers. However, we've been openly rebuilding our purge pipeline over the past few years (hopefully you’ve read <a href="https://blog.cloudflare.com/part1-coreless-purge/"><u>some of our</u></a> <a href="https://blog.cloudflare.com/rethinking-cache-purge-architecture/"><u>blog</u></a> <a href="https://blog.cloudflare.com/instant-purge/"><u>series</u></a>), and we're thrilled to share the results more broadly. We've spent recent months ensuring the new Instant Purge pipeline performs consistently under 150 ms, even during increased load scenarios, making it ready for every customer.  </p><p>But that's not all — we're also significantly raising the default purge rate limits for Enterprise customers, allowing even greater purge throughput thanks to the efficiency of our newly developed <a href="https://blog.cloudflare.com/instant-purge/"><u>Instant Purge</u></a> system.</p>
    <div>
      <h2>Building a better purge: a two-year journey</h2>
      <a href="#building-a-better-purge-a-two-year-journey">
        
      </a>
    </div>
    <p>Stepping back, today's announcement represents roughly two years of focused engineering. Near the end of 2022, our team went heads down rebuilding Cloudflare’s purge pipeline with a clear yet challenging goal: dramatically increase our throughput while maintaining near-instant invalidation across our global network.</p><p>Cloudflare operates <a href="https://www.cloudflare.com/network"><u>data centers in over 335 cities worldwide</u></a>. Popular cached assets can reside across all of our data centers, meaning each purge request must quickly propagate to every location caching that content. Upon receiving a purge command, each data center must efficiently locate and invalidate cached content, preventing stale responses from being served. The amount of content that must be invalidated can vary drastically, from a single file, to all cached assets associated with a particular hostname. After the content has been purged, any subsequent requests will trigger retrieval of a fresh copy from the origin server, which will be stored in Cloudflare’s cache during the response. </p><p>Ensuring consistent, rapid propagation of purge requests across a vast network introduces substantial technical challenges, especially when accounting for occasional data center outages, maintenance, or network interruptions. Maintaining consistency under these conditions requires robust distributed systems engineering.</p>
    <div>
      <h2>How did we scale purge?</h2>
      <a href="#how-did-we-scale-purge">
        
      </a>
    </div>
    <p>We've <a href="https://blog.cloudflare.com/instant-purge/"><u>previously discussed</u></a> how our new Instant Purge system was architected to achieve sub-150 ms purge times. It’s worth noting that the performance improvements were only part of what our new architecture achieved, as it also helped us solve significant scaling challenges around storage and throughput that allowed us to bring Instant Purge to all users. </p><p>Initially, our purge system scaled well, but with rapid customer growth, the storage consumption from millions of daily purge keys that needed to be stored reduced available caching space. Early attempts to manage this storage and throughput demand involved <a href="https://www.boltic.io/blog/kafka-queue"><u>queues</u></a> and batching for smoothing traffic spikes, but this introduced latency and underscored the tight coupling between increased usage and rising storage costs.</p><p>We needed to revisit our thinking on how to better store purge keys and when to remove purged content so we could reclaim space. Historically, when a customer would purge by tag, prefix or hostname, Cloudflare would mark the content as expired and allow it to be evicted later. This is known as lazy-purge because nothing is actively removed from disk. Lazy-purge is fast, but not necessarily efficient, because it consumes storage for expired but not-yet-evicted content. After examining global or data center-level indexing for purge keys, we decided that wasn't viable due to increases in system complexity and the latency those indices could bring due to our network size. So instead, we opted for per-machine indexing, integrating indices directly alongside our cache proxies. This minimized network complexity, simplified reliability, and provided predictable scaling.</p><p>After careful analysis and benchmarking, we selected <a href="https://rocksdb.org/"><u>RocksDB</u></a>, an embedded key-value store that we could optimize for our needs, which formed the basis of <a href="https://blog.cloudflare.com/instant-purge/#putting-it-all-together"><u>CacheDB</u></a>, our Rust-based service running alongside each cache proxy. CacheDB manages indexing and immediate purge execution (active purge), significantly reducing storage needs and freeing space for caching.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4FZ0bQSx5MUhx3x3hwlRuk/91a27af7db5e629cd6d5fbe692397eaf/image2.png" />
          </figure><p>Local queues within CacheDB buffer purge operations to ensure consistent throughput without latency spikes, while the cache proxies consult CacheDB to guarantee rapid, active purges. Our updated distribution pipeline broadcasts purges directly to CacheDB instances across machines, dramatically improving throughput and purge speed.</p><p>Using CacheDB, we've reduced storage requirements 10x by eliminating lazy purge storage accumulation, instantly freeing valuable disk space. The freed storage enhances cache retention, boosting cache HIT ratios and minimizing origin egress. These savings in storage and increased throughput allowed us to scale to the point where we can offer Instant Purge to more customers.</p><p>For more information on how we designed the new Instant Purge system, please see the previous <a href="https://blog.cloudflare.com/instant-purge/"><u>installment</u></a> of our Purge series blog posts. </p>
    <div>
      <h2>Striking the right balance: what to purge and when</h2>
      <a href="#striking-the-right-balance-what-to-purge-and-when">
        
      </a>
    </div>
    <p>Moving on to practical considerations of using these new purge methods, it’s important to use the right method for what you want to invalidate. Purging too aggressively can overwhelm origin servers with unnecessary requests, driving up egress costs and potentially causing downtime. Conversely, insufficient purging leaves visitors with outdated content. Balancing precision and speed is vital.</p><p>Cloudflare supports multiple targeted purge methods to help customers achieve this balance.</p><ul><li><p><a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-everything/"><b><u>Purge Everything</u></b></a>: Clears all cached content associated with a website.</p></li><li><p><a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge_by_prefix/"><b><u>Purge by Prefix</u></b></a>: Targets URLs sharing a common prefix.</p></li><li><p><a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-hostname/"><b><u>Purge by Hostname</u></b></a>: Invalidates content by specific hostnames.</p></li><li><p><a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-single-file/"><b><u>Purge by URL (single-file purge</u></b></a><b>)</b>: Precisely targets individual URLs.</p></li><li><p><a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/"><b><u>Purge by Tag</u></b></a>: Uses <a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/#add-cache-tag-http-response-headers"><u>Cache-Tag</u></a> headers to invalidate grouped assets, offering flexibility for complex cache management scenarios.</p></li></ul><p>Starting today, all of these methods are available to every Cloudflare customer.    </p>
    <div>
      <h2>How to purge </h2>
      <a href="#how-to-purge">
        
      </a>
    </div>
    <p>Users can select their purge method directly in the Cloudflare dashboard, located under the Cache tab in the <a href="https://dash.cloudflare.com/?to=/:account/:zone/caching/configuration"><u>configurations section</u></a>, or via the <a href="https://developers.cloudflare.com/api/resources/cache/"><u>Cloudflare API</u></a>. Each purge request should clearly specify the targeted URLs, hostnames, prefixes, or cache tags relevant to the selected purge type (known as purge keys). For instance, a prefix purge request might specify a directory such as example.com/foo/bar. To maximize efficiency and throughput, batching multiple purge keys in a single request is recommended over sending individual purge requests each with a single key.</p>
    <div>
      <h2>How much can you purge?</h2>
      <a href="#how-much-can-you-purge">
        
      </a>
    </div>
    <p>The new rate limits for Cloudflare's purge by tag, prefix, hostname, and purge everything are different for each plan type. We use a <a href="https://en.wikipedia.org/wiki/Token_bucket"><u>token bucket</u></a> rate limit system, so each account has a token bucket with a maximum size based on plan type. When we receive a purge request we first add tokens to the account’s bucket based on the time passed since the account’s last purge request divided by the refill rate for its plan type (which can be a fraction of a token). Then we check if there’s at least one whole token in the bucket, and if so we remove it and process the purge request. If not, the purge request will be rate limited. An easy way to think about this rate limit is that the refill rate represents the consistent rate of requests a user can send in a given period while the bucket size represents the maximum burst of requests available.</p><p>For example, a free user starts with a bucket size of 25 requests and a refill rate of 5 requests per minute (one request per 12 seconds). If the user were to send 26 requests all at once, the first 25 would be processed, but the last request would be rate limited. They would need to wait 12 seconds and retry their last request for it to succeed. </p><p>The current limits are applied per <b>account</b>: </p><table><tr><td><p><b>Plan</b></p></td><td><p><b>Bucket size</b></p></td><td><p><b>Request refill rate</b></p></td><td><p><b>Max keys per request</b></p></td><td><p><b>Total keys</b></p></td></tr><tr><td><p><b>Free</b></p></td><td><p>25 requests</p></td><td><p>5 per minute</p></td><td><p>100</p></td><td><p>500 per minute</p></td></tr><tr><td><p><b>Pro</b></p></td><td><p>25 requests</p></td><td><p>5 per second</p></td><td><p>100</p></td><td><p>500 per second</p></td></tr><tr><td><p><b>Biz</b></p></td><td><p>50 requests</p></td><td><p>10 per second</p></td><td><p>100</p></td><td><p>1,000 per second</p></td></tr><tr><td><p><b>Enterprise</b></p></td><td><p>500 requests</p></td><td><p>50 per second</p></td><td><p>100</p></td><td><p>5,000 per second</p></td></tr></table><p>More detailed documentation on all purge rate limits can be found in our <a href="https://developers.cloudflare.com/cache/how-to/purge-cache/"><u>documentation</u></a>.</p>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We’ve spent a lot of time optimizing our purge platform. But we’re not done yet. Looking forward, we will continue to enhance the performance of Cloudflare’s single-file purge. The current P50 performance is around 250 ms, and we suspect that we can optimize it further to bring it under 200 ms. We will also build out our ability to allow for greater purge throughput for all of our systems, and will continue to find ways to implement filtering techniques to ensure we can continue to scale effectively and allow customers to purge whatever and whenever they choose. </p><p>We invite you to try out our new purge system today and deliver an instant, seamless experience to your visitors.</p> ]]></content:encoded>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Cache Purge]]></category>
            <guid isPermaLink="false">4LTq8Utw6K58W4ojKxsqw8</guid>
            <dc:creator>Alex Krivit</dc:creator>
            <dc:creator> Connor Harwood</dc:creator>
            <dc:creator>Zaidoon Abd Al Hadi</dc:creator>
        </item>
        <item>
            <title><![CDATA[Instant Purge: invalidating cached content in under 150ms]]></title>
            <link>https://blog.cloudflare.com/instant-purge/</link>
            <pubDate>Tue, 24 Sep 2024 23:00:00 GMT</pubDate>
            <description><![CDATA[ We’ve built the fastest cache purge in the industry by offering a global purge latency for purge by tags, hostnames, and prefixes of less than 150ms on average (P50), representing a 90% improvement.  ]]></description>
            <content:encoded><![CDATA[ <p><sup>(part 3 of the Coreless Purge </sup><a href="https://blog.cloudflare.com/rethinking-cache-purge-architecture/"><sup>series</sup></a><sup>)</sup></p><p>Over the past 14 years, Cloudflare has evolved far beyond a Content Delivery Network (CDN), expanding its offerings to include a comprehensive <a href="https://developers.cloudflare.com/cloudflare-one/"><u>Zero Trust</u></a> security portfolio, network security &amp; performance <a href="https://www.cloudflare.com/network-services/products/"><u>services</u></a>, application security &amp; performance <a href="https://www.cloudflare.com/application-services/products/"><u>optimizations</u></a>, and a powerful <a href="https://www.cloudflare.com/developer-platform/products/"><u>developer platform</u></a>. But customers also continue to rely on Cloudflare for caching and delivering static website content. CDNs are often judged on their ability to return content to visitors as quickly as possible. However, the speed at which content is removed from a CDN's global cache is just as crucial.</p><p>When customers frequently update content such as news, scores, or other data, it is essential they<a href="https://www.cloudflare.com/learning/cdn/common-cdn-issues/"> avoid serving stale, out-of-date information</a> from cache to visitors. This can lead to a <a href="https://www.cloudflare.com/learning/cdn/common-cdn-issues/">subpar experience</a> where users might see invalid prices, or incorrect news. The goal is to remove the stale content and cache the new version of the file on the CDN, as quickly as possible. And that starts by issuing a “purge.”</p><p>In May 2022, we released the <a href="https://blog.cloudflare.com/part1-coreless-purge/"><u>first part</u><b><u> </u></b></a>of the series detailing our efforts to rebuild and publicly document the steps taken to improve the system our customers use, to purge their cached content. Our goal was to increase scalability, and importantly, the speed of our customer’s purges. In that initial post, we explained how our purge system worked and the design constraints we found when scaling. We outlined how after more than a decade, we had outgrown our purge system and started building an entirely new purge system, and provided purge performance benchmarking that users experienced at the time. We set ourselves a lofty goal: to be the fastest.</p><p><b>Today, we’re excited to share that we’ve built the fastest cache purge in the industry.</b>  We now offer a global purge latency for purge by tags, hostnames, and prefixes of less than 150ms on average (P50), representing a 90% improvement since May 2022. Users can now purge from anywhere, (almost) <i>instantly</i>. By the time you hit enter on a purge request and your eyes blink, the file is now removed from our global network — including data centers in <a href="https://www.cloudflare.com/network/"><u>330 cities</u></a> and <a href="https://blog.cloudflare.com/backbone2024/"><u>120+ countries</u></a>.</p><p>But that’s not all. It wouldn’t be Birthday Week if we stopped at just being the fastest purge. We are <b><i>also</i></b><i> </i>announcing that we’re opening up more purge options to Free, Pro, and Business plans. Historically, only Enterprise customers had access to the full arsenal of <a href="https://developers.cloudflare.com/cache/how-to/purge-cache/"><u>cache purge methods</u></a> supported by Cloudflare, such as purge by cache-tags, hostnames, and URL prefixes. As part of rebuilding our purge infrastructure, we’re not only fast but we are able to scale well beyond our current capacity. This enables more customers to use different types of purge. We are excited to offer these new capabilities to all plan types once we finish rolling out our new purge infrastructure, and expect to begin offering additional purge capabilities to all plan types in early 2025. </p>
    <div>
      <h3>Why cache and purge? </h3>
      <a href="#why-cache-and-purge">
        
      </a>
    </div>
    <p>Caching content is like pulling off a spectacular magic trick. It makes loading website content lightning-fast for visitors, slashes the load on origin servers and the cost to operate them, and enables global scalability with a single button press. But here's the catch: for the magic to work, caching requires predicting the future. The right content needs to be cached in the right data center, at the right moment when requests arrive, and in the ideal format. This guarantees astonishing performance for visitors and game-changing scalability for web properties.</p><p>Cloudflare helps make this caching magic trick easy. But regular users of our cache know that getting content into cache is only part of what makes it useful. When content is updated on an origin, it must also be updated in the cache. The beauty of caching is that it holds content until it expires or is evicted. To update the content, it must be actively removed and updated across the globe quickly and completely. If data centers are not uniformly updated or are updated at drastically different times, visitors risk getting different data depending on where they are located. This is where cache “purging” (also known as “cache invalidation”) comes in.</p>
    <div>
      <h3>One-to-many purges on Cloudflare</h3>
      <a href="#one-to-many-purges-on-cloudflare">
        
      </a>
    </div>
    <p>Back in <a href="https://blog.cloudflare.com/rethinking-cache-purge-architecture/"><u>part 2 of the blog series</u></a>, we touched on how there are multiple ways of purging cache: by URL, cache-tag, hostname, URL prefix, and “purge everything”, and discussed a necessary distinction between purging by URL and the other four kinds of purge — referred to as flexible purges — based on the scope of their impact.</p><blockquote><p><i>The reason flexible purge isn’t also fully coreless yet is because it’s a more complex task than “purge this object”; flexible purge requests can end up purging multiple objects – or even entire zones – from cache. They do this through an entirely different process that isn’t coreless compatible, so to make flexible purge fully coreless we would have needed to come up with an entirely new multi-purge mechanism on top of redesigning distribution. We chose instead to start with just purge by URL, so we could focus purely on the most impactful improvements, revamping distribution, without reworking the logic a data center uses to actually remove an object from cache.</i></p></blockquote><p>We said our next steps included a redesign of flexible purges at Cloudflare, and today we’d like to walk you through the resulting system. But first, a brief history of flexible cache purges at Cloudflare and elaboration on why the old flexible purge system wasn’t “coreless compatible”.</p>
    <div>
      <h3>Just in time</h3>
      <a href="#just-in-time">
        
      </a>
    </div>
    <p>“Cache” within a given data center is made up of many machines, all contributing disk space to store customer content. When a request comes in for an asset, the URL and headers are used to calculate a <a href="https://developers.cloudflare.com/cache/how-to/cache-keys/"><u>cache key</u></a>, which is the filename for that content on disk and also determines which machine in the datacenter that file lives on. The filename is the same for every data center, and every data center knows how to use it to find the right machine to cache the content. A <a href="https://developers.cloudflare.com/cache/how-to/purge-cache/"><u>purge request</u></a> for a URL (plus headers) therefore contains everything needed to generate the cache key — the pointer to the response object on disk — and getting that key to every data center is the hardest part of carrying out the purge.</p><p>Purging content based on response properties has a different hardest part. If a customer wants to purge all content with the cache-tag “foo”, for example, there’s no way for us to generate all the cache keys that will point to the files with that cache-tag at request time. Cache-tags are response headers, and the decision of where to store a file is based on request attributes only. To find all files with matching cache-tags, we would need to look at every file in every cache disk on every machine in every data center. That’s thousands upon thousands of machines we would be scanning for each purge-by-tag request. There are ways to avoid actually continuously scanning all disks worldwide (foreshadowing!) but for our first implementation of our flexible purge system, we hoped to avoid the problem space altogether.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7n56ZDJwdBbaTNPJII6s2S/db998973efdca121536a932bc50dd842/image5.png" />
            
            </figure><p>An alternative approach to going to every machine and looking for all files that match some criteria to actively delete from disk was something we affectionately referred to as “lazy purge”. Instead of deleting all matching files as soon as we process a purge request, we wait to do so when we get an end user request for one of those files. Whenever a request comes in, and we have the file in cache, we can compare the timestamp of any recent purge requests from the file owner to the insertion timestamp of the file we have on disk. If the purge timestamp is fresher than the insertion timestamp, we pretend we didn’t find the file on disk. For this to work, we needed to keep track of purge requests going back further than a data center’s maximum cache eviction age to be sure that any file a customer sends a matching flex purge to clear from cache will either be <a href="https://developers.cloudflare.com/cache/concepts/retention-vs-freshness/#retention"><u>naturally evicted</u></a>, or forced to cache MISS and get refreshed from the origin. With this approach, we just needed a distribution and storage system for keeping track of flexible purges.</p>
    <div>
      <h3>Purge looks a lot like a nail</h3>
      <a href="#purge-looks-a-lot-like-a-nail">
        
      </a>
    </div>
    <p>At Cloudflare there is a lot of configuration data that needs to go “everywhere”: cache configuration, load balancer settings, firewall rules, host metadata — countless products, features, and services that depend on configuration data that’s managed through Cloudflare’s control plane APIs. This data needs to be accessible by every machine in every datacenter in our network. The vast majority of that data is distributed via <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale/"><u>a system introduced several years ago called Quicksilver</u></a>. The system works <i>very, very well</i> (sub-second p99 replication lag, globally). It’s extremely flexible and reliable, and reads are lightning fast. The team responsible for the system has done such a good job that Quicksilver has become a hammer that when wielded, makes everything look like a nail… like flexible purges.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/BFntOlvapsYYjYTcxMs7o/d73ad47b63b9d4893b46aeddc28a8698/image2.png" />
            
            </figure><p><sup><i>Core-based purge request entering a data center and getting backhauled to a core data center where Quicksilver distributes the request to all network data centers (hub and spoke).</i></sup><sup> </sup></p><p>Our first version of the flexible purge system used Quicksilver’s spoke-hub distribution to send purges from a core data center to every other data center in our network. It took less than a second for flexible purges to propagate, and once in a given data center, the purge key lookups in the hot path to force cache misses were in the low hundreds of microseconds. We were quite happy with this system at the time, especially because of the simplicity. Using well-supported internal infrastructure meant we weren’t having to manage database clusters or worry about transport between data centers ourselves, since we got that “for free”. Flexible purge was a new feature set and the performance seemed pretty good, especially since we had no predecessor to compare against.</p>
    <div>
      <h3>Victims of our own success</h3>
      <a href="#victims-of-our-own-success">
        
      </a>
    </div>
    <p>Our first version of flexible purge didn’t start showing cracks for years, but eventually both our network and our customer base grew large enough that our system was reaching the limits of what it could scale to. As mentioned above, we needed to store purge requests beyond our maximum eviction age. Purge requests are relatively small, and compress well, but thousands of customers using the API millions of times a day adds up to quite a bit of storage that Quicksilver needed on each machine to maintain purge history, and all of that storage cut into disk space we could otherwise be using to cache customer content. We also found the limits of Quicksilver in terms of how many writes per second it could handle without replication slowing down. We bought ourselves more runway by putting <a href="https://www.boltic.io/blog/kafka-queue#:~:text=Apache%20Kafka%20Queues%3F-,Apache%20Kafka%20queues,-are%20a%20powerful"><u>Kafka queues</u></a> in front of Quicksilver to buffer and throttle ourselves to even out traffic spikes, and increased batching, but all of those protections introduced latency. We knew we needed to come up with a solution without such a strong correlation between usage and operational costs.</p><p>Another pain point exposed by our growing user base that we mentioned in <a href="https://blog.cloudflare.com/rethinking-cache-purge-architecture/"><u>Part 2</u></a> was the excessive round trip times experienced by customers furthest away from our core data centers. A purge request sent by a customer in Australia would have to cross the Pacific Ocean and back before local customers would see the new content.</p><p>To summarize, three issues were plaguing us:</p><ol><li><p>Latency corresponding to how far a customer was from the centralized ingest point.</p></li><li><p>Latency due to the bottleneck for writes at the centralized ingest point.</p></li><li><p>Storage needs in all data centers correlating strongly with throughput demand.</p></li></ol>
    <div>
      <h3>Coreless purge proves useful</h3>
      <a href="#coreless-purge-proves-useful">
        
      </a>
    </div>
    <p>The first two issues affected all types of purge. The spoke-hub distribution model was problematic for purge-by-URL just as much as it was for flexible purges. So we embarked on the path to peer-to-peer distribution for purge-by-URL to address the latency and throughput issues, and the results of that project were good enough that we wanted to propagate flexible purges through the same system. But doing so meant we’d have to replace our use of Quicksilver; it was so good at what it does (fast/reliable replication network-wide, extremely fast/high read throughput) in large part because of the core assumption of spoke-hub distribution it could optimize for. That meant there was no way to write to Quicksilver from “spoke” data centers, and we would need to find another storage system for our purges.</p>
    <div>
      <h3>Flipping purge on its head</h3>
      <a href="#flipping-purge-on-its-head">
        
      </a>
    </div>
    <p>We decided if we’re going to replace our storage system we should dig into exactly what our needs are and find the best fit. It was time to revisit some of our oldest conclusions to see if they still held true, and one of the earlier ones was that proactively purging content from disk would be difficult to do efficiently given our storage layout.</p><p>But was that true? Or could we make active cache purge fast and efficient (enough)? What would it take to quickly find files on disk based on their metadata? “Indexes!” you’re probably screaming, and for good reason. Indexing files’ hostnames, cache-tags, and URLs would undoubtedly make querying for relevant files trivial, but a few aspects of our network make it less straightforward.</p><p>Cloudflare has hundreds of data centers that see trillions of unique files, so any kind of global index — even ignoring the networking hurdles of aggregation — would suffer the same type of bottlenecking issues with our previous spoke-hub system. Scoping the indices to the data center level would be better, but they vary in size up to several hundred machines. Managing a database cluster in each data center scaled to the appropriate size for the aggregate traffic of all the machines was a daunting proposition; it could easily end up being enough work on its own for a separate team, not something we should take on as a side hustle.</p><p>The next step down in scope was an index per machine. Indexing on the same machine as the cache proxy had some compelling upsides: </p><ul><li><p>The proxy could talk to the index over <a href="https://en.wikipedia.org/wiki/Unix_domain_socket"><u>UDS</u></a> (Unix domain sockets), avoiding networking complexities in the hottest paths.</p></li><li><p>As a sidecar service, the index just had to be running anytime the machine was accepting traffic. If a machine died, so would the index, but that didn’t matter, so there wasn’t any need to deal with the complexities of distributed databases.</p></li><li><p>While data centers were frequently adding and removing machines, machines weren’t frequently adding and removing disks. An index could reasonably count on its maximum size being predictable and constant based on overall disk size.</p></li></ul><p>But we wanted to make sure it was feasible on our machines. We analyzed representative cache disks from across our fleet, gathering data like the number of cached assets per terabyte and the average number of cache-tags per asset. We looked at cache MISS, REVALIDATED, and EXPIRED rates to estimate the required write throughput.</p><p>After conducting a thorough analysis, we were convinced the design would work. With a clearer understanding of the anticipated read/write throughput, we started looking at databases that could meet our needs. After benchmarking several relational and non-relational databases, we ultimately chose <a href="https://github.com/facebook/rocksdb"><u>RocksDB</u></a>, a high-performance embedded key-value store. We found that with proper tuning, it could be extremely good at the types of queries we needed.</p>
    <div>
      <h3>Putting it all together</h3>
      <a href="#putting-it-all-together">
        
      </a>
    </div>
    <p>And so CacheDB was born — a service written in Rust and built on RocksDB, which operates on each machine alongside the cache proxy to manage the indexing and purging of cached files. We integrated the cache proxy with CacheDB to ensure that indices are stored whenever a file is cached or updated, and they’re deleted when a file is removed due to eviction or purging. In addition to indexing data, CacheDB maintains a local queue for buffering incoming purge operations. A background process reads purge operations in the queue, looking up all matching files using the indices, and deleting the matched files from disk. Once all matched files for an operation have been deleted, the process clears the indices and removes the purge operation from the queue.</p><p>To further optimize the speed of purges taking effect, the cache proxy was updated to check with CacheDB — similar to the previous lazy purge approach — when a cache HIT occurs before returning the asset. CacheDB does a quick scan of its local queue to see if there are any pending purge operations that match the asset in question, dictating whether the cache proxy should respond with the cached file or fetch a new copy. This means purges will prevent the cache proxy from returning a matching cached file as soon as a purge reaches the machine, even if there are millions of files that correspond to a purge key, and it takes a while to actually delete them all from disk.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5W0ZIGBBbG5Cnc3DSbCPGT/a1572b0b67d844d4e5b7cc7899d320b1/image3.png" />
            
            </figure><p><sup><i>Coreless purge using CacheDB and Durable Objects to distribute purges without needing to first stop at a core data center.</i></sup></p><p>The last piece to change was the distribution pipeline, updated to broadcast flexible purges not just to every data center, but to the CacheDB service running on every machine. We opted for CacheDB to handle the last-mile fan out of machine to machine within a data center, using <a href="https://www.consul.io/"><u>consul</u></a> to keep each machine informed of the health of its peers. The choice let us keep the Workers largely the same for purge-by-URL (more <a href="https://blog.cloudflare.com/rethinking-cache-purge-architecture/"><u>here</u></a>) and flexible purge handling, despite the difference in termination points.</p>
    <div>
      <h3>The payoff</h3>
      <a href="#the-payoff">
        
      </a>
    </div>
    <p>Our new approach reduced the long tail of the lazy purge, saving 10x storage. Better yet, we can now delete purged content immediately instead of waiting for the lazy purge to happen or expire. This new-found storage will improve cache retention on disk for all users, leading to improved cache HIT ratios and reduced egress from your origin.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6B0kVX9Q6qA2JshmcTZSrt/80a845904adf8ba69c121bb54923959e/image1.png" />
            
            </figure><p><sup><i>The shift from lazy content purging (</i></sup><sup><i><u>left</u></i></sup><sup><i>) to the new Coreless Purge architecture allows us to actively delete content (</i></sup><sup><i><u>right</u></i></sup><sup><i>). This helps reduce storage needs and increase cache retention times across our service.</i></sup></p><p>With the new coreless cache purge, we can now get a purge request into any datacenter, distribute the keys to purge, and instantly purge the content from the cache database. This all occurs in less than 150 milliseconds on P50 for tags, hostnames, and prefix URL, covering all <a href="https://www.cloudflare.com/network/"><u>330 cities</u></a> in <a href="https://blog.cloudflare.com/backbone2024/"><u>120+ countries</u></a>.</p>
    <div>
      <h3>Benchmarks</h3>
      <a href="#benchmarks">
        
      </a>
    </div>
    <p>To measure Instant Purge, we wanted to make sure that we were looking at real user metrics — that these were purges customers were actually issuing and performance that was representative of what we were seeing under real conditions, rather than marketing numbers.</p><p>The time we measure represents the period when a request enters the local datacenter, and ends with when the purge has been executed in every datacenter. When the local data center receives the request, one of the first things we do is to add a timestamp to the purge request. When all data centers have completed the purge action, another timestamp is added to “stop the clock.” Each purge request generates this performance data, and it is then sent to a database for us to measure the appropriate quantiles and to help us understand how we can improve further.</p><p>In August 2024, we took purge performance data and segmented our collected data by region based on where the local data center receiving the request was located.</p><table><tr><td><p><b>Region</b></p></td><td><p><b>P50 Aug 2024 (Coreless)</b></p></td><td><p><b>P50 May 2022 (Core-based)</b></p></td><td><p><b>Improvement</b></p></td></tr><tr><td><p>Africa</p></td><td><p>303ms</p></td><td><p>1,420ms</p></td><td><p>78.66%</p></td></tr><tr><td><p>Asia Pacific Region (APAC)</p></td><td><p>199ms</p></td><td><p>1,300ms</p></td><td><p>84.69%</p></td></tr><tr><td><p>Eastern Europe (EEUR)</p></td><td><p>140ms</p></td><td><p>1,240ms</p></td><td><p>88.70%</p></td></tr><tr><td><p>Eastern North America (ENAM)</p></td><td><p>119ms</p></td><td><p>1,080ms</p></td><td><p>88.98%</p></td></tr><tr><td><p>Oceania</p></td><td><p>191ms</p></td><td><p>1,160ms</p></td><td><p>83.53%</p></td></tr><tr><td><p>South America (SA)</p></td><td><p>196ms</p></td><td><p>1,250ms</p></td><td><p>84.32%</p></td></tr><tr><td><p>Western Europe (WEUR)</p></td><td><p>131ms</p></td><td><p>1,190ms</p></td><td><p>88.99%</p></td></tr><tr><td><p>Western North America (WNAM)</p></td><td><p>115ms</p></td><td><p>1,000ms</p></td><td><p>88.5%</p></td></tr><tr><td><p><b>Global</b></p></td><td><p><b>149ms</b></p></td><td><p><b>1,570ms</b></p></td><td><p><b>90.5%</b></p></td></tr></table><p><sup>Note: Global latency numbers on the core-based measurements (May 2022) may be larger than the regional numbers because it represents all of our data centers instead of only a regional portion, so outliers and retries might have an outsized effect.</sup></p>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We are currently wrapping up the roll-out of the last throughput changes which allow us to efficiently scale purge requests. As that happens, we will revise our rate limits and open up purge by tag, hostname, and prefix to all plan types! We expect to begin rolling out the additional purge types to all plans and users beginning in early <b>2025</b>.</p><p>In addition, in the process of implementing this new approach, we have identified improvements that will shave a few more milliseconds off our single-file purge. Currently, single-file purges have a P50 of 234ms. However, we want to, and can, bring that number down to below 200ms.</p><p>If you want to come work on the world's fastest purge system, check out <a href="http://www.cloudflare.com/careers">our open positions</a>.</p>
    <div>
      <h3>Watch on Cloudflare TV</h3>
      <a href="#watch-on-cloudflare-tv">
        
      </a>
    </div>
    <div>
  
</div><p></p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <guid isPermaLink="false">11EWaw0wCUNwPTM30w7oUN</guid>
            <dc:creator>Alex Krivit</dc:creator>
            <dc:creator>Tim Kornhammar</dc:creator>
            <dc:creator> Connor Harwood</dc:creator>
        </item>
    </channel>
</rss>