
<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>Thu, 09 Apr 2026 10:53:50 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Developer Week 2025 wrap-up]]></title>
            <link>https://blog.cloudflare.com/developer-week-2025-wrap-up/</link>
            <pubDate>Mon, 14 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ We’ve closed out Developer Week 2025. Here’s a quick recap of the announcements and in-depth technical explorations that went out during the week. ]]></description>
            <content:encoded><![CDATA[ <p>As we conclude Developer Week 2025, we’re proud to reflect upon the capabilities we’ve added to our developer platform. It’s so rewarding to deliver products, features and tools that help developers build smarter and ship faster, and even more so hearing your responses throughout the week!</p><p>Our VP of Product, Rita Kozlov, <a href="https://blog.cloudflare.com/welcome-to-developer-week-2025/"><u>kicked off Developer Week 2025</u></a> discussing the ever-evolving landscape of development, particularly in the age of AI. AI is no longer just a buzzword or a trope for a science-fiction future — in the realm of modern development, it’s a core tenet (and utility) of how we build, innovate, and solve problems. It’s influencing how and how frequently we ship code, as well as enabling <i>anyone</i> to write it.</p><p>It’s exciting to not only witness this technical revolution, but also to be building a platform that enables developers to be part of it. We want to hear your feedback and see what you build with the new capabilities — reach out to us on <a href="https://discord.com/invite/cloudflaredev"><u>Discord</u></a> or <a href="https://x.com/cloudflaredev"><u>X</u></a>.</p><p>Here’s a recap of our Developer Week 2025 announcements:</p>
    <div>
      <h3>Monday, April 7</h3>
      <a href="#monday-april-7">
        
      </a>
    </div>
    <table><tr><td><p><b>Announcement</b></p></td><td><p><b>Summary</b></p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/building-ai-agents-with-mcp-authn-authz-and-durable-objects"><u>Piecing together the Agent puzzle: MCP, authentication &amp; authorization, and Durable Objects free tier </u></a></p></td><td><p>Toolkit for AI agents includes new Agents SDK support for MCP (Model Context Protocol) clients, authentication/authorization/hibernation for MCP servers, and Durable Objects free tier.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/introducing-autorag-on-cloudflare"><u>Introducing AutoRAG: Fully-Managed Retrieval-Augmented Generation on Cloudflare</u></a></p></td><td><p>Fully managed Retrieval-Augmented Generation (RAG) pipelines powered by Cloudflare's global network and developer platform simplifies how you build and scale RAG pipelines to power your context-aware AI and search applications.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/workflows-ga-production-ready-durable-execution"><u>Cloudflare Workflows is now GA: production-ready durable execution</u></a></p></td><td><p>Workflows — a durable execution engine built directly on top of Workers — is Generally Available and production-ready with new human-in-the-loop capabilities, more scale, and more metrics.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/cloudflare-acquires-outerbase-database-dx"><u>Cloudflare acquires Outerbase to expand database and agent developer experience capabilities</u></a></p></td><td><p>Cloudflare acquired Outerbase, expanding our database and agent developer experience capabilities.</p></td></tr></table>
    <div>
      <h3>Tuesday, April 8</h3>
      <a href="#tuesday-april-8">
        
      </a>
    </div>
    <table><tr><td><p><b>Announcement</b></p></td><td><p><b>Summary</b></p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/building-global-mysql-apps-with-cloudflare-workers-and-hyperdrive"><u>Build global MySQL apps using Cloudflare Workers and Hyperdrive</u></a></p></td><td><p>Workers connect to your MySQL databases with Hyperdrive to deliver optimal performance for regional databases, with support for your favorite drivers and ORMs.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/how-hyperdrive-speeds-up-database-access"><u>Pools across the sea: how Hyperdrive speeds up access to databases and why we’re making it free</u></a></p></td><td><p>Hyperdrive, now available on free tier, leverages key innovations to make global database connections fast. </p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/introducing-the-cloudflare-vite-plugin"><u>“Just use Vite”… with the Workers runtime</u></a></p></td><td><p>The Cloudflare Vite plugin integrates Vite, one of the most popular build tools for web development, with the Workers runtime. We announced the 1.0 release and official support for React Router v7.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/deploying-nextjs-apps-to-cloudflare-workers-with-the-opennext-adapter"><u>Deploy your Next.js app to Cloudflare Workers with the Cloudflare adapter for OpenNext</u></a></p></td><td><p>With the 1.0-beta release of the Cloudflare adapter for OpenNext, you can host your Next.js 14 and 15 applications on Cloudflare Workers.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/full-stack-development-on-cloudflare-workers"><u>Your frontend, backend, and database — now in one Cloudflare Worker</u></a></p></td><td><p>You can now deploy static sites, full-stack, and stateful applications on Cloudflare Workers — the primitives are all here. Framework support for React Router v7, Astro, Vue, and more are generally available today, as is the Cloudflare Vite plugin.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/deploy-workers-applications-in-seconds"><u>Skip the setup: deploy a Workers application in seconds</u></a></p></td><td><p>Developers can set up and deploy your Worker application with a Deploy to Cloudflare button.</p></td></tr></table>
    <div>
      <h3>Wednesday, April 9</h3>
      <a href="#wednesday-april-9">
        
      </a>
    </div>
    <table><tr><td><p><b>Announcement</b></p></td><td><p><b>Summary</b></p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/introducing-cloudflare-realtime-and-realtimekit"><u>Make your apps truly interactive with Cloudflare Realtime and RealtimeKit</u></a></p></td><td><p>We announced Cloudflare Realtime and RealtimeKit, a complete toolkit for shipping real-time audio and video apps in days with SDKs for Kotlin, React Native, Swift, JavaScript, and Flutter.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/secrets-store-beta"><u>Introducing Cloudflare Secrets Store (Beta): secure your secrets, simplify your workflow</u></a></p></td><td><p>Securely store, manage, and deploy account level secrets to Cloudflare Workers through Cloudflare Secrets Store, available in beta — with role-based access control, audit logging, and Wrangler support.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/snippets"><u>Cloudflare Snippets are now Generally Available</u></a></p></td><td><p>Cloudflare Snippets are generally available, enabling fast, cost-free JavaScript-based HTTP traffic modifications across all paid plans. </p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/introducing-workers-observability-logs-metrics-and-queries-all-in-one-place/"><u>Introducing Workers Observability: logs, metrics, and queries – all in one place</u></a></p></td><td><p>Workers Observability powers up with General Availability of Workers Logs and new Query Builder to help you investigate log events across all of your Workers.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/network-performance-update-developer-week-2025"><u>Network performance update: Developer Week 2025</u></a></p></td><td><p>Cloudflare has been tracking and comparing our speed with other top networks since 2021. We take a look at how things have changed since our last update.</p></td></tr></table>
    <div>
      <h3>Thursday, April 10</h3>
      <a href="#thursday-april-10">
        
      </a>
    </div>
    <table><tr><td><p><b>Announcement</b></p></td><td><p><b>Summary</b></p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/r2-data-catalog-public-beta"><u>R2 Data Catalog: Managed Apache Iceberg tables with zero egress fees</u></a></p></td><td><p>R2 Data Catalog is now in public beta: a managed Apache Iceberg data catalog built directly into your R2 bucket.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/d1-read-replication-beta"><u>Sequential consistency without borders: how D1 implements global read replication</u></a></p></td><td><p>D1, Cloudflare’s managed SQL database, announces global read replication beta.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/cloudflare-acquires-arroyo-pipelines-streaming-ingestion-beta"><u>Just landed: streaming ingestion on Cloudflare with Arroyo and Pipelines</u></a></p></td><td><p>We’ve just shipped our new streaming ingestion service, Pipelines. And, we’ve acquired Arroyo, enabling us to bring new SQL-based, stateful transformations to Pipelines and R2.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/making-super-slurper-five-times-faster"><u>Making Super Slurper 5x faster with Workers, Durable Objects, and Queues</u></a></p></td><td><p>We re-architected Super Slurper from the ground up using our Developer Platform — leveraging Cloudflare Workers, Durable Objects, and Queues — and improved transfer speeds to R2 by up to 5x.</p></td></tr></table>
    <div>
      <h3>Friday, April 11</h3>
      <a href="#friday-april-11">
        
      </a>
    </div>
    <table><tr><td><p><b>Announcement</b></p></td><td><p><b>Summary</b></p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/workers-virtual-private-cloud"><u>A global virtual private cloud to build secure cross-cloud apps on Workers</u></a></p></td><td><p>We’re announcing Workers VPC: a global private network that allows applications deployed on Cloudflare Workers to connect to your legacy cloud infrastructure. Now, you can unlock access to your existing APIs and data in external clouds and build global, modern, cross-cloud apps on Workers.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/ai-agents-and-innovation-with-launchpad-cohort5"><u>Startup spotlight: building AI agents and accelerating innovation with Cohort #5</u></a></p></td><td><p>Explore how developers in Workers Launchpad are using Cloudflare to scale AI workloads and streamline automation.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/expanding-cloudflares-startup-program"><u>Startup Program update: empowering every stage of the startup journey</u></a></p></td><td><p>Cloudflare’s Startup Program offers up to \$250,000 in credits for companies building on our Developer Platform across 4 tiers: \$5,000, \$25,000, \$100,000, and \$250,000. </p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/"><u>Simple, scalable, and global: Containers are coming to Cloudflare Workers in June 2025</u></a></p></td><td><p>Cloudflare Containers are coming this June. Run new types of workloads on our network with an experience that is simple, scalable, global and deeply integrated with Workers.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/workers-ai-improvements"><u>Workers AI gets a speed boost, batch workload support, more LoRAs, new models, and a refreshed dashboard</u></a></p></td><td><p>Workers AI inference is faster with speculative decoding &amp; prefix caching. Use our new batch inference for handling large request volumes seamlessly. Build tailored AI apps with more LoRA options. Lastly, new models and a refreshed dashboard round out this Developer Week update for Workers AI.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/simplifying-ncmec-reporting-with-cloudflare-workflows"><u>How we simplified NCMEC reporting with Cloudflare Workflows</u></a></p></td><td><p>Cloudflare replaced a queues-based architecture in our <a href="https://www.missingkids.org/home"><u>National Center for Missing &amp; Exploited Children</u></a> (NCMEC) reporting system with Cloudflare Workflows for a structured, retryable workflow that’s easier to debug and maintain.</p></td></tr><tr><td><p><a href="https://blog.cloudflare.com/azul-certificate-transparency-log"><u>A next-generation Certificate Transparency log built on Cloudflare Workers</u></a></p></td><td><p>With recent developments in Certificate Transparency (CT), Cloudflare built a next-generation CT log on top of Cloudflare’s Developer Platform.</p></td></tr></table>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/kJLEDuWxj3rUFlNNpbL5M/bb81a64ba8d301d8ba707af4362ec806/image1.png" />
          </figure><p>Even though 2025 Developer Week has come to a close, we can’t wait to hear what you’re building and hope you’ll share it with us on <a href="https://x.com/cloudflaredev"><u>X</u></a> or <a href="https://discord.com/invite/cloudflaredev"><u>Discord</u></a>. If you’re looking to get started, check out our <a href="https://developers.cloudflare.com/products/?product-group=Developer+platform"><u>developer documentation</u></a>. </p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[AI]]></category>
            <guid isPermaLink="false">XKeWT3uNotBgN0sHVxCmb</guid>
            <dc:creator>Vy Ton</dc:creator>
        </item>
        <item>
            <title><![CDATA[Simple, scalable, and global: Containers are coming to Cloudflare Workers in June 2025]]></title>
            <link>https://blog.cloudflare.com/cloudflare-containers-coming-2025/</link>
            <pubDate>Fri, 11 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Containers are coming this June. Run new types of workloads on our network with an experience that is simple, scalable, global and deeply integrated with Workers. ]]></description>
            <content:encoded><![CDATA[ <p>It is almost the end of Developer Week and we haven’t talked about containers: until now. As some of you <a href="https://blog.cloudflare.com/container-platform-preview/"><u>may know</u></a>, we’ve been working on a container platform behind the scenes for some time.</p><p>In late June, we plan to release Containers in open beta, and today we’ll give you a sneak peek at what makes it unique.</p><p>Workers are the simplest way to ship software around the world with little overhead. But sometimes you need to do more. You might want to:</p><ul><li><p>Run user-generated code in any language</p></li><li><p>Execute a CLI tool that needs a full Linux environment</p></li><li><p>Use several gigabytes of memory or multiple CPU cores</p></li><li><p>Port an existing application from AWS, GCP, or Azure without a major rewrite</p></li></ul><p>Cloudflare Containers let you do all of that while being simple, scalable, and global.</p><p>Through a deep integration with <a href="https://www.cloudflare.com/developer-platform/products/workers/">Workers</a> and an architecture built on <a href="https://www.cloudflare.com/developer-platform/products/durable-objects/">Durable Objects</a>, Workers can be your:</p><ul><li><p><b>API Gateway</b>: Letting you control routing, authentication, caching, and rate-limiting before requests reach a container</p></li><li><p><b>Service Mesh</b>: Creating private connections between containers with a programmable routing layer</p></li><li><p><b>Orchestrator</b>: Allowing you to write custom scheduling, scaling, and health checking logic for your containers</p></li></ul><p>Instead of having to deploy new services, write custom Kubernetes operators, or wade through control plane configuration to extend the platform, you just write code.</p><p>Let’s see what it looks like.</p>
    <div>
      <h2>Deploying different application types</h2>
      <a href="#deploying-different-application-types">
        
      </a>
    </div>
    
    <div>
      <h3>A stateful workload: executing AI-generated code</h3>
      <a href="#a-stateful-workload-executing-ai-generated-code">
        
      </a>
    </div>
    <p>First, let’s take a look at a stateful example.</p><p>Imagine you are building a platform where end-users can run code generated by an <a href="https://www.cloudflare.com/learning/ai/what-is-large-language-model/">LLM</a>. This code is untrusted, so each user needs their own secure sandbox. Additionally, you want users to be able to run multiple requests in sequence, potentially writing to local files or saving in-memory state.</p><p>To do this, you need to create a container on-demand for each user session, then route subsequent requests to that container. Here’s how you can accomplish this:</p><p>First, you write some <a href="https://github.com/cloudflare/containers-demos/blob/main/ai/wrangler.jsonc#L6"><u>basic Wrangler config</u></a>, then you route requests to containers via your Worker:</p>
            <pre><code>import { Container } from "cloudflare:workers";

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.pathname.startsWith("/execute-code")) {
      const { sessionId, messages } = await request.json();
      // pass in prompt to get the code from Llama 4
      const codeToExecute = await env.AI.run("@cf/meta/llama-4-scout-17b-16e-instruct", { messages });

      // get a different container for each user session
      const id = env.CODE_EXECUTOR.idFromName(sessionId);
      const sandbox = env.CODE_EXECUTOR.get(id);

      // execute a request on the container
      return sandbox.fetch("/execute-code", { method: "POST", body: codeToExecute });
    }

    // ... rest of Worker ...
  },
};

// define your container using the Container class from cloudflare:workers
export class CodeExecutor extends Container {
  defaultPort = 8080;
  sleepAfter = "1m";
}</code></pre>
            <p>Then, deploy your code with a single command: <code>wrangler deploy</code>. This builds your container image, pushes it to Cloudflare’s registry, readies containers to boot quickly across the globe, and deploys your Worker.</p>
            <pre><code>$ wrangler deploy</code></pre>
            <p>That’s it.</p><p>How does it work?</p><p>Your Worker creates and starts up containers on-demand. Each time you call <code>env.CODE_EXECUTOR.get(id)</code> with a unique ID, it sends requests to a unique container instance. The container will automatically boot on the first <code>fetch</code>, then put itself to sleep after a configurable timeout, in this case 1 minute. You only pay for the time that the container is actively running.</p><p>When you request a new container, we boot one in a Cloudflare location near the incoming request. This means that low-latency workloads are well-served no matter the region. Cloudflare takes care of all the pre-warming and caching so you don’t have to think about it.</p><p>This allows each user to run code in their own secure environment.</p>
    <div>
      <h3>Stateless and global: FFmpeg everywhere</h3>
      <a href="#stateless-and-global-ffmpeg-everywhere">
        
      </a>
    </div>
    <p>Stateless and autoscaling applications work equally well on Cloudflare Containers.</p><p>Imagine you want to run a container that takes a video file and turns it into an animated GIF using <a href="https://www.ffmpeg.org/"><u>FFmpeg</u></a>. Unlike the previous example, any container can serve any request, but you still don’t want to send bytes across an ocean and back unnecessarily. So, ideally the app can be deployed everywhere.</p><p>To do this, you declare a container in Wrangler config and turn on <code>autoscaling</code>. This specific configuration ensures that one instance is always running and if CPU usage increases beyond 75% of capacity, additional instances are added:</p>
            <pre><code>"containers": [
  {
    "class_name": "GifMaker",
    "image": "./Dockerfile", // container source code can be alongside Worker code
    "instance_type": "basic",
    "autoscaling": {
      "minimum_instances": 1,
      "cpu_target": 75,
    }
  }
],
// ...rest of wrangler.jsonc...</code></pre>
            <p>To route requests, you just call <code>env.GIF_MAKER.fetch</code> and requests are automatically sent to the closest container:</p>
            <pre><code>import { Container } from "cloudflare:workers";

export class GifMaker extends Container {
  defaultPort: 1337,
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.pathname === "/make-gif") {
      return env.GIF_MAKER.fetch(request)
    }

    // ... rest of Worker ...
  },
};</code></pre>
            
    <div>
      <h3>Going beyond the basics</h3>
      <a href="#going-beyond-the-basics">
        
      </a>
    </div>
    <p>From the examples above, you can see that getting a basic container service running on Workers just takes a few lines of config and a little Workers code. There’s no need to worry about capacity, artifact registries, regions, or scaling.</p><p>For more advanced use, we’ve designed Cloudflare Containers to run on top of Durable Objects and work in tandem with Workers. Let’s take a look at the underlying architecture and see some of the advanced use cases it enables.</p>
    <div>
      <h2>Durable Objects as programmable sidecars</h2>
      <a href="#durable-objects-as-programmable-sidecars">
        
      </a>
    </div>
    <p>Routing to containers is enabled using <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a> under the hood. In the examples above, the <code>Container</code> class from <code>cloudflare:workers</code> just wraps a container-enabled Durable Object and provides helper methods for common patterns. In the rest of this post, we’ll look at examples using Durable Objects directly, as this should shed light on the platform’s underlying design.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/tEAz34lNlHaJtLQVp1qia/734fc01c90e2aca8e5d75c060f09be9e/1.png" />
          </figure><p>Each Durable Object acts as a programmable sidecar that can proxy requests to the container and manages its lifecycle. This allows you to control and extend your containers in ways that are hard on other platforms. </p><p>You can manually start, stop, and execute commands on a specific container by calling RPC methods on its Durable Object, which now has a new object at <code>this.ctx.container</code>:</p>
            <pre><code>class MyContainer extends DurableObject {
  // these RPC methods are callable from a Worker
  async customBoot(entrypoint, envVars) {
    this.ctx.container.start({ entrypoint, env: envVars });
  }

  async stopContainer() {
    const SIGTERM = 15;
    this.ctx.container.signal(SIGTERM);
  }

  async startBackupScript() {
    await this.ctx.container.exec(["./backup"]);
  }
}</code></pre>
            <p>You can also monitor your container and run hooks in response to Container status changes.</p><p>For instance, say you have a CI job that runs builds in a Container. You want to post a message to a <a href="https://developers.cloudflare.com/queues/"><u>Queue</u></a> based on the exit status. You can easily program this behavior:</p>
            <pre><code>class BuilderContainer extends DurableObject {
  constructor(ctx, env) {
    super(ctx, env)
    async function onContainerExit() {
      await this.env.QUEUE.send({ status: "success", message: "Build Complete" });
    }

    async function onContainerError(err) {
      await this.env.QUEUE.send({ status: "error", message: err});
    }

    this.ctx.container.start();
    this.ctx.container.monitor().then(onContainerExit).catch(onContainerError); 
  }

  async isRunning() { return this.ctx.container.running; }
}</code></pre>
            <p>And lastly, if you have state that needs to be loaded into a container each time it runs, you can use status hooks to persist state from the container before it sleeps and to reload state into the container after it starts:</p>
            <pre><code>import { startAndWaitForPort } from "./helpers"

class MyContainer extends DurableObject {
  constructor(ctx, env) {
    super(ctx, env)
    this.ctx.blockConcurrencyWhile(async () =&gt; {
      this.ctx.storage.sql.exec('CREATE TABLE IF NOT EXISTS state (value TEXT)');
      this.ctx.storage.sql.exec("INSERT INTO state (value) SELECT '' WHERE NOT EXISTS 
(SELECT * FROM state)");
      await startAndWaitForPort(this.ctx.container, 8080);
      await this.setupContainer();
      this.ctx.container.monitor().then(this.onContainerExit); 
    });
  }

  async setupContainer() {
    const initialState = this.ctx.storage.sql.exec('SELECT * FROM state LIMIT 1').one().value;
    return this.ctx.container
      .getTcpPort(8080)
      .fetch("http://container/state", { body: initialState, method: 'POST' });
  }

  async onContainerExit() {
    const response = await this.ctx.container
      .getTcpPort(8080)
      .fetch('http://container/state');
    const newState = await response.text();
    this.ctx.storage.sql.exec('UPDATE state SET value = ?', newState);
  }
}</code></pre>
            
    <div>
      <h2>Building around your Containers with Workers</h2>
      <a href="#building-around-your-containers-with-workers">
        
      </a>
    </div>
    <p>Not only do Durable Objects allow you to have fine-grained control over the Container lifecycle, the whole Workers platform allows you to extend routing and scheduling behavior as you see fit.</p>
    <div>
      <h3>Using Workers as an API gateway</h3>
      <a href="#using-workers-as-an-api-gateway">
        
      </a>
    </div>
    <p>Workers provide programmable ingress logic from <a href="https://www.cloudflare.com/network/"><u>over 300 locations</u></a> around the world. In this sense, they provide similar functionality to an <a href="https://www.cloudflare.com/learning/security/api/what-is-an-api-gateway/">API gateway</a>.</p><p>For instance, let’s say you want to route requests to a different version of a container based on information in a header. This is accomplished in a few lines of code:</p>
            <pre><code>export default {
  async fetch(request, env) {
    const isExperimental = request.headers.get("x-version") === "experimental";
    
    if (isExperimental) {
      return env.MY_SERVICE_EXPERIMENTAL.fetch(request);
    } else {
      return env.MY_SERVICE_STANDARD.fetch(request);
    }
  },
};</code></pre>
            <p>Or you want to rate limit and authenticate requests to the container:</p>
            <pre><code>async fetch(request, env) {
  const url = new URL(request.url);

  if (url.pathname.startsWith('/api/')) {
    const token = request.headers.get("token");

    const isAuthenticated = await authenticateRequest(token);
    if (!isAuthenticated) {
      return new Response("Not authenticated", { status: 401 });
    }

    const { withinRateLimit } = await env.MY_RATE_LIMITER.limit({ key: token });
    if (!withinRateLimit) {
      return new Response("Rate limit exceeded for token", { status: 429 });
    }

    return env.MY_APP.fetch(request);
  }
  // ...
}</code></pre>
            
    <div>
      <h3>Using Workers as a service mesh</h3>
      <a href="#using-workers-as-a-service-mesh">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/56gJk7HEDJs4dsNXDkBX1x/7c9d6be661d0cd6baea13caeedf478c0/2.png" />
          </figure><p>By default, Containers are private and can only be accessed via Workers, which can connect to one of many container ports. From within the container, you can expose a plain HTTP port, but requests will still be encrypted from the end user to the moment we send the data to the container’s TCP port in the host. Due to the communication being relayed through the Cloudflare network, the container does not need to set up <a href="https://www.cloudflare.com/application-services/products/ssl/">TLS certificates</a> to have secure connections in its open ports.</p><p>You can connect to the container through a WebSocket from the client too. See <a href="https://github.com/cloudflare/containers-demos/tree/main/websockets"><u>this repository</u></a> for a full example of using Websockets.</p><p>Just as the Durable Object can act as proxy <i>to the container</i>, it can act as a proxy <i>from the container </i>as well. When setting up a container, you can toggle Internet access off and ensure that outgoing requests pass through Workers.</p>
            <pre><code>// ... when starting the container...
this.ctx.container.start({ 
  workersAddress: '10.0.0.2:8080',
  enableInternet: false, // 'enableInternet' is false by default
});

// ... container requests to '10.0.0.2:8080' securely route to a different service...
override async onContainerRequest(request: Request) {
  const containerId = this.env.SUB_SERVICE.idFromName(request.headers['X-Account-Id']);
  return this.env.SUB_SERVICE.get(containerId).fetch(request);
}</code></pre>
            <p>You can ensure all traffic in and out of your container is secured and encrypted end to end without having to deal with networking yourself.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/9PzlxveRZ6NNBiNeZTiWs/c70e5db9cc7d63c02a21f21769801bc4/3.png" />
          </figure><p>This allows you to protect and connect containers within Cloudflare’s network… or even when connecting to <a href="https://blog.cloudflare.com/workers-virtual-private-cloud"><u>external private networks</u></a>.</p>
    <div>
      <h3>Using Workers as an orchestrator</h3>
      <a href="#using-workers-as-an-orchestrator">
        
      </a>
    </div>
    <p>You might require custom scheduling and scaling logic that goes beyond what Cloudflare provides out of the box.</p><p>We don’t want you having to manage complex chains of API calls or writing an <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/"><u>operator</u></a> to get the logic you need. Just write some Worker code.</p><p>For instance, imagine your containers have a long startup period that involves loading data from an external source. You need to pre-warm containers manually, and need control over the specific region to prewarm. Additionally, you need to set up manual health checks that are accessible via Workers. You’re able to achieve this fairly simply with Workers and Durable Objects.</p>
            <pre><code>import { Container, DurableObject } from "cloudflare:workers";

// A singleton Durable Object to manage and scale containers

class ContainerManager extends DurableObject {
  scale(region, instanceCount) {
    for (let i = 0; i &lt; instanceCount; i++) {
      const containerId = env.CONTAINER.idFromName(`instance-${region}-${i}`);
      // spawns a new container with a location hint
      await env.CONTAINER.get(containerId, { locationHint: region }).start();
    }
  }

  async setHealthy(containerId, isHealthy) {
    await this.ctx.storage.put(containerId, isHealthy);
  }
}

// A Container class for the underlying compute

class MyContainer extends Container {
  defaultPort = 8080;

  async onContainerStart() {
    // run healthcheck every 500ms
    await this.scheduleEvery(0.5, 'healthcheck');
  }

  async healthcheck() {
    const manager = this.env.MANAGER.get(
      this.env.MANAGER.idFromName("manager")
    );
    const id = this.ctx.id.toString();

    await this.container.fetch("/_health")
      .then(() =&gt; manager.setHealthy(id, true))
      .catch(() =&gt; manager.setHealthy(id, false));
  }
}</code></pre>
            <p>The <code>ContainerManager </code>Durable Object exposes the <code>scale</code> RPC call, which you can call as needed with a <code>region</code> and <code>instanceCount</code> which scales up the number of active Container instances in a given region <a href="https://developers.cloudflare.com/durable-objects/reference/data-location/#provide-a-location-hint"><u>using a location hint</u></a>. The <code>this.schedule</code> code executes a manually defined <code>healthcheck</code> method on the Container and tracks its state in the Manager for use by other logic in your system.</p><p>These building blocks let users handle complex scheduling logic themselves. For a more detailed example using standard Durable Objects, see <a href="https://github.com/cloudflare/containers-demos/tree/main/load-balancer"><u>this repository</u></a>.</p><p>We are excited to see the patterns you come up with when orchestrating complex applications built with containers, and trust that between Workers and Durable Objects, you’ll have the tools you need.</p>
    <div>
      <h2>Integrating with more of Cloudflare’s Developer Platform</h2>
      <a href="#integrating-with-more-of-cloudflares-developer-platform">
        
      </a>
    </div>
    <p>Since it is <a href="https://blog.cloudflare.com/welcome-to-developer-week-2025/"><u>Developer Week 2025</u></a>, we would be remiss to not talk about <a href="https://developers.cloudflare.com/workflows/"><u>Workflows</u></a>, which <a href="https://blog.cloudflare.com/workflows-ga-production-ready-durable-execution/"><u>just went GA</u></a>, and <a href="https://agents.cloudflare.com/"><u>Agents</u></a>, which <a href="https://blog.cloudflare.com/building-ai-agents-with-mcp-authn-authz-and-durable-objects/"><u>just got even better</u></a>.</p><p>Let’s finish up by taking a quick look at how you can integrate Containers with these two tools.</p>
    <div>
      <h3>Running a short-lived job with Workflows &amp; R2</h3>
      <a href="#running-a-short-lived-job-with-workflows-r2">
        
      </a>
    </div>
    <p>You need to download a large file from <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>, compress it, and upload it. You want to ensure that this succeeds, but don’t want to write retry logic and error handling yourself. Additionally, you don’t want to deal with rotating R2 API tokens or worry about network connections — it should be secure by default.</p><p>This is a perfect opportunity for a <a href="https://developers.cloudflare.com/workflows/"><u>Workflow</u></a> using Containers. The container can do the heavy lifting of compressing files, Workers can stream the data to and from R2, and the Workflow can ensure durable execution.</p>
            <pre><code>export class EncoderWorkflow extends WorkflowEntrypoint&lt;Env, Params&gt; {
  async run(event: WorkflowEvent&lt;Params&gt;, step: WorkflowStep) {
    const id = this.env.ENCODER.idFromName(event.instanceId);
    const container = this.env.ENCODER.get(id);

    await step.do('init container', async () =&gt; {
      await container.init();
    });

    await step.do('compress the object with zstd', async () =&gt; {
      await container.ensureHealthy();
      const object = await this.env.ARTIFACTS.get(event.payload.r2Path);
      const result = await container.fetch('http://encoder/zstd', {
        method: 'POST', body: object.body 
      });
      await this.env.ARTIFACTS.put(`results${event.payload.r2Path}`, result.body);
    });

    await step.do('cleanup container', async () =&gt; {
      await container.destroy();
    });
  }
}</code></pre>
            
    <div>
      <h3>Calling a Container from an Agent</h3>
      <a href="#calling-a-container-from-an-agent">
        
      </a>
    </div>
    <p>Lastly, imagine you have an AI agent that needs to spin up cloud infrastructure (you like to live dangerously). To do this, you want to use <a href="https://github.com/hashicorp/terraform"><u>Terraform</u></a>, but since it’s run from the command line, you can’t run it on Workers.</p><p>By defining a <a href="https://developers.cloudflare.com/agents/concepts/tools/"><u>tool</u></a>, you can enable your Agent to run the shell commands from a container:</p>
            <pre><code>// Make tools that call to a container from an agent

const createExternalResources = tool({
  description: "runs Terraform in a container to create resources",
  parameters: z.object({ sessionId: z.number(), config: z.string() }),
  execute: async ({ sessionId, config }) =&gt; {
    return this.env.TERRAFORM_RUNNER.get(sessionId).applyConfig(config);
  },
});

// Expose RPC Methods that call to the container

class TerraformRunner extends DurableObject {
  async applyConfig(config) {
    await this.ctx.container.getTcpPort(8080).fetch(APPLY_URL, {
      method: 'POST',
      body: JSON.stringify({ config }),
    });
  }

  // ...rest of DO...
}</code></pre>
            <p>Containers are so much more powerful when combined with other tools. Workers make it easy to do so in a secure and simple way.</p>
    <div>
      <h2>Pay for what you use and use the right tool</h2>
      <a href="#pay-for-what-you-use-and-use-the-right-tool">
        
      </a>
    </div>
    <p>The deep integration between Workers and Containers also makes it easy to pick the right tool for the job with regards to cost.</p><p>With Cloudflare Containers, you only pay for what you use. Charges start when a request is sent to the container or it is manually started. Charges stop after the container goes to sleep, which can happen automatically after a configurable timeout. This makes it easy to scale to zero, and allows you to get high utilization even with highly-variable traffic.</p><p>Containers are billed for every 10ms that they are actively running at the following rates:</p><ul><li><p>Memory: $0.0000025 per GB-second</p></li><li><p>CPU: $0.000020 per vCPU-second</p></li><li><p>Disk $0.00000007 per GB-second
</p></li></ul><p>After 1 TB of free data transfer per month, egress from a Container will be priced per-region. We'll be working out the details between now and the beta, and will be launching with clear, transparent pricing across all dimensions so you know where you stand.</p><p>Workers are lighter weight than containers and <a href="https://blog.cloudflare.com/workers-pricing-scale-to-zero"><u>save you money by not charging when waiting on I/O</u></a>. This means that if you can, running on a Worker helps you save on cost. Luckily, on Cloudflare it is easy to route requests to the right tool.</p>
    <div>
      <h3>Cost comparison</h3>
      <a href="#cost-comparison">
        
      </a>
    </div>
    <p>Comparing containers and functions services on paper is always going to be an apples to oranges exercise, and results can vary so much depending on use case. But to share a real example of our own, a year ago when Cloudflare acquired Baselime, Baselime was a heavy user of AWS Lambda. By moving to Cloudflare, <a href="https://blog.cloudflare.com/80-percent-lower-cloud-cost-how-baselime-moved-from-aws-to-cloudflare/"><u>they lowered their cloud compute bill by 80%</u></a>.</p><p>Below we wanted to share one representative example that compares costs for an application that uses both containers and serverless functions together. It’d be easy for us to come up with a contrived example that uses containers sub-optimally on another platform, for the wrong types of workloads. We won’t do that here. We know that navigating cloud costs can be challenging, and that cost is a critical part of deciding what type of compute to use for which pieces of your application.</p><p>In the example below, we’ll compare Cloudflare Containers + Workers against Google Cloud Run, a very well-regarded container platform that we’ve been impressed by.</p>
    <div>
      <h4>Example application</h4>
      <a href="#example-application">
        
      </a>
    </div>
    <p>Imagine that you run an application that serves 50 million requests per month, and each request consumes an average 500 ms of wall-time. Requests to this application are not all the same though — half the requests require a container, and the other half can be served just using serverless functions.</p><table><tr><td><p>Requests per month</p></td><td><p>Wall-time (duration)</p></td><td><p>Compute required</p></td><td><p>Cloudflare</p></td><td><p>Google Cloud</p></td></tr><tr><td><p>25 million</p></td><td><p>500ms</p></td><td><p>Container + serverless functions</p></td><td><p>Containers + Workers</p></td><td><p>Google Cloud Run + Google Cloud Run Functions</p></td></tr><tr><td><p>25 million</p></td><td><p>500ms</p></td><td><p>Serverless functions</p></td><td><p>Workers</p></td><td><p>Google Cloud Run Functions</p></td></tr></table>
    <div>
      <h4>Container pricing</h4>
      <a href="#container-pricing">
        
      </a>
    </div>
    <p>On both Cloud Run and Cloudflare Containers, a container can serve multiple requests. On some platforms, such as AWS Lambda, each container instance is limited to a single request, pushing cost up significantly as request count grows. In this scenario, 50 requests can run simultaneously on a container with 4 GB memory and half of a vCPU. This means that to serve 25 million requests of 500ms each, we need 625,000 seconds worth of compute</p><p>In this example, traffic is bursty and we want to avoid paying for idle-time, so we’ll use Cloud Run’s request-based pricing.</p><table><tr><td><p>
</p></td><td><p>Price per vCPU second</p></td><td><p>Price per GB-second of memory</p></td><td><p>Price per 1m requests</p></td><td><p>Monthly Price for Compute + Requests</p></td></tr><tr><td><p>Cloudflare Containers</p></td><td><p>$0.000020</p></td><td><p>$0.0000025</p></td><td><p>$0.30</p></td><td><p>$20.00</p></td></tr><tr><td><p>Google Cloud Run</p></td><td><p>$0.000024</p></td><td><p>$0.0000025</p></td><td><p>$0.40</p></td><td><p>$23.75</p></td></tr></table><p><sup><i>* Comparison does not include free tiers for either provider and uses a single Tier 1 GCP region</i></sup></p><p>Compute pricing for both platforms are comparable. But as we showed earlier in this post, Containers on Cloudflare run anywhere, on-demand, without configuring and managing regions. Each container has a programmable sidecar with its own database, backed by Durable Objects. It’s the depth of integration with the rest of the platform that makes containers on Cloudflare uniquely programmable.</p>
    <div>
      <h4>Function pricing</h4>
      <a href="#function-pricing">
        
      </a>
    </div>
    <p>The other requests can be served with less compute, and code written in <a href="https://developers.cloudflare.com/workers/languages/javascript/"><u>JavaScript</u></a>, <a href="https://developers.cloudflare.com/workers/languages/typescript/"><u>TypeScript</u></a>, <a href="https://developers.cloudflare.com/workers/languages/python/"><u>Python</u></a> or <a href="https://developers.cloudflare.com/workers/languages/rust/"><u>Rust</u></a>, so we’ll use Workers and Cloud Run Functions.</p><p>These 25 million requests also run for 500 ms each, and each request spends 480 ms waiting on I/O. This means that Workers will <a href="https://blog.cloudflare.com/workers-pricing-scale-to-zero/"><u>only charge for 20 ms of “CPU-time”</u></a>, the time that the Worker actually spends using compute. This ratio of low CPU time to high wall time is extremely common when building AI apps that make inference requests, or even when just building REST APIs and other business logic. Most time is spent waiting on I/O. Based on our data, we typically see Workers use less than 5 ms of CPU time per request vs seconds of wall time (waiting on APIs or I/O).</p><p>The Cloud Run Function will use an instance with 0.083 vCPU and 128 MB memory and charge on both CPU-s and GiB-s for the full 500 ms of wall-time.</p><table><tr><td><p>
</p></td><td><p>Total Price for “wall-time”</p></td><td><p>Total Price for “CPU-time”</p></td><td><p>Total Price for Compute + Requests</p></td></tr><tr><td><p>Cloudflare Workers</p></td><td><p>N/A</p></td><td><p>$0.83</p></td><td><p>$8.33</p></td></tr><tr><td><p>Google Cloud Run Functions</p></td><td><p>$1.44</p></td><td><p>N/A</p></td><td><p>$11.44</p></td></tr></table><p><sup><i>* Comparison does not include free tiers and uses a single Tier 1 GCP region.</i></sup></p><p>This comparison assumes you have configured Google Cloud Run Functions with a max of 20 concurrent requests per instance. On Google Cloud Run Functions, the maximum number of concurrent requests an instance can handle varies based on the efficiency of your function, and your own tolerance for tail latency that can be introduced by traffic spikes. </p><p>Workers automatically scale horizontally, don’t require you to configure concurrency settings (and hope to get it right), and can run in <a href="https://www.cloudflare.com/en-gb/network/"><u>over 300 locations</u></a>.</p>
    <div>
      <h4>A holistic view of costs</h4>
      <a href="#a-holistic-view-of-costs">
        
      </a>
    </div>
    <p>The most important cost metric is the total cost of developing and running an application. And the only way to get the best results is to use the right compute for the job. So the question boils down to friction and integration. How easily can you integrate the ideal building blocks together?</p><p>As more and more software makes use of <a href="https://www.cloudflare.com/learning/ai/what-is-generative-ai/">generative AI</a>, and makes inference requests to LLMs, modern applications must communicate and integrate with a myriad of services. Most systems are increasingly real-time and chatty, often holding open long-lived connections, performing tasks in parallel. Running an instance of an application in a VM or container and calling it a day might have worked 10 years ago, but when we talk to developers in 2025, they are most often bringing many forms of compute to the table for particular use cases.</p><p>This shows the importance of picking a platform where you can seamlessly shift traffic from one source of compute to another. If you want to <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/"><u>rate-limit</u></a>, <a href="https://blog.cloudflare.com/full-stack-development-on-cloudflare-workers/"><u>serve server-side rendered pages, API responses and static assets</u></a>, handle authentication and authorization, make<a href="https://developers.cloudflare.com/workers-ai/"><u> inference requests to AI models</u></a>, run core business logic via <a href="https://blog.cloudflare.com/workflows-ga-production-ready-durable-execution/"><u>Workflows</u></a>, or <a href="https://blog.cloudflare.com/cloudflare-acquires-arroyo-pipelines-streaming-ingestion-beta/"><u>ingest streaming data</u></a>, just handle the request in Workers. Save the heavier compute only for where it is actually the only option. With Cloudflare Workers and Containers, this is as simple as an if-else statement in your Worker. This makes it easy to pick the right tool for the job.</p>
    <div>
      <h2>Coming June 2025</h2>
      <a href="#coming-june-2025">
        
      </a>
    </div>
    <p>We are collecting feedback and putting the finishing touches on our APIs now, and will release the open beta to the public in late June 2025.</p><p>From day one of building Cloudflare Workers, it’s been our goal to build an integrated platform, where Cloudflare products work together as a system, rather than just as a collection of separate products. We’ve taken this same approach with Containers, and aim to make Cloudflare not only the best place to deploy containers across the globe, but the best place to deploy the types of complete applications that developers are building, that use containers in tandem with serverless functions, <a href="http://developers.cloudflare.com/workflows"><u>Workflows</u></a>, <a href="https://agents.cloudflare.com/"><u>Agents</u></a>, and <a href="https://developers.cloudflare.com/"><u>much more</u></a>.</p><p>We’re excited to get this into your hands soon. Stay on the lookout this summer.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6tb0K0eJoK6L4QrUCTWISx/4346edb7593b47768cebcb48369cfa35/4.png" />
          </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Containers]]></category>
            <guid isPermaLink="false">7yspClA37lzZVogRwQzn5F</guid>
            <dc:creator>Mike Nomitch</dc:creator>
            <dc:creator>Gabi Villalonga Simón</dc:creator>
        </item>
        <item>
            <title><![CDATA[Startup spotlight: building AI agents and accelerating innovation with Cohort #5]]></title>
            <link>https://blog.cloudflare.com/ai-agents-and-innovation-with-launchpad-cohort5/</link>
            <pubDate>Fri, 11 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Discover how developers are using Cloudflare to scale AI workloads and streamline automation and how participants in Workers Launchpad Cohort #4 have built, and startups participating in Cohort #5 ]]></description>
            <content:encoded><![CDATA[ <p>With quick access to flexible infrastructure and innovative AI tools, startups are able to deploy production-ready applications with speed and efficiency. Cloudflare plays a pivotal role for countless applications, empowering founders and engineering teams to build, scale, and accelerate their innovations with ease — and without the burden of technical overhead. And when applicable, initiatives like our <a href="http://www.cloudflare.com/forstartups"><u>Startup Program</u></a> and <a href="http://www.cloudflare.com/lp/workers-launchpad"><u>Workers Launchpad</u></a> offer the tooling and resources that further fuel these ambitious projects.</p><p>Cloudflare recently announced<b> </b><a href="https://blog.cloudflare.com/build-ai-agents-on-cloudflare/"><b><u>AI agents</u></b></a>, allowing developers to leverage Cloudflare to deploy <a href="https://www.cloudflare.com/learning/ai/what-is-agentic-ai/">agents</a> to complete autonomous tasks. We’re already seeing some great examples of startups leveraging Cloudflare as their platform of choice to invest in building their agent infrastructure. Read on to see how a few up-and-coming startups are building their AI agent platforms, powered by Cloudflare.</p>
    <div>
      <h2>Lamatic AI built a scalable AI agent platform using Workers for Platform</h2>
      <a href="#lamatic-ai-built-a-scalable-ai-agent-platform-using-workers-for-platform">
        
      </a>
    </div>
    <p>Founded in 2023, Lamatic.ai empowers SaaS startups to seamlessly integrate intelligent AI agents into their products. Lamatic.ai simplifies the deployment of AI agents by offering a fully managed lifecycle with scalability and security in mind. SaaS providers have been leveraging Lamatic to <a href="https://www.cloudflare.com/learning/cloud/how-to-replatform-applications/">replatform</a> their AI workflows via a no-code visual builder to reduce technical debt and ship products faster. Designed for high availability, scalability, and low latency, Lamatic’s architecture enables developers to build AI-driven applications that remain performant under heavy load. After acquiring a high amount of users in a short amount of time on Product Hunt, Lamatic identified there was real interest to solve complex problems with AI Agents, and the team knew they needed to build a solution with scalability and performance in mind.</p><p>Cloudflare plays a key role in supporting Lamatic’s growth. Powered by Cloudflare <a href="https://workers.cloudflare.com/"><b><u>Workers</u></b></a><b>, </b>Lamatic ensures requests process closer to end users, minimizing latency while offloading computational strain from centralized servers. In just a few months, Lamatic.ai has efficiently scaled to over three million serverless requests per month, supporting over 1,000 customers — all managed by a lean three-person team. </p><p>Customers design their Agent Flows through a no-code visual builder, which generates an interoperable YAML configuration. Sensitive credentials such as API keys and model access tokens are securely encrypted and stored in Workers KV, ensuring they are only decrypted at runtime for enhanced security. All YAML configurations are then compiled into a Workers-compatible JavaScript bundle. When a project is deployed, Lamatic orchestrates critical components like sync jobs for scheduled data ETL operations and incoming webhooks to handle event-driven workflows via <b>Cloudflare </b><a href="https://developers.cloudflare.com/queues/"><b><u>Queues</u></b></a>. Once deployed, the project is fully operational as a Cloudflare Worker with an exposed API endpoint, allowing customers to integrate AI-powered automation directly into their applications with minimal friction.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3YUQpvrLjYaee5RJq4TRW3/8d2db69af7607b038855a0f4f1e27dc6/1.png" />
          </figure><p>To scale out their platform, Lamatic.ai built their architecture isolating serverless and AI logic on a per-customer basis. Rather than batching requests into a centralized cluster, Lamatic.ai distributes workloads across Cloudflare’s global network, ensuring each customer and endpoint is served by its own Worker executing dedicated logic. This per-customer deployment model — enabled by <a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/"><b><u>Workers for Platforms</u></b></a><b> </b>— allows Lamatic.ai to deliver customer-specific serverless functions at scale, and reduces technical overhead as they onboard additional customers. Each customer gets a dedicated Worker whose request and rate limits are enabled based on their level of subscription. </p><p>Beyond request processing, Lamatic uses <b>Cloudflare </b><a href="https://developers.cloudflare.com/kv/"><b><u>Workers KV</u></b></a> as a distributed config store to ensure high availability and security. All values are encrypted at rest with <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode"><u>AES-256-GCM</u></a> and decrypted only at runtime, keeping operations both secure and low-latency. Tokens and user credentials are encrypted and stored in the database and KV. </p><p>To further enhance performance, Cloudflare <b>Queues</b> plays a key role in orchestrating task completion. Lamatic uses Queues to offload work from Workers requests, and handle tasks such as webhooks and coordinating distributed processes, both essential for maintaining system consistency and reliability at scale. While Workers handle sync requests at point of execution, longer running jobs process via Queues. For example, during a scheduled ETL sync, new data records generated are stored as a message queue on Cloudflare <a href="https://developers.cloudflare.com/pub-sub/"><b><u>Pub/Sub</u></b></a>. A consumer Worker collects these messages and makes an API request to the pod using the Workers Queue. The consumer Worker consumes more messages as each queue is finished processing.</p><p>Another example of where this has been optimal is for managing AI workflows. Many AI workflows involve concurrent requests to multiple data sources, Queues <a href="https://www.cloudflare.com/learning/ai/how-to-build-rag-pipelines/">streamlines data processing and efficiently feeds information</a> into customers’ <a href="https://www.cloudflare.com/learning/ai/retrieval-augmented-generation-rag/"><u>Retrieval Augmented Generation (RAG)</u></a> workflows. This approach smooths out workload spikes, reduces bottlenecks, and ensures that AI agents can reliably aggregate and process data without delays.</p><p>Beyond this, Lamatic.ai offers <b>Workers AI</b> as one of the support inference providers that customers can use across their platform. Customers can choose to run one of the many open source models hosted on Workers AI, depending on their use case (chatbot, image generation, voice, etc.). Together, these layers solve the challenges of scaling AI agents by handling high volumes of data, maintaining low-latency responses, and ensuring robust security. With Cloudflare’s infrastructure as its backbone, Lamatic.ai has built a resilient and high-performing platform that meets the rigorous demands of modern AI applications, making it an ideal choice for startups embedding AI-driven features into their products.</p>
    <div>
      <h2>Skyward AI automates compliance using AI agents with Durable Objects and <code>agents</code></h2>
      <a href="#skyward-ai-automates-compliance-using-ai-agents-with-durable-objects-and-agents">
        
      </a>
    </div>
    <p>Skyward AI is transforming compliance operations by leveraging Cloudflare’s serverless computing capabilities to build AI-driven compliance agents that streamline critical tasks like evidence collection, real-time risk analysis, and policy updates. Compliance teams in fintech, supply chain, and other highly regulated industries use these AI Agents to extract and organize evidence, provide real-time recommendations, and orchestrate policy and procedural updates automatically. By handling document parsing, risk monitoring, and policy enforcement, these AI Agents reduce the risk of human error while allowing compliance professionals to focus on high-value tasks.</p><p>Skyward has built an AI agents platform designed with a serverless-first approach, avoiding the constraints of centralized cloud computing. To achieve this, the company leverages Cloudflare’s Developer Platform to create and maintain a highly responsive and scalable infrastructure. <b>Workers</b> handle incoming requests like chat inputs, compliance checks, or authentication, and route them efficiently across multiple geographies. Skyward initially built their AI agents infrastructure using <a href="https://developers.cloudflare.com/durable-objects/"><b><u>Durable Objects</u></b></a>,<b> </b><a href="https://developers.cloudflare.com/workflows/"><b><u>Workflows</u></b></a> and JavaScript-native RPC for AI coordination, but has recently transitioned to Cloudflare’s <a href="https://blog.cloudflare.com/build-ai-agents-on-cloudflare/"><u>new AI agents framework</u></a>. Given that <code>agents </code>provides a framework for building and orchestrating AI agents, the migration has helped Skyward abstract the need to manage Durable Objects manually, significantly reducing time spent on managing these tools. While this release is fairly recent, the transition has helped simplify the way that agents communicate, but it also preserved the benefits of their original design like data privacy, isolation, and concurrency management. This has also made it easier to provide real-time feedback and responses to their end users.</p><p>Skyward optimizes real-time compliance automation by achieving sub-100 ms response times for AI agent queries. Workloads are structured to minimize unnecessary network round-trips, and a sync-engine approach proactively preloads and pushes data to clients, delivering a highly responsive user experience. To proxy AI inference, Skyward uses <a href="https://developers.cloudflare.com/ai-gateway/"><b><u>AI Gateway</u></b></a> to provide observability into usage, performance, and costs across multiple vendors, improving their AI operational efficiency. Leveraging Cloudflare’s serverless Developer Platform has allowed Skyward to simplify their architecture while supporting global availability, avoiding the need for Kubernetes clusters or complex locking mechanisms. The team also avoids the burden of managing regional deployments, as Cloudflare’s multi-region support ensures consistent performance worldwide without added operational complexity.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/IW8Nh4NECUH897s3OEETe/6e3823712b3fceca699c068605aa9092/2.png" />
          </figure><p>State management is a critical component to execute agentic workflows. Each compliance session runs within a dedicated <b>Durable Object</b>, which keeps relevant data close to the execution layer. This setup minimizes database round-trips and ensures that tasks like Anti-Money Laundering (AML) checks, Know Your Business (KYB) validation, and document processing remain efficient. Once a compliance session is complete, the system summarizes and stores the relevant information in Postgres and <b>R2</b>, optimizing memory usage without requiring persistent cloud infrastructure.</p><p>To balance low-latency operations with long-term storage, Skyward employs a multi-layered data management strategy. The Skyward team, using <a href="https://developers.cloudflare.com/hyperdrive/"><b><u>Hyperdrive</u></b></a>, has been able to reduce query latency by nearly 50%, allowing compliance teams to receive immediate feedback. At the company's core, Skyward’s goal is to offer a platform that is "streamlined for compliance teams”. The team maintains that a speedy feedback loop ensures end customers get the data and responses needed to act. Whether there's one agent or hundreds of agents processing tasks in parallel, Hyperdrive ensures that database requests to assets like extensive company documentation (i.e. regulations, policies, procedures, internal documents), complex regulatory knowledge graphs, and on-demand context information for conversational workflows are all as performant as possible.</p><p><b>Durable Objects</b> facilitate real-time session state, ensuring AI agents function smoothly without complex locking mechanisms. For larger compliance-related documents, such as legal PDFs and archived data, Cloudflare <a href="https://developers.cloudflare.com/r2/"><b><u>R2</u></b></a> provides long-term storage, ensuring only frequently accessed information remains readily available. This approach enhances performance while keeping storage management efficient and cost-effective.</p><p>Security and scalability remain priorities for compliance-focused AI applications. Skyward enforces strict access controls, ensuring that only authorized users can access development and production environments. Each AI session maintains an auditable log of key events, user actions, and approvals, supporting the ability to export these insights for compliance and legal requirements. Because each agent is deployed in its own instance and has its own database, Skyward ensures that there is a detailed record of every required user, agent interaction, and auditing requirements. On top of this, the ability to deploy and scale globally with Cloudflare’s network has allowed Skyward to maintain consistent, high-performance operations across multiple regions without extensive infrastructure overhead.</p><p>Looking ahead, Skyward plans to further enhance AI agent responsiveness by running select models directly on <b>Cloudflare Workers AI</b>, reducing reliance on external inference providers. The team plans to further integrate <a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/"><b><u>Workers for Platforms</u></b></a> in an effort to better isolate customer data and workflows, giving end users greater control over their compliance automation. As Cloudflare continues to evolve its AI capabilities, Skyward aims to push the boundaries of distributed AI compliance solutions, making regulatory adherence more automated, scalable, and secure.</p>
    <div>
      <h2>Building on Cloudflare</h2>
      <a href="#building-on-cloudflare">
        
      </a>
    </div>
    <p>We’re inspired by how startups like Lamatic AI and Skyward AI are building their AI agent platforms on Cloudflare. This kind of innovation is why we’re proud to see so many startups trust Cloudflare for a scalable, reliable, and efficient foundation. </p><p>We’re also thrilled to share that both Lamatic AI and Skyward AI have been invited to join Cloudflare’s upcoming Workers Launchpad Cohort #5. Speaking of Workers Launchpad, it’s been a few months since our last update — let’s take a look at what’s new.</p>
    <div>
      <h2>Thank you to Workers Launchpad Cohort #4, and a warm welcome to Cohort #5</h2>
      <a href="#thank-you-to-workers-launchpad-cohort-4-and-a-warm-welcome-to-cohort-5">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/52soEAD02j2yywLaDkz7Jv/76bad474948e946fccf845bcda5ed801/3.png" />
          </figure><p>The Workers Launchpad team is blown away by what customers are demonstrating on the Developer Platform. Members of Cohort #4 presented at our bi-annual <a href="https://cloudflare.tv/shows/workers-launchpad-demo-day/workers-launchpad-demo-day-cohort-4/QgLLnBg1"><u>Demo Day</u></a>. We had customers demonstrate what they’re building across a multitude of industries, including (of course) AI / ML, developer tools, 3D design, cloud infrastructure, adtech, media, and beyond. It’s incredibly encouraging to see what all these amazing companies are building on the Cloudflare network, and we look forward to continuing to partner with them throughout their startup journey.</p><p>Following the Demo Day for Workers Launchpad Cohort #4, we’ve seen the largest influx of applications from startups across the globe eager to join Cohort #5. This next wave of founders is pushing the boundaries of what’s possible, building in areas like AI agents, developer tooling, <a href="https://www.cloudflare.com/learning/ai/what-is-model-context-protocol-mcp/">MCP</a>, media, and beyond. With each new cohort, we’re continually inspired by the caliber of founding teams, the bold ideas they bring to life, and the real-world problems they’re tackling with technology.</p><p>Help us give some love and a warm welcome to the participants of Cohort #5:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1RIIq1qTdmBeqeJS7ZoOKh/d65f0cf2c1bb66bdc2f5658b41ad8112/4.png" />
          </figure><p>We can’t wait to share more about what Cohort #5 achieves. Be sure to follow <a href="https://x.com/CloudflareDev"><u>@CloudflareDev</u></a> on X and join our <a href="https://discord.com/invite/cloudflaredev?cf_target_id=4874A68FFE682C2DFA5CB8063B4025ED"><u>Developer Discord</u></a> server to hear updates on the cohorts.</p><p>If you’re developing your application on our Developer Platform, we’d love to learn how Cloudflare is powering your journey. Please <a href="https://docs.google.com/forms/d/e/1FAIpQLSeIZmgY5ste4LZi-hNaJzbXHBbeD2DHWe8R2xxp0wKO0byRYA/viewform"><u>share more</u></a> about what you’re building, and our team will be sure to review your submission. And if you’re a startup and interested in joining Workers Launchpad, feel free to <a href="https://www.cloudflare.com/lp/workers-launchpad/"><u>apply for Cohort 6</u></a> — applications are now open!</p><table><tr><td><p><b>Company</b></p></td><td><p><b>About</b></p></td></tr><tr><td><p><a href="https://www.acemate.ai"><b>Acemate</b></a></p></td><td><p>AI learning platform for university students and educators</p></td></tr><tr><td><p><a href="http://www.centillion.ai"><b>Centillion AI</b></a></p></td><td><p>Building an MCP for identity verification &amp; fraud prevention</p></td></tr><tr><td><p><a href="https://dreamlit.ai/"><b>Dreamlit AI</b></a></p></td><td><p>Add transactional notifications into your app in minutes with no engineers required</p></td></tr><tr><td><p><a href="http://www.ductize.com"><b>Ductize</b></a></p></td><td><p>Productize your service and launch your website with integrated payments within 30 minutes</p></td></tr><tr><td><p><a href="http://www.firmly.ai"><b>Firmly</b></a></p></td><td><p>Agentic commerce platform that enables consumers to shop at the moment of inspiration</p></td></tr><tr><td><p><a href="http://www.heartspace.ai"><b>Heartspace</b></a></p></td><td><p>Deliver quality PR on demand: pay for placements, not retainers</p></td></tr><tr><td><p><a href="http://www.lamatic.ai"><b>Lamatic</b></a></p></td><td><p>Managed AI middleware and IDE to embed AI features quickly and reliably</p></td></tr><tr><td><p><a href="http://lu.ma"><b>Lu.ma</b></a></p></td><td><p>Application that makes it easy to host great events</p></td></tr><tr><td><p><a href="http://www.manticore.ai"><b>Manticore</b></a></p></td><td><p>AI-driven penetration testing: identify, remediate, and retest on-demand</p></td></tr><tr><td><p><a href="http://mc2.fi"><b>MC</b></a><a href="https://mc2finance.com/about-mc-">²</a><a href="http://mc2.fi"><b> Finance</b></a></p></td><td><p>Crowdsource DeFi strategies and list them as ETFs</p></td></tr><tr><td><p><a href="http://muppet.dev"><b>Muppet</b></a></p></td><td><p>Platform for building and managing MCPs at scale</p></td></tr><tr><td><p><a href="http://www.navatech.ai"><b>Navatech</b></a></p></td><td><p>Remove barriers to language, access, and modality for frontline workers</p></td></tr><tr><td><p><a href="https://era.new/"><b>New Era</b></a></p></td><td><p>Craft beautiful emails using natural language</p></td></tr><tr><td><p><a href="http://www.newharbor.co"><b>New Harbor</b></a></p></td><td><p>Simple, friendly, all-in-one cybersecurity for organizations</p></td></tr><tr><td><p><a href="https://nexartis.com/"><b>Nexartis</b></a></p></td><td><p>Enable new paths for rights management, protect intellectual property, and properly monetize individual contributions</p></td></tr><tr><td><p><a href="http://www.nordcraft.com"><b>Nordcraft</b></a></p></td><td><p>Web development engine that lets product teams build beautiful interactive web applications</p></td></tr><tr><td><p><a href="http://www.periculum.io"><b>Periculum</b></a></p></td><td><p>AI provider offering data analytics software solutions to organizations in underserved markets</p></td></tr><tr><td><p><a href="http://pressbox.studio/"><b>Pressbox</b></a></p></td><td><p>AI-powered multi-modal content personalization for sports and media organizations</p></td></tr><tr><td><p><a href="http://www.prompteus.com"><b>Prompteus</b></a></p></td><td><p>Guardrails, logging, and cost reduction for AI integrations</p></td></tr><tr><td><p><a href="https://remixlabs.com/"><b>Remix Labs</b></a></p></td><td><p>Enable next-gen digital engagement with agentic app experiences</p></td></tr><tr><td><p><a href="http://www.skyward.ai"><b>Skyward</b></a></p></td><td><p>The AI-native workspace for compliance teams</p></td></tr><tr><td><p><a href="http://www.usesonora.com"><b>Sonora</b></a></p></td><td><p>AI-native customer intelligence platform that synthesizes customer feedback and delivers actionable insights across all communication channels</p></td></tr><tr><td><p><a href="https://ssojet.com/"><b>SSOJet</b></a></p></td><td><p>Intelligent enterprise SSO that just works</p></td></tr><tr><td><p><a href="http://syrenn.co"><b>Syrenn</b></a></p></td><td><p>AI outbound sales-coaching platform</p></td></tr><tr><td><p><a href="http://testdriver.ai"><b>Testdriver</b></a></p></td><td><p>Increase test coverage with Computer-Use agents</p></td></tr><tr><td><p></p></td><td><p></p></td></tr><tr><td><p><a href="http://www.toolhouse.ai"><b>Toolhouse</b></a></p></td><td><p>Platform that enables any developer to build AI agents and workflows with a great developer experience</p></td></tr><tr><td><p><a href="http://www.unravo.org"><b>Unravo</b></a></p></td><td><p>Agentic AI-powered, end-to-end business research platform</p></td></tr><tr><td><p><a href="http://wittify.ai"><b>Wittify.ai</b></a></p></td><td><p>Advanced Arabic conversational AI for customer engagement activities</p></td></tr><tr><td><p><a href="http://actualize.zerosumdefense.io"><b>Zero Sum Defense</b></a></p></td><td><p>Digital freedom through tailored security and privacy</p></td></tr></table>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/40boL9159chHNCBcqOccse/96aae4b197042500da8a78b172db0cf0/5.png" />
          </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Workers Launchpad]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <guid isPermaLink="false">44H31LjCVMieZ4K7wqlLaj</guid>
            <dc:creator>Christopher Rotas</dc:creator>
        </item>
        <item>
            <title><![CDATA[A global virtual private cloud for building secure cross-cloud apps on Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/workers-virtual-private-cloud/</link>
            <pubDate>Fri, 11 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ We’re announcing Workers VPC: a global private network that allows applications deployed on Cloudflare Workers to connect to your legacy cloud infrastructure.  ]]></description>
            <content:encoded><![CDATA[ <p>Today, we’re sharing a preview of a new feature that makes it easier to build cross-cloud apps: Workers VPC. </p><p>Workers VPC is our take on the traditional <a href="https://www.cloudflare.com/learning/cloud/what-is-a-virtual-private-cloud/"><u>virtual private cloud (VPC)</u></a>, modernized for a network and compute that isn’t tied to a single cloud region. And we’re complementing it with Workers VPC Private Links to make building across clouds easier. Together, they introduce two new capabilities to <a href="https://developers.cloudflare.com/workers"><u>Workers</u></a>:</p><ol><li><p>A way to group your apps’ resources on Cloudflare into isolated environments, where only resources within a Workers VPC can access one another, allowing you to secure and segment app-to-app traffic (a “Workers VPC”).</p></li><li><p>A way to connect a Workers VPC to a legacy VPC in a public or private cloud, enabling your Cloudflare resources to access your resources in private networks and vice versa, as if they were in a single VPC (the “Workers VPC Private Link”).</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3b8ZShcyU8OvMSKi9Ku9fW/70c1ee1a3f10242551dd32438d0bcfba/1.png" />
          </figure><p><sup><i>Workers VPC and Workers VPC Private Link enable bidirectional connectivity between Cloudflare and external clouds</i></sup></p><p>When linked to an external VPC, Workers VPC makes the underlying resources directly addressable, so that application developers can think at the application layer, without dropping down to the network layer. Think of this like a unified VPC across clouds, with built-in service discovery.</p><p>We’re actively building Workers VPC on the foundation of our existing private networking products and expect to roll it out later in 2025. We wanted to share a preview of it early to get feedback and learn more about what you need. </p>
    <div>
      <h2>Building private cross-cloud apps is hard </h2>
      <a href="#building-private-cross-cloud-apps-is-hard">
        
      </a>
    </div>
    <p>Developers are increasingly choosing Workers as their platform of choice, building rich, stateful applications on it. We’re way past Workers’ <a href="https://blog.cloudflare.com/introducing-cloudflare-workers"><u>original edge use-cases</u></a>: you’re modernizing more of your stack and moving more business logic on to Workers. You’re choosing Workers to build real-time collaboration applications that access your external databases, large scale applications that use your secured APIs, and <a href="https://www.cloudflare.com/learning/ai/what-is-model-context-protocol-mcp/">Model Context Protocol</a> (MCP) servers that expose your business logic to <a href="https://www.cloudflare.com/learning/ai/what-is-agentic-ai/">agents</a> as close to your end users as possible.</p><p>Now, you’re running into the final barrier holding you back in external clouds: the VPC. Virtual private clouds provide you with peace of mind and security, but they’ve been cleverly designed to deliberately add mile-high barriers to building your apps on Workers. That’s the unspoken, vested interest behind getting you to use more legacy VPCs:<b> </b>it’s yet another way that <a href="https://blog.cloudflare.com/tag/connectivity-cloud/"><u>captivity clouds</u></a><b> </b>hold your data and apps hostage and lock you in. </p><p>In conversation after conversation, you’ve told us “VPCs are a blocker”. We get it: your company policies mandate the VPC, and with good reason! So, to access private resources from Workers, you have to either 1) create new public APIs that perform authentication to provide secure access, or 2) set up and scale Cloudflare Tunnels and Zero Trust for each resource that you want to access. That’s a lot of hoops to jump through before you can even start building.</p><p>While we have the storage and compute options for you to build fully on Workers, we also understand that you won’t be moving your applications or your data overnight! But we think you should at least be <b>free</b> to choose Workers <b>today</b> to build modern applications, AI agents, and real-time global applications with your existing private APIs and databases. That’s why we’re building Workers VPC.</p><p>We’ve witnessed the pain of building around VPCs first hand. In 2024, we shipped <a href="https://blog.cloudflare.com/elephants-in-tunnels-how-hyperdrive-connects-to-databases-inside-your-vpc-networks/"><u>support for private databases</u></a> for <a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a>. This made it possible for you to connect to databases in an external VPC from Cloudflare Workers, using Cloudflare Tunnels as the underlying network solution. As a point-to-point solution, it’s been working great! But this solution has its limitations: managing and scaling a Cloudflare Tunnel for each resource in your external cloud isn’t sustainable for large, complex architectures. </p><p>We want to provide a dead-simple solution for you to unlock access to external cloud resources, in a manner that scales as you modernize more of your workloads with Workers. And we’re leveraging the experience we have in building Magic WAN and Magic Cloud Networking to make that possible.</p><p>So, we’re taking VPCs global with Workers VPC. And we’re letting you connect them to your legacy private networks with Workers VPC Private Links. Because we think you should be free to build secure, global, cross-cloud apps on Workers. </p>
    <div>
      <h2>Global cross-cloud apps need a global VPC</h2>
      <a href="#global-cross-cloud-apps-need-a-global-vpc">
        
      </a>
    </div>
    <p>Private networks are complex to set up, they span across many layers of abstraction, and entire teams are needed to manage them. There are few things as complex as managing architectures that have outgrown their original point-to-point network! So we knew we needed to provide a simple solution for isolated environments on our platform.</p><p>Workers VPCs are, by definition, virtual private clouds. That means that they allow you to define isolated  environments of Workers and Developer Platform resources like <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>R2</u></a>, <a href="https://developers.cloudflare.com/kv"><u>Workers KV</u></a>, and <a href="https://www.cloudflare.com/developer-platform/products/d1/"><u>D1</u></a> that have secure access to one another. Other resources in your Cloudflare account won’t have access to these — VPCs allow you to specify certain sets of resources that are associated with certain apps and ensure no cross-application access of resources happens.</p><p>Workers VPCs are the equivalent of the legacy VPC, re-envisioned for the Cloudflare Developer Platform. The main difference is how Workers VPCs are implemented under the hood: instead of being built on top of regional, IP-based networking, Workers VPCs are built for global scale with the Cloudflare network performing isolation of resources across all of its datacenters. </p><p>And as you would expect from traditional VPCs, Workers VPCs have networking capabilities that allow them to seamlessly integrate with traditional networks, enabling you to build cross-cloud apps that never leave the networks you trust. That’s where Workers VPC Private Links comes in. </p><p>Like AWS PrivateLink and other VPC-to-VPC approaches, Workers VPC Private Links connect your Workers VPC to your external cloud using either standard tunnels over IPsec or <a href="https://blog.cloudflare.com/cloudflare-network-interconnect/"><u>Cloudflare Network Interconnect</u></a>. When a Private Link is established, resources from either side can access one another directly, with nothing exposed over the public Internet, as if they were a single, connected VPC.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6TXRsaIO3a9tFFWo3LZJqq/adc1d2d54baf595bad661ff9ed035640/2.png" />
          </figure><p><sup><i>Workers VPC Private Link automatically provisions a gateway for IPsec tunnels or Cloudflare Network Interconnect and configures DNS for routing to Cloudflare resources</i></sup></p><p>To make this possible, Workers VPC and Private Links work together to automatically provision and manage the resources in your external cloud. This establishes the connection between both networks and configures the resources required to make bidirectional routing possible. And, because we know some teams will want to maintain full responsibility over resource provisioning, Workers VPC Private Link can automatically provide you with Terraform scripts to provision external cloud resources that you can run yourself.</p><p>After the connection is made, Workers VPC will automatically detect the resources in your external VPC and make them available as bindings with unique IDs. Requests made through the Workers VPC resource binding will automatically be routed to your external VPC, where DNS resolution will occur (if you’re using hostname-accessed resources) and will be routed to the expected resource. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6TvQBKtKVxy9MC0buyE07x/57d03cf1c0be765b582318e53e6d6a8e/3.png" />
          </figure><p>For example, connecting from Cloudflare Workers to a private API in an external VPC is just a matter of calling fetch() on a binding to a named Workers VPC resource:</p>
            <pre><code>const response = await env.WORKERS_VPC_RESOURCE.fetch("/api/users/342");</code></pre>
            <p>Similarly, Cloudflare resources are accessible via a standardized URL that has been configured within a private DNS resource in your external cloud by Workers VPC Private Link. If you were attempting to access R2 objects from an API in your VPC, you would be able to make the request to the expected URL:</p>
            <pre><code>const response = await fetch("https://&lt;account_id&gt;.r2.cloudflarestorage.com.cloudflare-workers-vpc.com");</code></pre>
            <p>Best of all, since Workers VPC is built on our existing platform, it takes full advantage of our networking and routing capabilities to reduce egress fees and let you build global apps.</p><p>First, by supporting <a href="https://developers.cloudflare.com/network-interconnect/"><u>Cloudflare Network Interconnect</u></a> as the underlying connection method, Workers VPC Private Links can help you lower your bandwidth costs by taking advantage of discounted external cloud egress pricing. Second, since Workers VPC is global by nature, your Workers and resources can be placed wherever needed to ensure optimal performance. For instance, with Workers’ <a href="https://developers.cloudflare.com/workers/configuration/smart-placement/"><u>Smart Placement</u></a>, you can ensure that your Workers are automatically placed in a region closest to your external, regional VPC to maximize app performance. </p>
    <div>
      <h2>An end-to-end connectivity cloud</h2>
      <a href="#an-end-to-end-connectivity-cloud">
        
      </a>
    </div>
    <p>Workers VPC unlocks huge swaths of your workloads that are currently locked into external clouds, without requiring you to expose those private resources to the public Internet to build on Workers. Here are real examples of applications that you’ve told us you’re looking forward to build on Workers with Workers VPC:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/52a04nrfXOe7FSVTAJzEn5/20747efd37dcd3e21f2d752ce4a6cdd8/4.png" />
          </figure><p><sup><i>Sample architecture of real-time canvas application built on Workers and Durable Objects accessing a private database and container in an external VPC</i></sup></p><p>Let’s say you’re trying to build a new feature for your application on <a href="https://developers.cloudflare.com/workers"><u>Workers</u></a>. You also want to add real-time collaboration to your app using <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>. And you’re using <a href="http://blog.cloudflare.com/cloudflare-containers-coming-2025"><u>Containers</u></a> as well because you need to access FFmpeg for live video processing. In each scenario, you need a way to persist the state updates in your existing traditional database and access your existing APIs.</p><p>While in the past, you might have had to create a separate API just to handle update operations from Workers and Durable Objects, you can now directly access the traditional database and update the value directly with Workers VPC. </p><p>Same thing goes for <a href="https://modelcontextprotocol.io/introduction"><u>Model Context Protocol (MCP)</u></a> servers! If you’re <a href="https://developers.cloudflare.com/agents/guides/remote-mcp-server/"><u>building an MCP server on Workers</u></a>, you may want to expose certain functionality that isn’t immediately available as a public API, especially if time to market is important. With Workers VPC, you can create new functionality directly in your MCP server that builds upon your private APIs or databases, enabling you to ship quickly and securely. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3B4uuJCwP6RdFugEMjDLsq/65c01455b7903884384390ec344a6f57/5.png" />
          </figure><p><sup><i>Sample architecture of external cloud resources accessing data from R2, D1, KV</i></sup></p><p>Lots of development teams are landing more and more data on the Cloudflare Developer Platform, whether it is storing AI training data on <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a> due to its <a href="https://www.cloudflare.com/the-net/cloud-egress-fees-challenge-future-ai/">zero-egress cost efficiency</a>, application data in <a href="https://developers.cloudflare.com/d1"><u>D1</u></a> with its horizontal sharding model, or configuration data in <a href="https://developers.cloudflare.com/kv"><u>KV</u></a> for its global single-digit millisecond read latencies. </p><p>Now, you need to provide a way to use the training data in R2 from your compute in your external cloud to train or fine-tune LLM models. Since you’re accessing user data, you need to use a private network because it’s mandated by your security teams. Likewise, you need to access user data and configuration data in D1 and KV for certain administrative or analytical tasks and you want to do so while avoiding the public Internet. Workers VPC enables direct, private routing from your external VPC to Cloudflare resources, with easily accessible hostnames from the automatically configured private DNS.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1AF32up4fVJsS9NoI11GRl/bdc8741dcae296c8b3f9ab34c823b520/6.png" />
          </figure><p>Finally, let’s use an <a href="https://developers.cloudflare.com/agents/"><u>AI agents</u></a> example — it’s <a href="https://blog.cloudflare.com/welcome-to-developer-week-2025/"><u>Developer Week 2025</u></a> after all! This AI agent is built on Workers, and uses retrieval augmented generation (RAG) to improve the results of its generated text while minimizing the context window. </p><p>You’re using <a href="https://www.postgresql.org/"><u>PostgreSQL</u></a> and <a href="https://www.elastic.co/elasticsearch"><u>Elasticsearch</u></a> in your external cloud because that’s where your data currently resides and you’re a fan of <a href="https://github.com/pgvector/pgvector"><u>pgvector</u></a>. You’ve decided to use Workers because you want to get to market quickly, and now you need to access your database. Your database is, once again, placed in a private network and is inaccessible from the public Internet. </p><p>While you could provision a new Hyperdrive and Cloudflare Tunnel in a container, since your Workers VPC is already set up and linked, you can access the database directly using either Workers or <a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a>. </p><p>And what if new documents get added to your <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a> in your external cloud? You might want to kick off a workflow to process the new document, chunk it, get embeddings for it, and update the state of your application in consequence, all while providing real-time updates to your end users about the status of the workflow? </p><p>Well, in that case, you can use <a href="https://developers.cloudflare.com/workflows"><u>Workflows</u></a>, triggered by a serverless function in the external cloud. The Workflow will then fetch the new document in object storage, process it as needed, use your preferred embedding provider (whether Workers AI or another provider) in order to process and update the vector stores in Postgres, and then update the state of your application. </p><p>These are just some of the workloads that we know will benefit from Workers VPC on day 1. We’re excited to see what you build and are looking forward to working with you to make global VPCs real. </p>
    <div>
      <h2>A new era for virtual private clouds</h2>
      <a href="#a-new-era-for-virtual-private-clouds">
        
      </a>
    </div>
    <p>We’re incredibly excited for you to be able to build more on Workers with Workers VPC. We believe that private access to your APIs and databases in your private networks will redefine what you can build on Workers. Workers VPCs unlock access to your private resources to let your ship faster, more performant apps on Workers. And we’re obviously going to ensure that <a href="http://blog.cloudflare.com/cloudflare-containers-coming-2025"><u>Containers</u></a> integrate natively with Workers VPC.</p><p>We’re actively building Workers VPC on the networking primitives and on-ramps we’ve been using to connect customer networks at scale, and our goal is to ship an early preview later in 2025.</p><p>We’re planning to tackle connectivity from Workers to external clouds first, enabling you to modernize more apps that need access to private APIs and databases with Workers, before expanding to support full-directional traffic flows and multiple Workers VPC networks. If you want to shape the vision of Workers VPC and have workloads trapped in a legacy cloud, <a href="https://www.cloudflare.com/workers-virtual-private-cloud-signup"><u>express interest here</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Workers VPC]]></category>
            <guid isPermaLink="false">1YpOIeuQls9R5sHSC6ScsF</guid>
            <dc:creator>Thomas Gauvin</dc:creator>
            <dc:creator>Steve Welham</dc:creator>
        </item>
        <item>
            <title><![CDATA[Startup Program update: empowering every stage of the startup journey]]></title>
            <link>https://blog.cloudflare.com/expanding-cloudflares-startup-program/</link>
            <pubDate>Fri, 11 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare’s Startup Program offers up to $250,000 in credits for companies building on our Developer Platform across 4 tiers: $5,000, $25,000, $100,000, and $250,000. ]]></description>
            <content:encoded><![CDATA[ <p>During Cloudflare’s Birthday Week in September 2024, we <a href="https://blog.cloudflare.com/startup-program-250k-credits/"><u>introduced</u></a> a revamped Startup Program designed to make it easier for startups to adopt Cloudflare through a new credits system. This update focused on better aligning the program with how startups and developers actually consume Cloudflare, by providing them with clearer insight into their projected usage, especially as they approach graduation from the program.</p><p>Today, we’re excited to announce an expansion to that program: new credit tiers that better match startups at every stage of their journey. But before we dive into what’s new, let’s take a quick look at what the Startup Program is and why it exists.</p>
    <div>
      <h2>A refresher: what is the Startup Program?</h2>
      <a href="#a-refresher-what-is-the-startup-program">
        
      </a>
    </div>
    <p>Cloudflare for Startups provides credits to help early-stage companies build the next big idea on our platform. Startups accepted into the program receive credits valid for one year or until they’re fully used, whichever comes first.</p><p>Beyond credits, the program includes access to up to three domains with enterprise-level services, giving startups the same advanced tools we provide to large companies to protect and accelerate their most critical applications.</p><p>We know that building a startup is expensive, and Cloudflare is uniquely positioned to support the full-stack needs of modern applications. Our goal is simple: ensure that you have access to the best of Cloudflare’s global network, without the barriers of cost or availability.</p><p>Since launching the revamped credits system in September, we’ve learned a lot from the startups in our program, including what they’re building, what they need, and where they need more flexibility. One of the most common requests was more credit tier options.</p><p>That’s why we’re introducing new tiers that provide even more options to startups as they scale.</p>
    <div>
      <h2>Introducing additional credit tiers</h2>
      <a href="#introducing-additional-credit-tiers">
        
      </a>
    </div>
    <p>The Cloudflare for Startups Program now offers four credit tiers: </p><table><tr><td><p><b>Credit Amount</b></p></td><td><p><b>$5,000</b></p></td><td><p><b>$25,000</b></p></td><td><p><b>$100,000</b></p></td><td><p><b>$250,000</b></p></td></tr><tr><td><p><b>Stage</b></p></td><td><p>Bootstrapped, stealth startups</p></td><td><p>Up-and-coming startups</p></td><td><p>Seed-funded startups</p></td><td><p>Tier 1 startups</p></td></tr><tr><td><p><b>Description</b></p></td><td><p>For startups who are just getting started. This tier is great for building, testing, and iterating your product.</p></td><td><p>For startups with early adopters and proving product market fit.</p></td><td><p>For startups that have raised capital, and are experiencing high growth.</p></td><td><p>For scaling startups that belong to our Tier 1 VC and accelerator network, are building a mission-critical AI application, or are participating in our <a href="https://www.cloudflare.com/lp/workers-launchpad/"><u>Workers Launchpad</u></a> Program.</p></td></tr><tr><td><p><b>Criteria</b></p></td><td><p>Building a software-based product or service</p><p>Founded in the last 5 years</p><p>Valid and matching email address</p></td><td><p><b>$5,000 criteria plus:</b></p><p>
</p><p>Active LinkedIn</p><p>Funded up to $1M</p></td><td><p><b>$25,000 criteria plus:</b></p><p>
</p><p>Funded between $1M and $5M</p><p>Belong to any of our 250+ approved VC or Accelerator partners</p></td><td><p><b>$100,000 criteria plus:</b></p><p>
</p><p>High growth / AI companies, <b>OR</b></p><p>Tier 1 VC &amp; Accelerators</p></td></tr></table><p>These tiers are designed to offer simplicity and clarity by aligning with where you are in your growth journey. (You can check out eligibility criteria and apply to the Startup Program <a href="https://www.cloudflare.com/forstartups/?utm_medium=referral&amp;utm_source=partner&amp;utm_campaign=2024-q4-dev-gbl-developers-ge-ge-general-pay_devweek25"><u>here</u></a>). These tiers are still subject to the same Cloudflare for Startups Terms of Service. Credits are valid for up to one year or when all credits are consumed (whichever comes first).</p>
    <div>
      <h2>Why are we adding additional credit tiers?</h2>
      <a href="#why-are-we-adding-additional-credit-tiers">
        
      </a>
    </div>
    <p>We understand that each startup may have different needs depending on where they’re at in their journey. Some are just getting off the ground, others are scaling rapidly, and each has unique infrastructure needs. With this expansion, we’re reaffirming Cloudflare’s commitment to startups of all sizes, making it easier for you to access the right level of support and resources, exactly when you need them.</p><p>Whether you're launching your MVP or preparing for your next funding round, Cloudflare is here to help you grow.</p>
    <div>
      <h2>What can I use the credit tiers for?</h2>
      <a href="#what-can-i-use-the-credit-tiers-for">
        
      </a>
    </div>
    <p>The vast majority of Cloudflare products (including all products found on the pay-as-you-go plans) can be used on the Startup Program. Beyond going to the <a href="http://www.cloudflare.com/forstartups"><u>website</u></a> to see what products are included, below are a few examples of what you can use your credits for:  </p><p><b>Build AI applications</b></p><p>Store your training data in <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>R2</u></a>, build AI-powered agents (via <a href="https://developers.cloudflare.com/agents/api-reference/"><u>Agents SDK</u></a>) that autonomously perform tasks with <a href="https://www.cloudflare.com/developer-platform/products/durable-objects/"><u>Durable Objects</u></a> and <a href="https://www.cloudflare.com/developer-platform/products/workers/"><u>Workers</u></a>, or use one of over <a href="https://developers.cloudflare.com/workers-ai/models/"><u>50 models</u></a> to run inference tasks on Cloudflare’s global network.  </p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents-starter">
<img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p><p><b>Create immersive realtime experiences</b></p><p>Deliver live audio and video via our <a href="https://www.cloudflare.com/developer-platform/products/cloudflare-calls/"><u>Realtime Kit</u></a>, enhance the experience with an AI-powered chatbot running on <a href="https://www.cloudflare.com/developer-platform/products/workers-ai/"><u>Workers AI</u></a> to transcribe the call, broadcast to large audiences with <a href="https://www.cloudflare.com/developer-platform/products/cloudflare-stream/"><u>Stream</u></a>.  </p><p><b>Build durable multi-step applications</b></p><p>Design and run long-lived, multi-step processes like onboarding flows, document processing, or order fulfillment. Use <a href="https://developers.cloudflare.com/workflows/"><u>Workflows</u></a> to coordinate logic across Workers, Durable Objects, <a href="https://developers.cloudflare.com/queues/"><u>Queues</u></a>, and AI tasks. Easily handle retries, timeouts, and state management without complex orchestration infrastructure.</p>
    <div>
      <h2>What are startups saying about Cloudflare?</h2>
      <a href="#what-are-startups-saying-about-cloudflare">
        
      </a>
    </div>
    
    <div>
      <h3>Webstudio’s no-code platform is powered by Cloudflare’s Developer Platform</h3>
      <a href="#webstudios-no-code-platform-is-powered-by-cloudflares-developer-platform">
        
      </a>
    </div>
    <blockquote><p>"From a modern design tool, you'd expect real-time collaborative features and would like to have resources as close to users as possible. Since betting on the Developer Platform architecture, Cloudflare has done more for us than any other vendor out there!" - Oleg Isonen (Founder &amp; CEO)</p></blockquote>
    <div>
      <h3>GrackerAI’s cybersecurity research engine runs on Cloudflare’s AI and serverless architecture</h3>
      <a href="#grackerais-cybersecurity-research-engine-runs-on-cloudflares-ai-and-serverless-architecture">
        
      </a>
    </div>
    <blockquote><p>“Cloudflare’s fusion of edge computing and AI empowers developers to deploy and utilize AI models with unprecedented efficiency and scale, marking a significant leap forward in how we build and interact with intelligent systems.” - Deepak Gupta (Co-founder &amp; CEO)</p></blockquote>
    <div>
      <h3>Render Better powers faster ecommerce experiences with Cloudflare Workers</h3>
      <a href="#render-better-powers-faster-ecommerce-experiences-with-cloudflare-workers">
        
      </a>
    </div>
    <blockquote><p>"Each month Render Better optimizes billions of monthly requests for ecommerce visitors, delivering faster loading sites that make top brands millions more in revenue. We're able to scale up with Cloudflare's serverless workers, handling every request at the network edge within milliseconds, thanks to the rock solid, DX-friendly scope of the Developer Platform." -  James Koshigoe (Co-founder &amp; CEO)</p></blockquote>
    <div>
      <h2>What will you build on Cloudflare? </h2>
      <a href="#what-will-you-build-on-cloudflare">
        
      </a>
    </div>
    <p>We can’t wait to see what you will build on Cloudflare. <a href="https://www.cloudflare.com/forstartups/"><u>Apply here</u></a> to take advantage of the Cloudflare for Startups Program.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4kpJ5k3dobY8IscTGoxi7u/2bf888052df84772ece9d9858591329d/image1.png" />
          </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare for Startups]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[Startup Enterprise Plan]]></category>
            <guid isPermaLink="false">5ojXWmlREPcudSVM0TNmZk</guid>
            <dc:creator>Christopher Rotas</dc:creator>
            <dc:creator>Melissa Kargiannakis</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we simplified NCMEC reporting with Cloudflare Workflows]]></title>
            <link>https://blog.cloudflare.com/simplifying-ncmec-reporting-with-cloudflare-workflows/</link>
            <pubDate>Fri, 11 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ We transitioned to Cloudflare Workflows to manage complex, multi-step processes more efficiently. This shift replaced our National Center for Missing & Exploited Children (NCMEC) reporting system. ]]></description>
            <content:encoded><![CDATA[ <p>Cloudflare plays a significant role in supporting the Internet’s infrastructure. <a href="https://w3techs.com/technologies/history_overview/proxy/all/q"><u>As a reverse proxy by approximately 20% of all websites</u></a>, we sit directly in the request path between users and the origin, helping to improve performance, security, and reliability at scale. Beyond that, our global network powers services like <a href="https://www.cloudflare.com/en-gb/application-services/products/cdn/"><u>delivery</u></a>, <a href="https://workers.cloudflare.com/"><u>Workers</u></a>, and <a href="https://www.cloudflare.com/en-gb/developer-platform/products/r2/"><u>R2</u></a> — making Cloudflare not just a passive intermediary, but an active platform for delivering and hosting content across the Internet.</p><p>Since Cloudflare’s launch in 2010, we have collaborated with the National Center for Missing and Exploited Children (<a href="https://www.missingkids.org/home"><u>NCMEC</u></a>), a US-based clearinghouse for reporting child sexual abuse material (CSAM), and are committed to doing what we can to support identification and removal of CSAM content.</p><p>Members of the public, <a href="https://blog.cloudflare.com/cloudflares-response-to-csam-online/"><u>customers, and trusted organizations can submit reports</u></a> of abuse observed on Cloudflare’s network. A minority of these reports relate to CSAM, which are triaged with the highest priority by Cloudflare’s Trust &amp; Safety team. We will also forward details of the report, along with relevant files (where applicable) and supplemental information to NCMEC.</p><p>The process to generate and submit reports to NCMEC involves multiple steps, dependencies, and error handling, which quickly became complex under our original queue-based architecture. In this blog post, we discuss how Cloudflare <a href="https://developers.cloudflare.com/workflows/"><u>Workflows</u></a> helped streamline this process and simplify the code behind it.</p>
    <div>
      <h2>Life before Cloudflare Workflows</h2>
      <a href="#life-before-cloudflare-workflows">
        
      </a>
    </div>
    <p>When we designed our latest NCMEC reporting system in early 2024, <a href="https://blog.cloudflare.com/building-workflows-durable-execution-on-workers/"><u>Cloudflare Workflows</u></a> did not exist yet. We used the Workers platform <a href="https://developers.cloudflare.com/queues/"><b><u>Queues</u></b></a> as a solution for managing asynchronous tasks, and structured our system around them.</p><p>Our goal was to ensure reliability, fault tolerance, and automatic retries. However, without an orchestrator, we had to manually handle state, retries, and inter-queue messaging. While Queues worked, we needed something more explicit to help debug and observe the more complex asynchronous workflows we were building on top of the messaging system that Queues gave us.</p><p>In our queue-based architecture each report would go through multiple steps:</p><ol><li><p><b>Validate input</b>: Ensure the report has all necessary details.</p></li><li><p><b>Initiate report</b>: Call the NCMEC API to create a report.</p></li><li><p><b>Fetch impounded files (if applicable)</b>: Retrieve files stored in R2.</p></li><li><p><b>Upload files</b>: Send files to NCMEC via API.</p></li><li><p><b>Finalize report</b>: Mark the report as completed.</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7n99a6YkThlegGitE2i7iv/a53e70ac11e21025d436c27dce7aaf3a/image2.png" />
          </figure><p><sup><i>A diagram of our queue-based architecture </i></sup></p><p>Each of these steps was handled by a separate queue, and if an error occurred, the system would retry the message several times before marking the report as failed. But errors weren’t always straightforward — for instance, if an external API call consistently failed due to bad input or returned an unexpected response shape, retries wouldn’t help. In those cases, the report could get stuck in an intermediate state, and we’d often have to manually dig through logs across different queues to figure out what went wrong.</p><p>Even more frustrating, when handling failed reports, we relied on a "Reaper" — a cron job that ran every hour to resubmit failed reports. Since a report could fail at any step, the Reaper had to deduce which queue failed and send a message to begin reprocessing. This meant:</p><ul><li><p><b>Debugging was a nightmare</b>: Tracing the journey of a single report meant jumping between logs for multiple queues.</p></li><li><p><b>Retries were unreliable</b>: Some queues had retry logic, while others relied on the Reaper, leading to inconsistencies.</p></li><li><p><b>State management was painful</b>: We had no clear way to track whether a report was halfway through the pipeline or completely lost, except by looking through the logs.</p></li><li><p><b>Operational overhead was high</b>: Developers frequently had to manually inspect failed reports and resubmit them.</p></li></ul><p>Queues gave us a solid foundation for moving messages around, but it wasn’t meant to handle orchestration. What we’d really done was build a bunch of loosely connected steps on top of a message bus and hoped it would all hold together. It worked, for the most part, but it was clunky, hard to reason about, and easy to break. Just understanding how a single report moved through the system meant tracing messages across multiple queues and digging through logs.</p><p>We knew we needed something better: a way to define workflows explicitly, with clear visibility into where things were and what had failed. But back then, we didn’t have a good way to do that without bringing in heavyweight tools or writing a bunch of glue code ourselves. When Cloudflare Workflows came along, it felt like the missing piece, finally giving us a simple, reliable way to orchestrate everything without duct tape.</p>
    <div>
      <h2>The solution: Cloudflare Workflows</h2>
      <a href="#the-solution-cloudflare-workflows">
        
      </a>
    </div>
    <p>Once <a href="https://developers.cloudflare.com/workflows/"><u>Cloudflare Workflows</u></a> was <a href="https://blog.cloudflare.com/building-workflows-durable-execution-on-workers/"><u>announced</u></a>, we saw an immediate opportunity to replace our queue-based architecture with a more structured, observable, and retryable system. Instead of relying on a web of multiple queues passing messages to each other, we now have a single workflow that orchestrates the entire process from start to finish. Critically, if any step failed, the Workflow could pick back up from where it left off, without having to repeat earlier processing steps, re-parsing files, or duplicating uploads.</p><p>With Cloudflare Workflows, each report follows a clear sequence of steps:</p><ol><li><p><b>Creating the report</b>: The system validates the incoming report and initiates it with NCMEC.</p></li><li><p><b>Checking for impounded files</b>: If there are impounded files associated with the report, the workflow proceeds to file collection.</p></li><li><p><b>Gathering files</b>: The system retrieves impounded files stored in R2 and prepares them for upload.</p></li><li><p><b>Uploading files to NCMEC</b>: Each file is uploaded to NCMEC using their API, ensuring all relevant evidence is submitted.</p></li><li><p><b>Adding file metadata</b>: Metadata about the uploaded files (hashes, timestamps, etc.) is attached to the report.</p></li><li><p><b>Finalizing the report</b>: Once all files are processed, the report is finalized and marked as complete.</p></li></ol><p>Here’s a simplified version of the orchestrator:</p>
            <pre><code>import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from 'cloudflare:workers';


export class ReportWorkflow extends WorkflowEntrypoint&lt;Env, ReportType&gt; {
  async run(event: WorkflowEvent&lt;ReportType&gt;, step: WorkflowStep) {
    const reportToCreate: ReportType = event.payload;
    let reportId: number | undefined;


    try {
      await step.do('Create Report', async () =&gt; {
        const createdReport = await createReportStep(reportToCreate, this.env);
        reportId = createdReport?.id;
      });


      if (reportToCreate.hasImpoundedFiles) {
        await step.do('Gather Files', async () =&gt; {
          if (!reportId) throw new Error('Report ID is undefined.');
          await gatherFilesStep(reportId, this.env);
        });


        await step.do('Upload Files', async () =&gt; {
          if (!reportId) throw new Error('Report ID is undefined.');
          await uploadFilesStep(reportId, this.env);
        });


        await step.do('Add File Metadata', async () =&gt; {
          if (!reportId) throw new Error('Report ID is undefined.');
          await addFilesInfoStep(reportId, this.env);
        });
      }


      await step.do('Finalize Report', async () =&gt; {
        if (!reportId) throw new Error('Report ID is undefined.');
        await finalizeReportStep(reportId, this.env);
      });
    } catch (error) {
      console.error(error);
      throw error;
    }
  }
}</code></pre>
            <p>Not only can tasks be broken into discrete steps, but the Workflows dashboard gives us real-time visibility into each report processed and the status of each step in the workflow!</p><p>This allows us to easily see active and completed workflows, identify which steps failed and where, and retry failed steps or terminate workflows. These features revolutionize how we troubleshoot issues, providing us with a tool to deep dive into any issues that arise and retry steps with a click of a button.</p><p>Below are two dashboard screenshots, one of our running workflows and the second of an inspection of the success and failures of each step in the workflow. Some workflows look slower or “stuck” — that’s because failed steps are retried with exponential backoff. This helps smooth over transient issues like flaky APIs without manual intervention.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2DjVg3WMp8e5QGy19TuHMj/69e611c9267598c44e5a2b120f0f59ac/image4.png" />
          </figure><p><sup><i>Cloudflare Workflows Dashboard for our NCMEC Workflow</i></sup></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5ElqnGMtnJQumNhuWZI3nb/6866cc9aa2b27856a8730a9faebc1747/image3.png" />
          </figure><p><sup><i>Cloudflare Workflows Dashboard containing a breakout of the NCMEC Workflow Steps</i></sup></p><p>Cloudflare Workflows transformed how we handle NCMEC incident reports. What was once a complex, queue-based architecture is now a structured, retryable, and observable process. Debugging is easier, error handling is more robust, and monitoring is seamless. </p>
    <div>
      <h3>Deploy your own Workflows</h3>
      <a href="#deploy-your-own-workflows">
        
      </a>
    </div>
    <p>If you’re also building larger, multi-step applications, or have an existing Workers application that has started to approach what we ended up with for our incident reporting process, then you can typically wrap that code within a Workflow with minimal changes. <a href="https://developers.cloudflare.com/workflows/examples/backup-d1/"><u>Workflows can read from R2, write to KV, query D1</u></a> and call other APIs just like any other Worker, but are designed to help orchestrate asynchronous, long-running tasks.</p><p>To get started with Workflows, you can head to the <a href="https://developers.cloudflare.com/workflows/"><u>Workflows developer documentation</u></a> and/or pull down the starter project and dive into the code immediately:</p>
            <pre><code>$ npm create cloudflare@latest workflows-starter -- 
--template="cloudflare/workflows-starter"
</code></pre>
            <p><i>Learn more about </i><a href="https://developers.cloudflare.com/workers/workflows"><i><u>Cloudflare Workflows</u></i></a><i>, and about using </i><a href="https://developers.cloudflare.com/cache/reference/csam-scanning/"><i><u>the Cloudflare CSAM Scanning Tool</u></i></a><i>.</i></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Workflows]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[CSAM Reporting]]></category>
            <category><![CDATA[Automation]]></category>
            <category><![CDATA[Security]]></category>
            <guid isPermaLink="false">32j7ZR5lpPUtSjC9lwtY0t</guid>
            <dc:creator>Mahmoud Salem</dc:creator>
            <dc:creator>Rachael Truong</dc:creator>
        </item>
        <item>
            <title><![CDATA[A next-generation Certificate Transparency log built on Cloudflare Workers]]></title>
            <link>https://blog.cloudflare.com/azul-certificate-transparency-log/</link>
            <pubDate>Fri, 11 Apr 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ Learn about recent developments in Certificate Transparency (CT), and how we built a next-generation CT log on top of Cloudflare's Developer Platform. ]]></description>
            <content:encoded><![CDATA[ <p>Any public <a href="https://en.wikipedia.org/wiki/Certificate_authority"><u>certification authority (CA)</u></a> can issue a <a href="https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/"><u>certificate</u></a> for any website on the Internet to allow a webserver to authenticate itself to connecting clients. Take a moment to scroll through the list of trusted CAs for your web browser (e.g., <a href="https://chromium.googlesource.com/chromium/src/+/main/net/data/ssl/chrome_root_store/test_store.certs"><u>Chrome</u></a>). You may recognize (and even trust) some of the names on that list, but it should make you uncomfortable that <i>any</i> CA on that list could issue a certificate for any website, and your browser would trust it. It’s a castle with 150 doors.</p><p><a href="https://datatracker.ietf.org/doc/html/rfc6962"><u>Certificate Transparency (CT)</u></a> plays a vital role in the <a href="https://datatracker.ietf.org/wg/wpkops/about/"><u>Web Public Key Infrastructure (WebPKI)</u></a>, the set of systems, policies, and procedures that help to establish trust on the Internet. CT ensures that all website certificates are <a href="https://crt.sh"><u>publicly visible</u></a> and <a href="https://developers.cloudflare.com/ssl/edge-certificates/additional-options/certificate-transparency-monitoring/"><u>auditable</u></a>, helping to protect website operators from certificate mis-issuance by dishonest CAs, and helping honest CAs to detect key compromise and other failures.</p><p>In this post, we’ll discuss the history, evolution, and future of the CT ecosystem. We’ll cover some of the challenges we and others have faced in operating CT logs, and how the new <a href="https://c2sp.org/static-ct-api"><u>static CT API</u></a> log design lowers the bar for operators, helping to ensure that this critical infrastructure keeps up with the fast growth and changing landscape of the Internet and WebPKI. We’re excited to open source our <a href="https://github.com/cloudflare/azul"><u>Rust implementation</u></a> of the new log design, built for deployment on Cloudflare’s Developer Platform, and to announce <a href="https://github.com/cloudflare/azul/tree/main/crates/ct_worker#test-logs"><u>test logs</u></a> deployed using this infrastructure.</p>
    <div>
      <h2>What is Certificate Transparency?</h2>
      <a href="#what-is-certificate-transparency">
        
      </a>
    </div>
    <p>In 2011, the Dutch CA DigiNotar was <a href="https://threatpost.com/final-report-diginotar-hack-shows-total-compromise-ca-servers-103112/77170/"><u>hacked</u></a>, allowing attackers to forge a certificate for *.google.com and use it to impersonate Gmail to targeted Iranian users in an attempt to compromise personal information. Google caught this because they used <a href="https://developers.cloudflare.com/ssl/reference/certificate-pinning/"><u>certificate pinning</u></a>, but that technique <a href="https://blog.cloudflare.com/why-certificate-pinning-is-outdated/"><u>doesn’t scale well</u></a> for the web. This, among other similar attacks, led a team at Google in 2013 to develop Certificate Transparency (CT) as a mechanism to catch mis-issued certificates. CT creates a public audit trail of all certificates issued by public CAs, helping to protect users and website owners by holding <a href="https://sslmate.com/resources/certificate_authority_failures"><u>CAs accountable</u></a> for the certificates they issue (even unwittingly, in the event of key compromise or software bugs). CT has been a great success: since 2013, over <a href="https://crt.sh/cert-populations"><u>17 billion</u></a> certificates have been logged, and CT was awarded the prestigious <a href="https://blog.transparency.dev/certificate-transparency-wins-the-levchin-prize"><u>Levchin Prize</u></a> in 2024 for its role as a critical safety mechanism for the Internet.</p><p>Let’s take a brief look at the entities involved in the CT ecosystem. Cloudflare itself operates the <a href="https://blog.cloudflare.com/introducing-certificate-transparency-and-nimbus/"><u>Nimbus CT logs</u></a> and the CT monitor powering the <a href="https://blog.cloudflare.com/a-tour-through-merkle-town-cloudflares-ct-ecosystem-dashboard/"><u>Merkle Town</u></a> <a href="https://ct.cloudflare.com"><u>dashboard</u></a>.</p><p><i>Certification Authorities (CAs)</i> are organizations entrusted to issue certificates on behalf of website operators, which in turn can use those certificates to authenticate themselves to connecting clients.</p><p><i>CT-enforcing clients</i> like the <a href="https://googlechrome.github.io/CertificateTransparency/ct_policy.html"><u>Chrome</u></a>, <a href="https://support.apple.com/en-us/103214"><u>Safari</u></a>, and <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Certificate_Transparency"><u>Firefox</u></a> browsers are web clients that only accept certificates compliant with their CT policies. For example, a policy might require that a certificate includes proof that it has been submitted to at least two independently-operated public CT logs.</p><p><i>Log operators</i> run CT logs, which are public, append-only lists of certificates. CAs and other clients can submit a certificate to a CT log to obtain a “promise” from the CT log that it will incorporate the entry into the append-only log within some grace period. CT logs periodically (every few seconds, typically) update their log state to incorporate batches of new entries, and publish a signed checkpoint that attests to the new state.</p><p><i>Monitors</i> are third parties that continuously crawl CT logs and check that their behavior is correct. For instance, they verify that a log is self-consistent and append-only by ensuring that when new entries are added to the log, no previous entries are deleted or modified. Monitors may also examine logged certificates to help website operators detect mis-issuance.</p>
    <div>
      <h2>Challenges in operating a CT log</h2>
      <a href="#challenges-in-operating-a-ct-log">
        
      </a>
    </div>
    <p>Despite the success of CT, it is a less than perfect system. Eric Rescorla has an <a href="https://educatedguesswork.org/posts/transparency-part-2/"><u>excellent writeup</u></a> on the many compromises made to make CT deployable on the Internet of 2013. We’ll focus on the operational complexities of running a CT log.</p><p>Let’s look at the requirements for running a CT log from <a href="https://googlechrome.github.io/CertificateTransparency/log_policy.html#ongoing-requirements-of-included-logs"><u>Chrome’s CT log policy</u></a> (which are more or less mirrored by those of <a href="https://support.apple.com/en-us/103703"><u>Safari</u></a> and <a href="https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/lypRGp4JGGE"><u>Firefox</u></a>), and what can go wrong. The requirements center around <b>integrity</b> and <b>availability</b>.</p><p>To be considered a trusted auditing source, CT logs necessarily have stringent <b>integrity</b> requirements. Anything the log produces must be correct and self-consistent, meaning that a CT log cannot present two different views of the log to different clients, and must present a consistent history for its entire lifetime. Similarly, when a CT log accepts a certificate and promises to incorporate it by returning a Signed Certificate Timestamp (SCT) to the client, it must eventually incorporate that certificate into its append-only log.</p><p>The integrity requirements are unforgiving. A single bit-flip due to a hardware failure or cosmic ray can (<a href="https://www.agwa.name/blog/post/how_ct_logs_fail"><u>and</u></a> <a href="https://groups.google.com/a/chromium.org/g/ct-policy/c/R27Zy9U5NjM"><u>has</u></a>) caused logs to produce incorrect results and thus be disqualified by CT programs. Even software updates to running logs can be fatal, as a change that causes a correctness violation cannot simply be rolled back. Perhaps the <a href="https://github.com/C2SP/C2SP/issues/79"><u>greatest risk</u></a> to individual log integrity is <a href="https://groups.google.com/a/chromium.org/g/ct-policy/c/W1Ty2gO0JNA"><u>failing to incorporate certificates</u></a> for which they issued SCTs, for example if they fail to commit those pending certificates to durable storage. See Andrew Ayer’s <a href="https://www.agwa.name/blog/post/how_ct_logs_fail"><u>great synopsis</u></a> for more examples of CT log failures (up to 2021).</p><p>A CT log must also meet certain <b>availability</b> requirements to effectively provide its core functionality as a publicly auditable log. Clients must be able to reliably retrieve log data — Chrome’s policy requires a minimum of 99% average uptime over a 90-day rolling period for each API endpoint — and any entries for which an SCT has been issued must be incorporated into the log within the grace period, called the Maximum Merge Delay (MMD), 24 hours in Chrome’s policy.</p><p>The design of the current CT log read APIs puts strain on the ability of log operators to meet uptime requirements. The API endpoints are <i>dynamic</i> and not easily cacheable without bespoke caching rules that are aware of the CT API. For instance, the <a href="https://datatracker.ietf.org/doc/html/rfc6962#section-4.6"><u>get-entries</u></a> endpoint allows a client to request arbitrary ranges of entries from a log, and the <a href="https://datatracker.ietf.org/doc/html/rfc6962#section-4.5"><u>get-proof-by-hash</u></a> requires the server to construct inclusion proofs for any certificate requested by the client. To serve these requests, CT log servers need to be backed by databases easily 5-10TB in size capable of serving tens of millions of requests per day. This increases operator complexity and expense, not to mention the high cost of bandwidth of serving these requests.</p><p>MMD violations are unfortunately not uncommon. Cloudflare’s own Nimbus logs have experienced prolonged outages in the past, most recently in <a href="https://blog.cloudflare.com/post-mortem-on-cloudflare-control-plane-and-analytics-outage/"><u>November 2023</u></a> due to complete power loss in the datacenter running the logs. During normal log operation, if the log accepts entries more quickly than it incorporates them, the backlog can grow to exceed the MMD. Log operators can remedy this by rate-limiting or temporarily disabling the write APIs, but this can in turn contribute to violations of the uptime requirements.</p><p>The high bar for log operation has limited the organizations operating CT logs to only <a href="https://ct.cloudflare.com/logs"><u>Cloudflare and five others</u></a>! Losing one or two logs is enough to compromise the stability of the CT ecosystem. Clearly, a change is needed.</p>
    <div>
      <h2>A next-generation CT log design</h2>
      <a href="#a-next-generation-ct-log-design">
        
      </a>
    </div>
    <p>In May 2024, Let’s Encrypt <a href="https://letsencrypt.org/2024/03/14/introducing-sunlight/"><u>announced</u></a> <a href="https://github.com/FiloSottile/sunlight"><u>Sunlight</u></a>, an implementation of a next-generation CT log designed for the modern WebPKI, incorporating a decade of lessons learned from running CT and similar transparency systems. The new CT log design, called the <a href="https://c2sp.org/static-ct-api"><u>static CT API</u></a>, is partially based on the <a href="https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md"><u>Go checksum database</u></a>, and organizes log data as a series of <a href="https://research.swtch.com/tlog#tiling_a_log"><u>tiles</u></a> that are easy to cache and serve. The new design provides efficiency improvements that cut operation costs, help logs to meet availability requirements, and reduce the risk of integrity violations.</p><p>The static CT API is split into two parts, the <a href="https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#monitoring-apis"><b><u>monitoring APIs</u></b></a> (so named because CT monitors are the primary clients), and the <a href="https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#monitoring-apis"><b><u>submission APIs</u></b></a> for adding new certificates to the log.</p><p>The <b>monitoring APIs</b> replace the dynamic read APIs of <a href="https://datatracker.ietf.org/doc/html/rfc6962#section-4"><u>RFC 6962</u></a>, and organize log data into static, cacheable tiles. (See <a href="https://research.swtch.com/tlog#tiling_a_log"><u>Russ Cox’s blog post</u></a> for an in-depth explanation of tiled logs.) CT log operators can efficiently serve static tiles from <a href="https://www.cloudflare.com/developer-platform/solutions/s3-compatible-object-storage/">S3-compatible object storage buckets</a> and cache them using CDN infrastructure, without needing dedicated API servers. Clients can then download the necessary tiles to retrieve specific log entries or reconstruct arbitrary proofs.</p><p>The static CT API introduces another efficiency by deduplicating intermediate and root “issuer” certificates in a log entry’s certificate chain. The number of publicly-trusted issuer certificates is small (<a href="https://www.ccadb.org/"><u>in the low thousands</u></a>), so instead of storing them repeatedly for each log entry, only the issuer hash is stored. Clients can look up issuer certificates by hash from a <a href="https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#issuers"><u>separate endpoint</u></a>.</p><p>The <b>submission APIs</b> remain backwards-compatible with <a href="https://datatracker.ietf.org/doc/html/rfc6962#section-4"><u>RFC 6962</u></a>, meaning that TLS clients and CAs can submit to them without any changes. However, there is one notable addition: the static CT specification requires logs to hold on to requests as it batches and sequences them, and responds with an SCT only after entries have been incorporated into the log. The specification defines a <a href="https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#sct-extension"><u>required SCT extension</u></a> indicating the entry’s index in the log. At the cost of slightly delayed SCT issuance (on the order of seconds), this change eliminates one of the major pain points of operating a CT log (the Merge Delay).</p><p>Having the log <i>index</i> of a certificate available in an SCT enables further efficiencies. <i>SCT auditing</i> refers to the process by which TLS clients or monitors can check if a log has fulfilled its promise to incorporate a certificate for which it has issued an SCT. In the RFC 6962 API, checking if a certificate is present in a log when you don’t already know the index requires using the <a href="https://datatracker.ietf.org/doc/html/rfc6962#section-4.5"><u>get-proof-by-hash</u></a> endpoint to look up the entry by the certificate hash (and the server needs to maintain a mapping from hash to index to efficiently serve these requests). Instead, with the index immediately available in the SCT, clients can directly retrieve the specific log data tile covering that index, even with <a href="https://transparency.dev/summit2024/sct-auditing.html"><u>efficient privacy-preserving techniques</u></a>.</p><p>Since it was announced, the static CT API has taken the CT ecosystem by storm. Aside from <a href="https://github.com/FiloSottile/sunlight"><u>Sunlight</u></a> and our brand new <a href="https://github.com/cloudflare/azul"><u>Azul</u></a> (discussed below), there are at least two other independent implementations, <a href="https://blog.transparency.dev/i-built-a-new-certificate-transparency-log-in-2024-heres-what-i-learned"><u>Itko</u></a> and <a href="https://blog.transparency.dev/introducing-trillian-tessera"><u>Trillian Tessera</u></a>. Several CT monitors (including <a href="https://crt.sh"><u>crt.sh</u></a>, <a href="https://sslmate.com/certspotter/"><u>certspotter</u></a>, <a href="https://censys.com/"><u>Censys</u></a>, and our own <a href="https://ct.cloudflare.com"><u>Merkle Town</u></a>) have added support for the new log format, and as of April 1, 2025, Chrome has begun accepting submissions for <a href="https://groups.google.com/a/chromium.org/g/ct-policy/c/HBFZHG0TCsY/m/HAaVRK6MAAAJ"><u>static CT API logs</u></a> into their CT log program.</p>
    <div>
      <h2>A static CT API implementation on Workers</h2>
      <a href="#a-static-ct-api-implementation-on-workers">
        
      </a>
    </div>
    <p>This section discusses how we designed and built our static CT log implementation, <a href="https://github.com/cloudflare/azul"><u>Azul</u></a> (short for <a href="https://en.wikipedia.org/wiki/Azulejo"><u>azulejos</u></a>, the colorful Portuguese and Spanish ceramic tiles). For curious readers and prospective CT log operators, we encourage you to follow the instructions in the repo to quickly set up your own static CT log. Questions and feedback in the form of GitHub issues are welcome!</p><p>Our two prototype logs, <a href="https://static-ct.cloudflareresearch.com/logs/cftest2025h1a/metadata"><u>Cloudflare Research 2025h1a</u></a> and <a href="https://static-ct.cloudflareresearch.com/logs/cftest2025h2a/metadata"><u>Cloudflare Research 2025h2a</u></a> (accepting certificates expiring in the first and second half of 2025, respectively), are available for testing.</p>
    <div>
      <h3>Design decisions and goals</h3>
      <a href="#design-decisions-and-goals">
        
      </a>
    </div>
    <p>The advent of the static CT API gave us the perfect opportunity to rethink how we run our CT logs. There were a few design decisions we made early on to shape the project.</p><p>First and foremost, we wanted to run our CT logs on our distributed global network. Especially after the <a href="https://blog.cloudflare.com/post-mortem-on-cloudflare-control-plane-and-analytics-outage/"><u>painful November 2023 control plane outage</u></a>, there’s been a push to deploy services on our highly available and resilient network instead of running in centralized datacenters.</p><p>Second, with Cloudflare’s deeply engrained culture of <a href="https://blog.cloudflare.com/tag/dogfooding/"><u>dogfooding</u></a> (building Cloudflare on top of Cloudflare), we decided to implement the CT log on top of Cloudflare’s Developer Platform and <a href="https://workers.cloudflare.com/"><u>Workers</u></a>. </p><p>Dogfooding gives us an opportunity to find pain points in our product offerings, and to provide feedback to our development teams to improve the developer experience for everyone. We restricted ourselves to only features and default limits generally available to customers, so that we could have the same experience as an external Cloudflare developer, and would produce an implementation that anyone could deploy.</p><p>Another major design decision was to implement the CT log in Rust, a modern systems programming language with static typing and built-in memory safety that is heavily used across Cloudflare, and which already has mature (if sometimes <a href="#developing-a-workers-application-in-rust"><u>lacking full feature parity</u></a>) <a href="https://github.com/cloudflare/workers-rs"><u>Workers bindings</u></a> that we have used to build <a href="https://blog.cloudflare.com/wasm-coredumps/"><u>several production services</u></a>. This also provided us with an opportunity to produce Rust crates porting <a href="https://pkg.go.dev/golang.org/x/mod/sumdb"><u>Go implementations</u></a> of various <a href="https://c2sp.org"><u>C2SP</u></a> specifications that can be reused across other projects.</p><p>For the new logs to be deployable, they needed to be at least as performant as existing CT logs. As a point of reference, the <a href="https://ct.cloudflare.com/logs/nimbus2025"><u>Nimbus2025</u></a> log currently handles just over 33 million requests per day (~380/s) across the read APIs, and about 6 million per day (~70/s) across the write APIs.</p>
    <div>
      <h3>Implementation </h3>
      <a href="#implementation">
        
      </a>
    </div>
    <p>We based Azul heavily on <a href="https://github.com/FiloSottile/sunlight"><u>Sunlight</u></a>, a Go application built for deployment as a standalone server. As such, this section serves as a reference for translating a traditional server to Cloudflare’s serverless platform.</p><p>To start, let’s briefly review the Sunlight architecture (described in more detail in the <a href="https://github.com/FiloSottile/sunlight/blob/main/README.md"><u>README</u></a> and <a href="https://filippo.io/a-different-CT-log"><u>original design doc</u></a>). A Sunlight instance is a single Go process, serving one or multiple CT logs. It is backed by three different storage locations with different properties:</p><ul><li><p>A “lock backend” which stores the current checkpoint for each log. This datastore needs to be strongly consistent, but only stores trivial amounts of data.</p></li><li><p>A per-log object storage bucket from which to serve tiles, checkpoints, and issuers to CT clients. This datastore needs to be strongly consistent, and to handle multiple terabytes of data.</p></li><li><p>A per-log deduplication cache, to return SCTs for previously-submitted (pre-)certificates. This datastore is best-effort (as duplicate entries are not fatal to log operation), and stores tens to hundreds of gigabytes of data.</p></li></ul><p>Two major components handle the bulk of the CT log application logic:</p><ul><li><p>A frontend HTTP server handles incoming requests to the submission APIs to add new certificates to the log, validates them, checks the deduplication cache, adds the certificate to a pool of entries to be sequenced, and waits for sequencing to complete before responding to the client.</p></li><li><p>The sequencer periodically (every 1s, by default) sequences the pool of pending entries, writes new tiles to the object backend, persists the latest checkpoint covering the new log state to the lock and object backends, and signals to waiting requests that the pool has been sequenced.</p></li></ul>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6gLwzRo4Azbls2wvM12TJx/80d6f7aad1317f31dfe06a0c474ee93c/image5.png" />
          </figure><p><sup><i>A static CT API log running on a traditional server using the Sunlight implementation.</i></sup></p><p>Next, let’s look at how we can translate these components into ones suitable for deployment on Workers.</p>
    <div>
      <h4>Making it work</h4>
      <a href="#making-it-work">
        
      </a>
    </div>
    <p>Let’s start with the easy choices. The static CT <a href="https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#monitoring-apis"><u>monitoring APIs</u></a> are designed to serve static, cacheable, compressible assets from object storage. The API should be highly available and have the capacity to serve any number of CT clients. The natural choice is <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>Cloudflare R2</u></a>, which provides globally consistent storage with capacity for <a href="https://developers.cloudflare.com/r2/platform/limits/"><u>large data volumes</u></a>, customizability to configure caching and compression, and unbounded read operations.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/qsC1dO8blS1eGOysu9WQa/75da37719be35824a7533dbbd62bede3/image4.png" />
          </figure><p><sup><i>A static CT API log running on Workers using a preliminary version of the Azul implementation which ran into performance limitations.</i></sup></p><p>The static CT <a href="https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#submission-apis"><u>submission APIs</u></a> are where the real challenge lies. In particular, they allow CT clients to submit certificate chains to be incorporated into the append-only log. We used <a href="https://developers.cloudflare.com/learning-paths/workers/concepts/workers-concepts/"><u>Workers</u></a> as the frontend for the CT log application. Workers run in data centers close to the client, scaling on demand to handle request load, making them the ideal place to run the majority of the heavyweight request handling logic, including validating requests, checking the deduplication cache (discussed below), and submitting the entry to be sequenced.</p><p>The next question was where and how we’d run the backend to handle the CT log sequencing logic, which needs to be stateful and tightly coordinated. We chose <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects (DOs)</u></a>, a special type of stateful Cloudflare Worker where each instance has persistent storage and a unique name which can be used to route requests to it from anywhere in the world. DOs are designed to scale effortlessly for applications that can be easily broken up into self-contained units that do not need a lot of coordination across units. For example, a <a href="https://blog.cloudflare.com/introducing-workers-durable-objects/#demo-chat"><u>chat application</u></a> can use one DO to control each chat room. In our model, then, each CT log is controlled by a single DO. This architecture allows us to easily run multiple CT logs within a single Workers application, but as we’ll see, the limitations of <i>individual</i> single-threaded DOs can easily become a bottleneck. More on this later.</p><p>With the CT log backend as a Durable Object, several other components fell into place: Durable Objects’ <a href="https://developers.cloudflare.com/durable-objects/api/storage-api/"><u>strongly-consistent transactional storage</u></a> neatly fit the requirements for the “lock backend” to persist the log’s latest checkpoint, and we can use an <a href="https://developers.cloudflare.com/durable-objects/api/alarms/"><u>alarm</u></a> to trigger the log sequencing every second. We can also use <a href="https://developers.cloudflare.com/durable-objects/reference/data-location/#provide-a-location-hint"><u>location hints</u></a> to place CT logs in locations geographically close to clients for reduced latency, similar to <a href="https://groups.google.com/g/certificate-transparency/c/I74Wp-KdWHc"><u>Google’s Argon and Xenon logs</u></a>.</p><p>The <a href="https://developers.cloudflare.com/workers/platform/storage-options/"><u>choice of datastore</u></a> for the deduplication cache proved to be non-obvious. The cache is best-effort, and intended to avoid re-sequencing entries that are already present in the log. The cache key is computed by hashing certain fields of the <code>add-[pre-]chain</code> request, and the cache value consists of the entry’s index in the log and the timestamp at which it was sequenced. At current log submission rates, the deduplication cache could grow in excess of <a href="https://github.com/FiloSottile/sunlight/tree/main?tab=readme-ov-file#operating-a-sunlight-log"><u>50 GB for 6 months of log data</u></a>. In the Sunlight implementation, the deduplication cache is implemented as a local SQLite database, where checks against it are tightly coupled with sequencing, which ensures that duplicates from in-flight requests are correctly accounted for. However, this architecture did not translate well to Cloudflare's architecture. The data size doesn’t comfortably fit within <a href="https://developers.cloudflare.com/durable-objects/platform/limits/"><u>Durable Object Storage</u></a> or <a href="https://developers.cloudflare.com/d1/platform/limits/"><u>single-database D1</u></a> limits, and it was too slow to directly read and write to remote storage from within the sequencing loop. Ultimately, we split the deduplication cache into two components: a local fixed-size in-memory cache for fast deduplication over short periods of time (on the order of minutes), and the other a long-term deduplication cache built on <a href="https://developers.cloudflare.com/kv/"><u>Cloudflare Workers KV</u></a> a global, low-latency, <a href="https://developers.cloudflare.com/kv/reference/faq/#is-workers-kv-eventually-consistent-or-strongly-consistent"><u>eventually-consistent</u></a> key-value store <a href="https://developers.cloudflare.com/kv/platform/limits/"><u>without storage limitations</u></a>.</p><p>With this architecture, it was <a href="#developing-a-workers-application-in-rust"><u>relatively straightforward</u></a> to port the Go code to Rust, and to bring up a functional static CT log up on Workers. We’re done then, right? Not quite. Performance tests showed that the log was only capable of sequencing 20-30 new entries per second, well under the 70 per second target of existing logs. We could work around this by simply <a href="https://letsencrypt.org/2024/03/14/introducing-sunlight/#running-more-logs"><u>running more logs</u></a>, but that puts strain on other parts of the CT ecosystem — namely on TLS clients and monitors, which need to keep state for each log. Additionally, the alarm used to trigger sequencing would often be delayed by multiple seconds, meaning that the log was failing to produce new tree heads at consistent intervals. Time to go back to the drawing board.</p>
    <div>
      <h4>Making it fast</h4>
      <a href="#making-it-fast">
        
      </a>
    </div>
    <p>In the design thus far, we’re asking a single-threaded Durable Object instance to do a lot of multi-tasking. The DO processes incoming requests from the Frontend Worker to add entries to the sequencing pool, and must periodically sequence the pool and write state to the various storage backends. A log handling 100 requests per second needs to switch between 101 running tasks (the extra one for the sequencing), plus any async tasks like writing to remote storage — usually 10+ writes to object storage and one write to the long-term deduplication cache per sequenced entry. No wonder the sequencing task was getting delayed!</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7BCidjDyYw2YS1Ot84LHdk/240ce935eb4e36c82255d846d964fdff/image2.png" />
          </figure><p><sup><i>A static CT API log running on Workers using the Azul implementation with batching to improve performance.</i></sup></p><p>We were able to work around these issues by adding an additional layer of DOs between the Frontend Worker and the Sequencer, which we call Batchers. The Frontend Worker uses <a href="https://en.wikipedia.org/wiki/Consistent_hashing"><u>consistent hashing</u></a> on the cache key to determine which of several Batchers to submit the entry to, and the Batcher helps to reduce the number of requests to the Sequencer by buffering requests and sending them together in batches. When the batch is sequenced, the Batcher distributes the responses back to the Frontend Workers that submitted the request. The Batcher also handles writing updates to the deduplication cache, further freeing up resources for the Sequencer.</p><p>By limiting the scope of the critical block of code that needed to be run synchronously in a single DO, and leaning on the strengths of DOs by scaling horizontally where the workload allows it, we were able to drastically improve application performance. With this new architecture, the CT log application can handle upwards of 500 requests per second to the submission APIs to add new log entries, while maintaining a consistent sequencing tempo to keep per-request latency low (typically 1-2 seconds).</p>
    <div>
      <h3>Developing a Workers application in Rust</h3>
      <a href="#developing-a-workers-application-in-rust">
        
      </a>
    </div>
    <p>One of the reasons I was excited to work on this project is that it gave me an opportunity to implement a Workers application in Rust, which I’d never done from scratch before. Not everything was smooth, but overall I would recommend the experience.</p><p>The <a href="https://github.com/cloudflare/workers-rs"><u>Rust bindings to Cloudflare Workers</u></a> are an open source project that aims to bring support for all of the features you know and love from the <a href="https://developers.cloudflare.com/workers/languages/javascript/"><u>JavaScript APIs</u></a> to the Rust language. However, there is some lag in terms of feature parity. Often when working on this project, I’d read about a particular Workers feature in the <a href="https://developers.cloudflare.com"><u>developer docs</u></a>, only to find that support had <a href="https://github.com/cloudflare/workers-rs/issues/645"><u>not yet</u></a> <a href="https://github.com/cloudflare/workers-rs/issues/716"><u>been added</u></a>, or was only <a href="https://github.com/cloudflare/workers-rs?tab=readme-ov-file#rpc-support"><u>partially supported</u></a>, for the Rust bindings. I came across some <a href="https://github.com/cloudflare/workers-rs/issues/432"><u>surprising gotchas</u></a> (not all bad, like <a href="https://docs.rs/tokio/1.44.1/tokio/sync/watch/index.html"><u>tokio::sync::watch</u></a> channels <a href="https://github.com/cloudflare/workers-rs/pull/719"><u>working seamlessly</u></a>, despite <a href="https://github.com/cloudflare/workers-rs?tab=readme-ov-file#faq"><u>this warning</u></a>). Documentation about <a href="https://developers.cloudflare.com/workers/observability/dev-tools/breakpoints/"><u>debugging</u></a> and <a href="https://developers.cloudflare.com/workers/observability/dev-tools/cpu-usage/"><u>profiling</u></a> Rust Workers was also not clear (e.g., how to <a href="https://github.com/cloudflare/cloudflare-docs/pull/21347"><u>preserve debug symbols</u></a>), but it does in fact work!</p><p>To be clear, these rough edges are expected! The Workers platform is continuously gaining new features, and it’s natural that the Rust bindings would fall behind. As more developers rely on (and contribute to, <i>hint hint</i>) the Rust bindings, the developer experience will continue to improve.</p>
    <div>
      <h2>What is next for Certificate Transparency</h2>
      <a href="#what-is-next-for-certificate-transparency">
        
      </a>
    </div>
    <p>The WebPKI is constantly evolving and growing, and upcoming changes, in particular shorter certificate lifetimes and larger post-quantum certificates, are going to place significantly more load on the CT ecosystem.</p><p>The <a href="https://cabforum.org/"><u>CA/Browser Forum</u></a> defines a set of <a href="https://cabforum.org/working-groups/server/baseline-requirements/documents/TLSBRv2.0.4.pdf"><u>Baseline Requirements</u></a> for publicly-trusted TLS server certificates.  As of 2020, the maximum certificate lifetime for publicly-trusted certificates is 398 days. However, there is a <a href="https://github.com/cabforum/servercert/pull/553"><u>ballot measure</u></a> to reduce that period to as low as 47 days by March 2029. Let’s Encrypt is going even further, and at the <a href="https://letsencrypt.org/2024/12/11/eoy-letter-2024/"><u>end of 2024 announced</u></a> that they will be offering short-lived certificates with a lifetime of only <a href="https://letsencrypt.org/2025/01/16/6-day-and-ip-certs/"><u>six days</u></a> by the end of 2025. Based on some back-of-the-envelope calculations using statistics from <a href="https://ct.cloudflare.com/"><u>Merkle Town</u></a>, these changes could increase the number of logged entries in the CT ecosystem by <b>16-20x</b>.</p><p>If you’ve been keeping up with this blog, you’ll also know that <a href="https://blog.cloudflare.com/another-look-at-pq-signatures/"><u>post-quantum certificates</u></a> are on the horizon, bringing with them larger signature and public key sizes. Today, a <a href="https://crt.sh/?id=17119212878"><u>certificate</u></a> with an P-256 ECDSA public key and issuer signature can be less than 1kB. Dropping in a ML-DSA<sub>44</sub> public key and signature brings the same certificate size to 4.6 kB, assuming the SCTs use 96-byte <a href="https://blog.cloudflare.com/another-look-at-pq-signatures/"><u>UOV</u><u><sub>ls-pkc</sub></u></a> signatures. With these choices, post-quantum certificates could require CT logs to store <b>4x</b> the amount of data per log entry.</p><p>The static CT API design helps to ensure that CT logs are much better equipped to handle this increased load, especially if the load is distributed across <a href="https://letsencrypt.org/2024/03/14/introducing-sunlight/#running-more-logs"><u>multiple logs</u></a> per operator. Our <a href="https://github.com/cloudflare/azul"><u>new implementation</u></a> makes it easy for log operators to run CT logs on top of Cloudflare’s infrastructure, adding more operational diversity and robustness to the CT ecosystem. We welcome feedback on the design and implementation as <a href="https://github.com/cloudflare/azul/issues"><u>GitHub issues</u></a>, and encourage CAs and other interested parties to start submitting to and consuming from our <a href="https://github.com/cloudflare/azul/tree/main/crates/ct_worker#test-logs"><u>test logs</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Research]]></category>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Transparency]]></category>
            <category><![CDATA[Certificate Transparency]]></category>
            <guid isPermaLink="false">5n88kLCWbpk22AmRzMQN9g</guid>
            <dc:creator>Luke Valenta</dc:creator>
        </item>
        <item>
            <title><![CDATA[Workers AI gets a speed boost, batch workload support, more LoRAs, new models, and a refreshed dashboard]]></title>
            <link>https://blog.cloudflare.com/workers-ai-improvements/</link>
            <pubDate>Fri, 11 Apr 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ We just made Workers AI inference faster with speculative decoding & prefix caching. Use our new batch inference for handling large request volumes seamlessly. ]]></description>
            <content:encoded><![CDATA[ <p>Since the <a href="https://blog.cloudflare.com/workers-ai/"><u>launch of Workers AI</u></a> in September 2023, our mission has been to make inference accessible to everyone.</p><p>Over the last few quarters, our Workers AI team has been heads down on improving the quality of our platform, working on various routing improvements, GPU optimizations, and capacity management improvements. Managing a distributed inference platform is not a simple task, but distributed systems are also what we do best. You’ll notice a recurring theme from all these announcements that has always been part of the core Cloudflare ethos — we try to solve problems through clever engineering so that we are able to do more with less.</p><p>Today, we’re excited to introduce speculative decoding to bring you faster inference, an asynchronous batch API for large workloads, and expanded LoRA support for more customized responses. Lastly, we’ll be recapping some of our newly added models, updated pricing, and unveiling a new dashboard to round out the usability of the platform.</p>
    <div>
      <h2>Speeding up inference by 2-4x with speculative decoding and more</h2>
      <a href="#speeding-up-inference-by-2-4x-with-speculative-decoding-and-more">
        
      </a>
    </div>
    <p>We’re excited to roll out speed improvements to models in our catalog, starting with the Llama 3.3 70b model. These improvements include speculative decoding, prefix caching, an updated inference backend, and more. We’ve previously done a technical deep dive on speculative decoding and how we’re making Workers AI faster, which <a href="https://blog.cloudflare.com/making-workers-ai-faster/"><u>you can read about here</u></a>. With these changes, we’ve been able to improve inference times by 2-4x, without any significant change to the quality of answers generated. We’re planning to incorporate these improvements into more models in the future as we release them. Today, we’re starting to roll out these changes so all Workers AI users of <code>@cf/meta/llama-3.3-70b-instruct-fp8-fast</code> will enjoy this automatic speed boost.</p>
    <div>
      <h3>What is speculative decoding?</h3>
      <a href="#what-is-speculative-decoding">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1Jc5CeeOpTW1LSZ7xeZumY/99ced72a25bdabea276f98c03bc17e27/image3.png" />
          </figure><p>The way LLMs work is by generating text by predicting the next token in a sentence given the previous tokens. Typically, an LLM is able to predict a single future token (n+1) with one forward pass through the model. These forward passes can be computationally expensive, since they need to work through all the parameters of a model to generate one token (e.g., 70 billion parameters for Llama 3.3 70b).</p><p>With speculative decoding, we put a small model (known as the draft model) in front of the original model that helps predict n+x future tokens. The draft model generates a subset of candidate tokens, and the original model just has to evaluate and confirm if they should be incorporated into the generation. Evaluating tokens is less computationally expensive, as the model can evaluate multiple tokens concurrently in a forward pass. As such, inference times can be sped up by 2-4x — meaning that users can get responses much faster.</p><p>What makes speculative decoding particularly efficient is that it’s able to use unused GPU compute left behind due to the GPU memory bottleneck LLMs create. Speculative decoding takes advantage of the unused compute by squeezing in a draft model to generate tokens faster. This means we’re able to improve the utilization of our GPUs by using them to their full extent without having parts of the GPU sit idle.</p>
    <div>
      <h3>What is prefix caching?</h3>
      <a href="#what-is-prefix-caching">
        
      </a>
    </div>
    <p>With LLMs, there are usually two stages of generation – the first is known as “pre-fill”, which processes the user’s input tokens such as the prompt and context. Prefix caching is aimed at reducing the pre-fill time of a request. As an example, if you were asking a model to generate code based on a given file, you might insert the whole file into the context window of a request. Then, if you want to make a second request to generate the next line of code, you might send us the whole file again in the second request. Prefix caching allows us to cache the pre-fill tokens so we don’t have to process the context twice. With the same example, we would only do the pre-fill stage once for both requests, rather than doing it per request. This method is especially useful for requests that reuse the same context, such as <a href="https://www.cloudflare.com/learning/ai/retrieval-augmented-generation-rag/"><u>Retrieval Augmented Generation (RAG)</u></a>, code generation, chatbots with memory, and more. Skipping the pre-fill stage for similar requests means faster responses for our users and more efficient usage of resources. </p>
    <div>
      <h3>How did you validate that quality is preserved through these optimizations?</h3>
      <a href="#how-did-you-validate-that-quality-is-preserved-through-these-optimizations">
        
      </a>
    </div>
    <p>Since this is an in-place update to an existing model, we were particularly cautious in ensuring that we would not break any existing applications with this update. We did extensive A/B testing through a blind arena with internal employees to validate the model quality, and we asked internal and external customers to test the new version of the model to ensure that response formats were compatible and model quality was acceptable. Our testing concluded that the model performed up to standards, with people being extremely excited about the speed of the model. Most LLMs are not perfectly deterministic even with the same set of inputs, but if you do notice something off, please let us know through <a href="https://discord.com/invite/cloudflaredev"><u>Discord</u></a> or <a href="http://x.com/cloudflaredev"><u>X</u></a>.</p>
    <div>
      <h2>Asynchronous batch API</h2>
      <a href="#asynchronous-batch-api">
        
      </a>
    </div>
    <p>Next up, we’re announcing an asynchronous (async) batch API which is helpful for users of large workloads. This feature allows customers to receive their inference responses asynchronously, with the promise that the inference will be completed at a later time rather than immediately erroring out due to capacity.</p><p>An example use case of batch workloads is people generating summaries of a large number of documents. You probably don’t need to use those summaries immediately, as you’ll likely use them once the whole document is complete versus one paragraph at a time. For these use cases, we’ve made it super simple for you to start sending us these requests in batches.</p>
    <div>
      <h3>Why batch requests?</h3>
      <a href="#why-batch-requests">
        
      </a>
    </div>
    <p>From talking to our customers, the most common use case we hear about is people creating embeddings or summarizing a large number of documents. Unfortunately, this is also one of the hardest use cases to manage capacity for as a serverless platform.</p><p>To illustrate this, imagine that you want to summarize a 70 page PDF. You typically chunk the document and then send an inference request for each chunk. If each chunk is a few paragraphs on a page, that means that we receive around 4 requests per page multiplied by 70 pages, which is about 280 requests. Multiply that by tens or hundreds of documents, and multiply that by a handful of concurrent users — this means that we get a sudden massive influx of thousands of requests when users start these large workloads.</p><p>The way we originally built Workers AI was to handle incoming requests as quickly as possible, assuming there's a human on the other side that needed an immediate response. The unique thing about batch workloads is that while they're not latency sensitive, they do require completeness guarantees — you don't want to come back the next day to realize none of your inference requests actually executed.</p><p>With the async API, you send us a batch of requests, and we promise to fulfill them as fast as possible and return them to you as a batch. This guarantees that your inference request will be fulfilled, rather than immediately (or eventually) erroring out. The async API also benefits users who have real-time use cases, as the model instances won’t be immediately consumed by these batch requests that can wait for a response. Inference times will be faster since there won’t be a bunch of competing requests in a queue waiting to reach the inference servers. </p><p>We have select models that support batch inference today, which include:</p><ul><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/"><u>@cf/meta/llama-3.3-70b-instruct-fp8-fast</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/bge-small-en-v1.5"><u>@cf/baai/bge-small-en-v1.5</u></a>, <a href="https://developers.cloudflare.com/workers-ai/models/bge-base-en-v1.5"><u>@cf/baai/bge-base-en-v1.5</u></a>, <a href="https://developers.cloudflare.com/workers-ai/models/bge-large-en-v1.5"><u>@cf/baai/bge-large-en-v1.5</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/bge-m3/"><u>@cf/baai/bge-m3</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/m2m100-1.2b/"><u>@cf/meta/m2m100-1.2b</u></a></p></li></ul>
    <div>
      <h3>How can I use the batch API?</h3>
      <a href="#how-can-i-use-the-batch-api">
        
      </a>
    </div>
    <p>Users can send a batch request to supported models by passing a flag:</p>
            <pre><code>let res = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-batch", {
  "requests": [{
    "prompt": "Explain mechanics of wormholes"
  }, {
    "prompt": "List different plant species found in America"
  }]
}, {
  queueRequest: true
});</code></pre>
            <p>Check out our <a href="https://developers.cloudflare.com/workers-ai/features/batch-api/"><u>developer docs</u></a> to learn more about the batch API, or use our <a href="https://github.com/craigsdennis/batch-please-workers-ai"><u>template</u></a> to deploy a worker that implements the batch API.</p><p>Today, our batch API can be used by sending us an array of requests, and we’ll return your responses in an array.  This is helpful for use cases like summarizing large amounts of data that you know beforehand. This means you can send us a single HTTP request with all of your requests, and receive a single HTTP request back with your responses. You can check on the status of the batch by polling it with the request ID we return when your batch is submitted. For the next iteration of our async API, we plan to allow queue-based inputs and outputs, where you push requests and pull responses from a queue. This will integrate tightly with <a href="https://developers.cloudflare.com/r2/buckets/event-notifications/"><u>Event Notifications</u></a> and <a href="https://developers.cloudflare.com/workflows/"><u>Workflows</u></a>, so you can execute subsequent actions upon receiving a response.</p>
    <div>
      <h2>Expanded LoRA support</h2>
      <a href="#expanded-lora-support">
        
      </a>
    </div>
    <p>At Birthday Week last year, <a href="https://blog.cloudflare.com/workers-ai-ga-huggingface-loras-python-support/#supporting-fine-tuned-inference-byo-loras"><u>we announced limited LoRA suppor</u></a>t for a handful of models. We’ve </p><p>iterated on this and now support 8 models as well as larger ranks of up to 32 and LoRA files up to 300 MB. Models that support LoRA inference now include:</p><ul><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.2-11b-vision-instruct/"><u>@cf/meta/llama-3.2-11b-vision-instruct</u></a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/"><u>@cf/meta/llama-3.3-70b-instruct-fp8-fast</u></a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-guard-3-8b/"><u>@cf/meta/llama-guard-3-8b</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.1-8b-instruct-fast/"><u>@cf/meta/llama-3.1-8b-instruct-fast</u></a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/deepseek-r1-distill-qwen-32b/"><u>@cf/deepseek-ai/deepseek-r1-distill-qwen-32b</u></a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/qwen2.5-coder-32b-instruct"><u>@cf/qwen/qwen2.5-coder-32b-instruct</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/qwq-32b"><u>@cf/qwen/qwq-32b</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/mistral-small-3.1-24b-instruct"><u>@cf/mistralai/mistral-small-3.1-24b-instruct</u></a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/gemma-3-12b-it"><u>@cf/google/gemma-3-12b-it</u></a> (soon)</p></li></ul>
    <div>
      <h3>What is LoRA?</h3>
      <a href="#what-is-lora">
        
      </a>
    </div>
    <p>In essence, a Low Rank Adaptation (LoRA) adapter allows people to take a trained adapter file and use it in conjunction with a model to alter the response of a model. We did a<a href="https://blog.cloudflare.com/fine-tuned-inference-with-loras/"><u> deep dive on LoRAs</u></a> in our Birthday Week blog post, which goes into further technical detail. LoRA adapters are great alternatives to fine-tuning a model, as it isn’t as expensive to train and adapters are much smaller and more portable. They are also effective enough to tweak the output of a model to fit a certain style of response.</p>
    <div>
      <h3>How do I get started?</h3>
      <a href="#how-do-i-get-started">
        
      </a>
    </div>
    <p>To get started, you first need to train your own LoRA adapter or find a public one on HuggingFace. Then, you’ll upload the <code>adapter_model.safetensors</code> and <code>adapter_config.json</code> to your account with the <a href="https://developers.cloudflare.com/workers-ai/fine-tunes/loras/"><u>documented wrangler commands or through the REST API</u></a>. LoRA files are private and scoped to your own account. After that, you can start running fine-tuned inference — check out our <a href="https://developers.cloudflare.com/workers-ai/features/fine-tunes/loras/"><u>LoRA developer docs</u></a> to get started.</p>
            <pre><code>const response = await env.AI.run(
  "@cf/qwen/qwen2.5-coder-32b-instruct", //the model supporting LoRAs
  {
      messages: [{"role": "user", "content": "Hello world"}],
      raw: true, //skip applying the default chat template
      lora: "00000000-0000-0000-0000-000000000", //the finetune id OR finetune name
  }
);</code></pre>
            
    <div>
      <h2>Quality of life improvements: updated pricing and a new dashboard for Workers AI</h2>
      <a href="#quality-of-life-improvements-updated-pricing-and-a-new-dashboard-for-workers-ai">
        
      </a>
    </div>
    <p>While the team has been focused on large engineering milestones, we’ve also landed some quality of life improvements over the last few months. In case you missed it, we’ve announced <a href="https://developers.cloudflare.com/changelog/2025-02-20-updated-pricing-docs/"><u>an updated pricing model</u></a> where usage will be shown in units such as tokens, audio seconds, image size/steps, etc. but still billed in neurons in the backend.</p><p>Today, we’re unveiling a new dashboard that allows users to see their usage in both units as well as neurons (built on <a href="https://blog.cloudflare.com/introducing-workers-observability-logs-metrics-and-queries-all-in-one-place/"><u>new Workers Observabilit</u></a>y components!). Model pricing is also available via dashboard and developer docs on the models page. And if you use AI Gateway, Workers AI usage will also be displayed as metrics now.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2ABZi7EC8dedCY4ru0ffsA/8eb63a9f4a626ca70ce6101760d23900/image1.png" />
          </figure>
    <div>
      <h2>New models available in Workers AI</h2>
      <a href="#new-models-available-in-workers-ai">
        
      </a>
    </div>
    <p>Lastly, we’ve steadily been adding new models on Workers AI, with over 10 new models and a few updates on existing models. Pricing is also now listed directly on the model page in the developer docs. To summarize, here are the new models we’ve added on Workers AI, including four new ones we’re releasing today:</p><ul><li><p><a href="https://developers.cloudflare.com/workers-ai/models/deepseek-r1-distill-qwen-32b/"><u>@cf/deepseek-ai/deepseek-r1-distill-qwen-32b</u></a>: a version of Qwen 32B distilled from Deepseek’s R1 that is capable of doing chain-of-thought reasoning.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/bge-m3/"><u>@cf/baai/bge-m3</u></a>: a multi-lingual embeddings model that supports over 100 languages. It can also simultaneously perform dense retrieval, multi-vector retrieval, and sparse retrieval, with the ability to process inputs of different granularities.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/bge-reranker-base/"><u>@cf/baai/bge-reranker-base</u></a>: our first reranker model! Rerankers are a type of text classification model that takes a query and context, and outputs a similarity score between the two. When used in RAG systems, you can use a reranker after the initial vector search to find the most relevant documents to return to a user by reranking the outputs.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/whisper-large-v3-turbo/"><u>@cf/openai/whisper-large-v3-turbo</u></a>: a faster, more accurate speech-to-text model. This model was added earlier but is graduating out of beta with pricing included today.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/melotts/"><u>@cf/myshell-ai/melotts</u></a>: our first text-to-speech model that allows users to generate an MP3 with voice audio from text input.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-4-scout-17b-16e-instruct/"><u>@cf/meta/llama-4-scout-17b-16e-instruct</u></a>: 17 billion parameter MoE model with 16 experts that is natively multimodal. Offers industry-leading performance in text and image understanding.</p></li><li><p>[NEW] <a href="https://developers.cloudflare.com/workers-ai/models/mistral-small-3.1-24b-instruct"><u>@cf/mistralai/mistral-small-3.1-24b-instruct</u></a>: a 24B parameter model achieving state-of-the-art capabilities comparable to larger models, with support for vision and tool calling.</p></li><li><p>[NEW] <a href="https://developers.cloudflare.com/workers-ai/models/gemma-3-12b-it"><u>@cf/google/gemma-3-12b-it</u></a>: well-suited for a variety of text generation and image understanding tasks, including question answering, summarization and reasoning, with a 128K context window, and multilingual support in over 140 languages.</p></li><li><p>[NEW] <a href="https://developers.cloudflare.com/workers-ai/models/qwq-32b"><u>@cf/qwen/qwq-32b</u></a>: a medium-sized reasoning model, which is capable of achieving competitive performance against state-of-the-art reasoning models, e.g., DeepSeek-R1, o1-mini.</p></li><li><p>[NEW] <a href="https://developers.cloudflare.com/workers-ai/models/qwen2.5-coder-32b-instruct"><u>@cf/qwen/qwen2.5-coder-32b-instruct</u></a>: the current state-of-the-art open-source code LLM, with its coding abilities matching those of GPT-4o.</p></li></ul><p>In addition, we are rolling out some in-place updates to existing models in our catalog:</p><ul><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/"><u>@cf/meta/llama-3.3-70b-instruct-fp8-fast</u></a> - Llama 3.3 70b gets a speed boost with new techniques such as speculative decoding, prefix caching, and an updated server back end (<a href="#speeding-up-inference-by-2-4x-with-speculative-decoding-and-more"><u>see above</u></a>).</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/bge-small-en-v1.5"><u>@cf/baai/bge-small-en-v1.5</u></a>, <a href="https://developers.cloudflare.com/workers-ai/models/bge-base-en-v1.5"><u>@cf/baai/bge-base-en-v1.5</u></a>, <a href="https://developers.cloudflare.com/workers-ai/models/bge-large-en-v1.5"><u>@cf/baai/bge-large-en-v1.5</u></a> - get a new input parameter called “pooling” which takes either “cls” or “mean”</p></li></ul><p>As we release these new models, we’ll be deprecating old models to encourage use of the state-of-the-art models and make room in our catalog. We will send out an email notice on this shortly. Stay up to date with our model releases and deprecation announcements by <a href="https://developers.cloudflare.com/changelog/"><u>subscribing to our Developer Docs changelog</u></a>.</p>
    <div>
      <h2>We’re (still) just getting started</h2>
      <a href="#were-still-just-getting-started">
        
      </a>
    </div>
    <p>Workers AI is one of Cloudflare’s newer products in a nascent industry, but we still operate with very traditional Cloudflare principles – learning how we can do more with less. Our engineering team is focused on solving the difficult problems that come with growing a distributed inference platform at a global scale, and we’re excited to release these new features today that we think will improve the platform as a whole for all our users. With faster inference times, better reliability, more customization possibilities, and better usability, we’re excited to see what you can do with more Workers AI — <a href="https://discord.com/invite/cloudflaredev"><u>let us know what you think</u></a>!</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">5iJwjQcUANzpsgir2tgfNE</guid>
            <dc:creator>Michelle Chen</dc:creator>
            <dc:creator>Jesse Kipp</dc:creator>
        </item>
        <item>
            <title><![CDATA[Making Super Slurper 5x faster with Workers, Durable Objects, and Queues]]></title>
            <link>https://blog.cloudflare.com/making-super-slurper-five-times-faster/</link>
            <pubDate>Thu, 10 Apr 2025 14:05:00 GMT</pubDate>
            <description><![CDATA[ We re-architected Super Slurper from the ground up using our Developer Platform — leveraging Cloudflare Workers, Durable Objects, and Queues — and improved transfer speeds by up to 5x. ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://developers.cloudflare.com/r2/data-migration/super-slurper/"><u>Super Slurper</u></a> is Cloudflare’s data migration tool that is designed to make large-scale data transfers between cloud object storage providers and <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>Cloudflare R2</u></a> easy. Since its launch, thousands of developers have used Super Slurper to move petabytes of data from AWS S3, Google Cloud Storage, and other <a href="https://www.cloudflare.com/developer-platform/solutions/s3-compatible-object-storage/">S3-compatible services</a> to R2.</p><p>But we saw an opportunity to make it even faster. We rearchitected Super Slurper from the ground up using our Developer Platform — building on <a href="https://developers.cloudflare.com/workers/"><u>Cloudflare Workers</u></a>, <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, and <a href="https://developers.cloudflare.com/queues/"><u>Queues</u></a> — and improved transfer speeds by up to 5x. In this post, we’ll dive into the original architecture, the performance bottlenecks we identified, how we solved them, and the real-world impact of these improvements.</p>
    <div>
      <h2>Initial architecture and performance bottlenecks</h2>
      <a href="#initial-architecture-and-performance-bottlenecks">
        
      </a>
    </div>
    <p>Super Slurper originally shared its architecture with <a href="https://developers.cloudflare.com/images/upload-images/sourcing-kit/"><u>SourcingKit</u></a>, a tool built to bulk import images from AWS S3 into <a href="https://developers.cloudflare.com/images/"><u>Cloudflare Images</u></a>. SourcingKit was deployed on Kubernetes and ran alongside the <a href="https://developers.cloudflare.com/images/"><u>Images</u></a> service. When we started building Super Slurper, we split it into its own Kubernetes namespace and introduced a few new APIs to make it easier to use for the object storage use case. This setup worked well and helped thousands of developers move data to R2.</p><p>However, it wasn’t without its challenges. SourcingKit wasn’t designed to handle the scale required for large, petabytes-scale transfers. SourcingKit, and by extension Super Slurper, operated on Kubernetes clusters located in one of our core data centers, meaning it had to share compute resources and bandwidth with Cloudflare’s control plane, analytics, and other services. As the number of migrations grew, these resource constraints became a clear bottleneck.</p><p>For a service transferring data between object storage providers, the job is simple: list objects from the source, copy them to the destination, and repeat. This is exactly how the original Super Slurper worked. We listed objects from the source bucket, pushed that list to a Postgres-based queue (<code>pg_queue</code>), and then pulled from this queue at a steady pace to copy objects over. Given the scale of object storage migrations, bandwidth usage was inevitably going to be high. This made it challenging to scale.</p><p>To address the bandwidth constraints operating solely in our core data center, we introduced <a href="https://developers.cloudflare.com/workers/"><u>Cloudflare Workers</u></a> into the mix. Instead of handling the copying of data in our core data center, we started calling out to a Worker to do the actual copying:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1EgtILMnu88y3VzUvYLlPl/479e2f99a62155f7bd8047f98a2a9cd2/1_.png" />
          </figure><p>As Super Slurper’s usage grew, so did our Kubernetes resource consumption. A significant amount of time during data transfers was spent waiting on network I/O or storage, and not actually doing compute-intensive tasks. So we didn’t need more memory or more CPU, we needed more concurrency.</p><p>To keep up with demand, we kept increasing the replica count. But eventually, we hit a wall. We were dealing with scalability challenges when running on the order of tens of pods when we wanted multiple orders of magnitude more.</p><p>We decided to rethink the entire approach from first principles, instead of leaning on the architecture we had inherited. In about a week, we built a rough proof of concept using <a href="https://developers.cloudflare.com/workers/"><u>Cloudflare Workers</u></a>, <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, and <a href="https://developers.cloudflare.com/queues/"><u>Queues</u></a>. We listed objects from the source bucket, pushed them into a queue, and then consumed messages from the queue to initiate transfers. Although this sounds very similar to what we did in the original implementation, building on our Developer Platform allowed us to automatically scale an order of magnitude higher than before.</p><ul><li><p><b>Cloudflare Queues</b>: Enables asynchronous object transfers and auto-scales to meet the number of objects being migrated.</p></li><li><p><b>Cloudflare Workers</b>: Runs lightweight compute tasks without the overhead of Kubernetes and optimizes where in the world each part of the process runs<b> </b>for lower latency and better performance.</p></li><li><p><b>SQLite-backed Durable Objects (DOs)</b>: Acts as a fully distributed database, eliminating the limitations of a single PostgreSQL instance.</p></li><li><p><b>Hyperdrive</b>: Provides fast access to historical job data from the original PostgreSQL database, keeping it as an archive store.</p></li></ul><p>We ran a few tests and found that our proof of concept was slower than the original implementation for small transfers (a few hundred objects), but it matched and eventually exceeded the performance of the original as transfers scaled into the millions of objects. That was the signal we needed to invest the time to take our proof of concept to production.</p><p>We removed our proof of concept hacks, worked on stability, and found new ways to make transfers scale to even higher concurrency. After a few iterations, we landed on something we were happy with.</p>
    <div>
      <h2>New architecture: Workers, Queues, and Durable Objects</h2>
      <a href="#new-architecture-workers-queues-and-durable-objects">
        
      </a>
    </div>
    
    <div>
      <h4>Processing layer: managing the flow of migration</h4>
      <a href="#processing-layer-managing-the-flow-of-migration">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/ieLgJoWErEYEEa90QaXLC/81470021a99486a974753301d2d2f809/2.png" />
          </figure><p>At the heart of our processing layer are <b>queues, consumers, and workers</b>. Here’s what the process looks like:</p>
    <div>
      <h4>Kicking off a migration</h4>
      <a href="#kicking-off-a-migration">
        
      </a>
    </div>
    <p>When a client triggers a migration, it starts with a request sent to our <b>API Worker</b>. This worker takes the details of the migration, stores them in the database, and adds a message to the <b>List Queue</b> to start the process.</p>
    <div>
      <h4>Listing source bucket objects</h4>
      <a href="#listing-source-bucket-objects">
        
      </a>
    </div>
    <p>The <b>List Queue Consumer</b> is where things start to pick up. It pulls messages from the queue, retrieves object listings from the source bucket, applies any necessary filters, and stores important metadata in the database. Then, it creates new tasks by enqueuing object transfer messages into the <b>Transfer Queue</b>.</p><p>We immediately queue new batches of work, maximizing concurrency. A built-in throttling mechanism prevents us from adding more messages to our queues when unexpected failures occur, such as dependent systems going down. This helps maintain stability and prevents overload during disruptions.</p>
    <div>
      <h4>Efficient object transfers</h4>
      <a href="#efficient-object-transfers">
        
      </a>
    </div>
    <p>The <b>Transfer Queue Consumer</b> Workers pull object transfer messages from the queue, ensuring that each object is processed only once by locking the object key in the database. When the transfer finishes, the object is unlocked. For larger objects, we break them into manageable chunks and transfer them as multipart uploads.</p>
    <div>
      <h4>Handling failures gracefully</h4>
      <a href="#handling-failures-gracefully">
        
      </a>
    </div>
    <p>Failures are inevitable in any distributed system, and we had to make sure we accounted for that. We implemented automatic retries for transient failures, so issues don’t interrupt the flow of the migration. But if something can’t be resolved with retries, the message goes into the <b>Dead Letter Queue (DLQ)</b>, where it is logged for later review and resolution.</p>
    <div>
      <h4>Job completion &amp; lifecycle management</h4>
      <a href="#job-completion-lifecycle-management">
        
      </a>
    </div>
    <p>Once all the objects are listed and the transfers are in progress, the <b>Lifecycle Queue Consumer</b> keeps an eye on everything. It monitors the ongoing transfers, ensuring that no object is left behind. When all the transfers are complete, the job is marked as finished and the migration process wraps up.</p>
    <div>
      <h3>Database layer: durable storage &amp; legacy data retrieval</h3>
      <a href="#database-layer-durable-storage-legacy-data-retrieval">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4OhENQndBrRkVLNmWQ4mWP/815173a64ec1943b7b626b02247d4887/3.png" />
          </figure><p>When building our new architecture, we knew we needed a robust solution to handle massive datasets while ensuring retrieval of historical job data. That's where our combination of <b>Durable Objects (DOs)</b> and <b>Hyperdrive</b> came in.</p>
    <div>
      <h4>Durable Objects</h4>
      <a href="#durable-objects">
        
      </a>
    </div>
    <p>We gave each account a dedicated Durable Object to track migration jobs. Each <b>job’s DO</b> stores vital details, such as bucket names, user options, and job state. This ensured everything stayed organized and easy to manage. To support large migrations, we also added a <b>Batch DO</b> that manages all the objects queued for transfer, storing their transfer state, object keys, and any extra metadata.</p><p>As migrations scaled up to <b>billions of objects</b>, we had to get creative with storage. We implemented a sharding strategy to distribute request loads, preventing bottlenecks and working around <b>SQLite DO’s 10 GB</b> storage limit. As objects are transferred, we clean up their details, optimizing storage space along the way. It’s surprising how much storage a billion object keys can require!</p>
    <div>
      <h4>Hyperdrive</h4>
      <a href="#hyperdrive">
        
      </a>
    </div>
    <p>Since we were rebuilding a system with years of migration history, we needed a way to preserve and access every past migration detail. Hyperdrive serves as a bridge to our legacy systems, enabling seamless retrieval of historical job data from our core <b>PostgreSQL</b> database. It's not just a data retrieval mechanism, but an archive for complex migration scenarios.</p>
    <div>
      <h2>Results: Super Slurper now transfers data to R2 up to 5x faster</h2>
      <a href="#results-super-slurper-now-transfers-data-to-r2-up-to-5x-faster">
        
      </a>
    </div>
    <p>So, after all of that, did we actually achieve our goal of making transfers faster?</p><p>We ran a test migration of 75,000 objects from AWS S3 to R2. With the original implementation, the transfer took 15 minutes and 30 seconds. After our performance improvements, the same migration completed in just 3 minutes and 25 seconds.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/57Pmt9tVNGYWvmRQQyvYE9/43443656bc81743485c3bb0f7d65b134/4.png" />
          </figure><p>When production migrations started using the new service in February, we saw even greater improvements in some cases, especially depending on the distribution of object sizes. Super Slurper has been around <a href="https://blog.cloudflare.com/r2-super-slurper-ga/"><u>for about two years</u></a>. But the improved performance has led to it being able to move much more data — 35% of all objects copied by Super Slurper happened just in the last two months.</p>
    <div>
      <h2>Challenges</h2>
      <a href="#challenges">
        
      </a>
    </div>
    <p>One of the biggest challenges we faced with the new architecture was handling duplicate messages. There were a couple of ways duplicates could occur:</p><ul><li><p>Queues provides at-least-once delivery, which means consumers may receive the same message more than once to guarantee delivery.</p></li><li><p>Failures and retries could also create apparent duplicates. For example, if a request to a Durable Object fails after the object has already been transferred, the retry could reprocess the same object.</p></li></ul><p>If not handled correctly, this could result in the same object being transferred multiple times. To solve this, we implemented several strategies to ensure each object was accurately accounted for and only transferred once:</p><ol><li><p>Since listing is sequential (e.g., to get object 2, you need the continuation token from listing object 1), we assign a sequence ID to each listing operation. This allows us to detect duplicate listings and prevent multiple processes from starting simultaneously. This is particularly useful because we don’t wait for database and queue operations to complete before listing the next batch. If listing 2 fails, we can retry it, and if listing 3 has already started, we can short-circuit unnecessary retries.</p></li><li><p>Each object is locked when its transfer begins, preventing parallel transfers of the same object. Once successfully transferred, the object is unlocked by deleting its key from the database. If a message for that object reappears later, we can safely assume it has already been transferred if the key no longer exists.</p></li><li><p>We rely on database transactions to keep our counts accurate. If an object fails to unlock, its count remains unchanged. Similarly, if an object key fails to be added to the database, the count isn’t updated, and the operation will be retried later.</p></li><li><p>As a last failsafe, we check whether the object already exists in the target bucket and was published after the start of our migration. If so, we assume it was transferred by our process (or another) and safely skip it.</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/17zkULEDjrPDlG6mNIpomw/5c95bde32595daf0684a558729ee055a/5.png" />
          </figure>
    <div>
      <h2>What’s next for Super Slurper?</h2>
      <a href="#whats-next-for-super-slurper">
        
      </a>
    </div>
    <p>We’re always exploring ways to make Super Slurper faster, more scalable, and even easier to use — this is just the beginning.</p><ul><li><p>We recently launched the ability to migrate from any <a href="https://developers.cloudflare.com/changelog/2025-02-24-r2-super-slurper-s3-compatible-support/"><u>S3 compatible storage provider</u></a>!</p></li><li><p>Data migrations are still currently limited to 3 concurrent migrations per account, but we want to increase that limit. This will allow object prefixes to be split up into separate migrations and run in parallel, drastically increasing the speed at which a bucket can be migrated. For more information on Super Slurper and how to migrate data from existing object storage to R2, refer to our <a href="https://developers.cloudflare.com/r2/data-migration/super-slurper/"><u>documentation</u></a>.</p></li></ul><p>P.S. As part of this update, we made the API much simpler to interact with, so migrations can now be <a href="https://developers.cloudflare.com/api/resources/r2/subresources/super_slurper/"><u>managed programmatically</u></a>!</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[R2 Super Slurper]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Durable Objects]]></category>
            <category><![CDATA[Cloudflare Queues]]></category>
            <category><![CDATA[Queues]]></category>
            <category><![CDATA[R2]]></category>
            <guid isPermaLink="false">12YmRoxQrsnW1ZVtEKBdht</guid>
            <dc:creator>Connor Maddox</dc:creator>
            <dc:creator>Siddhant Sinha</dc:creator>
            <dc:creator>Prasanna Sai Puvvada</dc:creator>
        </item>
        <item>
            <title><![CDATA[R2 Data Catalog: Managed Apache Iceberg tables with zero egress fees]]></title>
            <link>https://blog.cloudflare.com/r2-data-catalog-public-beta/</link>
            <pubDate>Thu, 10 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ R2 Data Catalog is now in public beta: a managed Apache Iceberg data catalog built directly into your R2 bucket. ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://iceberg.apache.org/"><u>Apache Iceberg</u></a> is quickly becoming the standard table format for querying large analytic datasets in <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a>. We’re seeing this trend firsthand as more and more developers and data teams adopt Iceberg on <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>Cloudflare R2</u></a>. But until now, using Iceberg with R2 meant managing additional infrastructure or relying on external data catalogs.</p><p>So we’re fixing this. Today, we’re launching the <a href="https://developers.cloudflare.com/r2/data-catalog/"><u>R2 Data Catalog</u></a> in open beta, a managed Apache Iceberg catalog built directly into your Cloudflare R2 bucket.</p><p>If you’re not already familiar with it, Iceberg is an open table format built for large-scale analytics on datasets stored in object storage. With R2 Data Catalog, you get the database-like capabilities Iceberg is known for – <a href="https://en.wikipedia.org/wiki/ACID"><u>ACID</u></a> transactions, schema evolution, and efficient querying – without the overhead of managing your own external catalog.</p><p>R2 Data Catalog exposes a standard Iceberg REST catalog interface, so you can connect the engines you already use, like <a href="https://py.iceberg.apache.org/"><u>PyIceberg</u></a>, <a href="https://www.snowflake.com/"><u>Snowflake</u></a>, and <a href="https://spark.apache.org/"><u>Spark</u></a>. And, as always with R2, there are no egress fees, meaning that no matter which cloud or region your data is consumed from, you won’t have to worry about growing data transfer costs.</p><p>Ready to query data in R2 right now? Jump into the <a href="https://developers.cloudflare.com/r2/data-catalog/"><u>developer docs</u></a> and enable a data catalog on your R2 bucket in just a few clicks. Or keep reading to learn more about Iceberg, data catalogs, how metadata files work under the hood, and how to create your first Iceberg table.</p>
    <div>
      <h2>What is Apache Iceberg?</h2>
      <a href="#what-is-apache-iceberg">
        
      </a>
    </div>
    <p><a href="https://iceberg.apache.org/"><u>Apache Iceberg</u></a> is an open table format for analyzing large datasets in object storage. It brings database-like features – ACID transactions, time travel, and schema evolution – to files stored in formats like <a href="https://parquet.apache.org/"><u>Parquet</u></a> or <a href="https://orc.apache.org/"><u>ORC</u></a>.</p><p>Historically, data lakes were just collections of raw files in object storage. However, without a unified metadata layer, datasets could easily become corrupted, were difficult to evolve, and queries often required expensive full-table scans.</p><p>Iceberg solves these problems by:</p><ul><li><p>Providing ACID transactions for reliable, concurrent reads and writes.</p></li><li><p>Maintaining optimized metadata, so engines can skip irrelevant files and avoid unnecessary full-table scans.</p></li><li><p>Supporting schema evolution, allowing columns to be added, renamed, or dropped without rewriting existing data.</p></li></ul><p>Iceberg is already <a href="https://iceberg.apache.org/vendors/"><u>widely supported</u></a> by engines like Apache Spark, Trino, Snowflake, DuckDB, and ClickHouse, with a fast-growing community behind it.</p>
    <div>
      <h3>How Iceberg tables are stored</h3>
      <a href="#how-iceberg-tables-are-stored">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/779M4zsH5QnpDwlTORk1fo/38e7732ca0e20645507bdc0c628f671b/1.png" />
          </figure><p>Internally, an Iceberg table is a collection of data files (typically stored in columnar formats like Parquet or ORC) and metadata files (typically stored in JSON or <a href="https://avro.apache.org/"><u>Avro</u></a>) that describe table snapshots, schemas, and partition layouts.</p><p>To understand how query engines interact efficiently with Iceberg tables, it helps to look at an Iceberg metadata file (simplified):</p>
            <pre><code>{
  "format-version": 2,
  "table-uuid": "0195e49b-8f7c-7933-8b43-d2902c72720a",
  "location": "s3://my-bucket/warehouse/0195e49b-79ca/table",
  "current-schema-id": 0,
  "schemas": [
    {
      "schema-id": 0,
      "type": "struct",
      "fields": [
        { "id": 1, "name": "id", "required": false, "type": "long" },
        { "id": 2, "name": "data", "required": false, "type": "string" }
      ]
    }
  ],
  "current-snapshot-id": 3567362634015106507,
  "snapshots": [
    {
      "snapshot-id": 3567362634015106507,
      "sequence-number": 1,
      "timestamp-ms": 1743297158403,
      "manifest-list": "s3://my-bucket/warehouse/0195e49b-79ca/table/metadata/snap-3567362634015106507-0.avro",
      "summary": {},
      "schema-id": 0
    }
  ],
  "partition-specs": [{ "spec-id": 0, "fields": [] }]
}</code></pre>
            <p>A few of the important components are:</p><ul><li><p><code>schemas</code>: Iceberg tracks schema changes over time. Engines use schema information to safely read and write data without needing to rewrite underlying files.</p></li><li><p><code>snapshots</code>: Each snapshot references a specific set of data files that represent the state of the table at a point in time. This enables features like time travel.</p></li><li><p><code>partition-specs</code>: These define how the table is logically partitioned. Query engines leverage this information during planning to skip unnecessary partitions, greatly improving query performance.</p></li></ul><p>By reading Iceberg metadata, query engines can efficiently prune partitions, load only the relevant snapshots, and fetch only the data files it needs, resulting in faster queries.</p>
    <div>
      <h3>Why do you need a data catalog?</h3>
      <a href="#why-do-you-need-a-data-catalog">
        
      </a>
    </div>
    <p>Although the Iceberg data and metadata files themselves live directly in object storage (like <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>), the list of tables and pointers to the current metadata need to be tracked centrally by a data catalog.</p><p>Think of a data catalog as a library's index system. While books (your data) are physically distributed across shelves (object storage), the index provides a single source of truth about what books exist, their locations, and their latest editions. Without this index, readers (query engines) would waste time searching for books, might access outdated versions, or could accidentally shelve new books in ways that make them unfindable.</p><p>Similarly, data catalogs ensure consistent, coordinated access, allowing multiple query engines to safely read from and write to the same tables without conflicts or data corruption.</p>
    <div>
      <h2>Create your first Iceberg table on R2</h2>
      <a href="#create-your-first-iceberg-table-on-r2">
        
      </a>
    </div>
    <p>Ready to try it out? Here’s a quick example using <a href="https://py.iceberg.apache.org/"><u>PyIceberg</u></a> and Python to get you started. For a detailed step-by-step guide, check out our <a href="https://developers.cloudflare.com/r2/data-catalog/get-started/"><u>developer docs</u></a>.</p><p>1. Enable R2 Data Catalog on your bucket:
</p>
            <pre><code>npx wrangler r2 bucket catalog enable my-bucket</code></pre>
            <p>Or use the Cloudflare dashboard: Navigate to <b>R2 Object Storage</b> &gt; <b>Settings</b> &gt; <b>R2 Data Catalog</b> and click <b>Enable</b>.</p><p>2. Create a <a href="https://developers.cloudflare.com/r2/api/s3/tokens/"><u>Cloudflare API token</u></a> with permissions for both R2 storage and the data catalog.</p><p>3. Install <a href="https://py.iceberg.apache.org/"><u>PyIceberg</u></a> and <a href="https://arrow.apache.org/docs/index.html"><u>PyArrow</u></a>, then open a Python shell or notebook:</p>
            <pre><code>pip install pyiceberg pyarrow</code></pre>
            <p>4. Connect to the catalog and create a table:</p>
            <pre><code>import pyarrow as pa
from pyiceberg.catalog.rest import RestCatalog

# Define catalog connection details (replace variables)
WAREHOUSE = "&lt;WAREHOUSE&gt;"
TOKEN = "&lt;TOKEN&gt;"
CATALOG_URI = "&lt;CATALOG_URI&gt;"

# Connect to R2 Data Catalog
catalog = RestCatalog(
    name="my_catalog",
    warehouse=WAREHOUSE,
    uri=CATALOG_URI,
    token=TOKEN,
)

# Create default namespace
catalog.create_namespace("default")

# Create simple PyArrow table
df = pa.table({
    "id": [1, 2, 3],
    "name": ["Alice", "Bob", "Charlie"],
})

# Create an Iceberg table
table = catalog.create_table(
    ("default", "my_table"),
    schema=df.schema,
)</code></pre>
            <p>You can now append more data or run queries, just as you would with any Apache Iceberg table.</p>
    <div>
      <h2>Pricing</h2>
      <a href="#pricing">
        
      </a>
    </div>
    <p>While R2 Data Catalog is in open beta, there will be no additional charges beyond standard R2 storage and operations costs incurred by query engines accessing data. <a href="https://r2-calculator.cloudflare.com/"><u>Storage pricing</u></a> for buckets with R2 Data Catalog enabled remains the same as standard R2 buckets – \$0.015 per GB-month. As always, egress directly from R2 buckets remains \$0.</p><p>In the future, we plan to introduce pricing for catalog operations (e.g., creating tables, retrieving table metadata, etc.) and data compaction.</p><p>Below is our current thinking on future pricing. We’ll communicate more details around timing well before billing begins, so you can confidently plan your workloads.</p><div>
    <figure>
        <table>
            <colgroup>
                <col></col>
                <col></col>
            </colgroup>
            <tbody>
                <tr>
                    <td> </td>
                    <td>
                        <p><span><span><strong>Pricing</strong></span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>R2 storage</span></span></p>
                        <p><span><span>For standard storage class</span></span></p>
                    </td>
                    <td>
                        <p><span><span>$0.015 per GB-month (no change)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>R2 Class A operations</span></span></p>
                    </td>
                    <td>
                        <p><span><span>$4.50 per million operations (no change)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>R2 Class B operations</span></span></p>
                    </td>
                    <td>
                        <p><span><span>$0.36 per million operations (no change)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Data Catalog operations</span></span></p>
                        <p><span><span>e.g., create table, get table metadata, update table properties</span></span></p>
                    </td>
                    <td>
                        <p><span><span>$9.00 per million catalog operations</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Data Catalog compaction data processed</span></span></p>
                    </td>
                    <td>
                        <p><span><span>$0.05 per GB processed</span></span></p>
                        <p><span><span>$4.00 per million objects processed</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Data egress</span></span></p>
                    </td>
                    <td>
                        <p><span><span>$0 (no change, always free)</span></span></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </figure>
</div>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We’re excited to see how you use R2 Data Catalog! If you’ve never worked with Iceberg – or even analytics data – before, we think this is the easiest way to get started.</p><p>Next on our roadmap is tackling compaction and table optimization. Query engines typically perform better when dealing with fewer, but larger data files. We will automatically re-write collections of small data files into larger files to deliver even faster query performance. </p><p>We’re also collaborating with the broad Apache Iceberg community to expand query-engine compatibility with the Iceberg REST Catalog spec.</p><p>We’d love your feedback. Join the <a href="https://discord.cloudflare.com/"><u>Cloudflare Developer Discord</u></a> to ask questions and share your thoughts during the public beta. For more details, examples, and guides, visit our <a href="https://developers.cloudflare.com/r2/data-catalog/get-started/"><u>developer documentation</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[R2]]></category>
            <category><![CDATA[Data Catalog]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">6JFB9cHUOoMZnVmYIuTLzd</guid>
            <dc:creator>Phillip Jones</dc:creator>
            <dc:creator>Garvit Gupta</dc:creator>
            <dc:creator>Alex Graham</dc:creator>
            <dc:creator>Garrett Gu</dc:creator>
        </item>
        <item>
            <title><![CDATA[Sequential consistency without borders: how D1 implements global read replication]]></title>
            <link>https://blog.cloudflare.com/d1-read-replication-beta/</link>
            <pubDate>Thu, 10 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ D1, Cloudflare’s managed SQL database, announces read replication beta. Here's a deep dive of the read replication implementation and how your queries can remain consistent across all regions. ]]></description>
            <content:encoded><![CDATA[ <p>Read replication of <a href="https://www.cloudflare.com/developer-platform/products/d1/">D1 databases</a> is in public beta!</p><p>D1 read replication makes read-only copies of your database available in multiple regions across Cloudflare’s network.  For busy, read-heavy applications like e-commerce websites, content management tools, and mobile apps:</p><ul><li><p>D1 read replication lowers average latency by routing user requests to read replicas in nearby regions.</p></li><li><p>D1 read replication increases overall throughput by offloading read queries to read replicas, allowing the primary database to handle more write queries.</p></li></ul><p>The main copy of your database is called the primary database and the read-only copies are called read replicas.  When you enable replication for a D1 database, the D1 service automatically creates and maintains read replicas of your primary database.  As your users make requests, D1 routes those requests to an appropriate copy of the database (either the primary or a replica) based on performance heuristics, the type of queries made in those requests, and the query consistency needs as expressed by your application.</p><p>All of this global replica creation and request routing is handled by Cloudflare at no additional cost.</p><p>To take advantage of read replication, your Worker needs to use the new D1 <a href="https://developers.cloudflare.com/d1/best-practices/read-replication/"><u>Sessions API</u></a>. Click the button below to run a Worker using D1 read replication with this <a href="https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template"><u>code example</u></a> to see for yourself!</p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p>
    <div>
      <h2>D1 Sessions API</h2>
      <a href="#d1-sessions-api">
        
      </a>
    </div>
    <p>D1’s read replication feature is built around the concept of database <i>sessions</i>.  A session encapsulates all the queries representing one logical session for your application. For example, a session might represent all requests coming from a particular web browser or all requests coming from a mobile app used by one of your users. If you use sessions, your queries will use the appropriate copy of the D1 database that makes the most sense for your request, be that the primary database or a nearby replica.</p><p>The sessions implementation ensures <a href="https://jepsen.io/consistency/models/sequential"><u>sequential consistency</u></a> for all queries in the session, no matter what copy of the database each query is routed to.  The sequential consistency model has important properties like "<a href="https://jepsen.io/consistency/models/read-your-writes"><u>read my own writes</u></a>" and "<a href="https://jepsen.io/consistency/models/writes-follow-reads"><u>writes follow reads</u></a>," as well as a total ordering of writes. The total ordering of writes means that every replica will see transactions committed in the same order, which is exactly the behavior we want in a transactional system.  Said another way, sequential consistency guarantees that the reads and writes are executed in the order in which you write them in your code.</p><p>Some examples of consistency implications in real-world applications:</p><ul><li><p>You are using an online store and just placed an order (write query), followed by a visit to the account page to list all your orders (read query handled by a replica). You want the newly placed order to be listed there as well.</p></li><li><p>You are using your bank’s web application and make a transfer to your electricity provider (write query), and then immediately navigate to the account balance page (read query handled by a replica) to check the latest balance of your account, including that last payment.</p></li></ul><p>Why do we need the Sessions API? Why can we not just query replicas directly?</p><p>Applications using D1 read replication need the Sessions API because D1 runs on Cloudflare’s global network and there’s no way to ensure that requests from the same client get routed to the same replica for every request. For example, the client may switch from WiFi to a mobile network in a way that changes how their requests are routed to Cloudflare. Or the data center that handled previous requests could be down because of an outage or maintenance.</p><p>D1’s read replication is asynchronous, so it’s possible that when you switch between replicas, the replica you switch to lags behind the replica you were using. This could mean that, for example, the new replica hasn’t learned of the writes you just completed.  We could no longer guarantee useful properties like “read your own writes”.  In fact, in the presence of shifty routing, the only consistency property we could guarantee is that what you read had been committed at some point in the past (<a href="https://jepsen.io/consistency/models/read-committed"><u>read committed</u></a> consistency), which isn’t very useful at all!</p><p>Since we can’t guarantee routing to the same replica, we flip the script and use the information we get from the Sessions API to make sure whatever replica we land on can handle the request in a sequentially-consistent manner.</p><p>Here’s what the Sessions API looks like in a Worker:</p>
            <pre><code>export default {
  async fetch(request: Request, env: Env) {
    // A. Create the session.
    // When we create a D1 session, we can continue where we left off from a previous    
    // session if we have that session's last bookmark or use a constraint.
    const bookmark = request.headers.get('x-d1-bookmark') ?? 'first-unconstrained'
    const session = env.DB.withSession(bookmark)

    // Use this session for all our Workers' routes.
    const response = await handleRequest(request, session)

    // B. Return the bookmark so we can continue the session in another request.
    response.headers.set('x-d1-bookmark', session.getBookmark())

    return response
  }
}

async function handleRequest(request: Request, session: D1DatabaseSession) {
  const { pathname } = new URL(request.url)

  if (request.method === "GET" &amp;&amp; pathname === '/api/orders') {
    // C. Session read query.
    const { results } = await session.prepare('SELECT * FROM Orders').all()
    return Response.json(results)

  } else if (request.method === "POST" &amp;&amp; pathname === '/api/orders') {
    const order = await request.json&lt;Order&gt;()

    // D. Session write query.
    // Since this is a write query, D1 will transparently forward it to the primary.
    await session
      .prepare('INSERT INTO Orders VALUES (?, ?, ?)')
      .bind(order.orderId, order.customerId, order.quantity)
      .run()

    // E. Session read-after-write query.
    // In order for the application to be correct, this SELECT statement must see
    // the results of the INSERT statement above.
    const { results } = await session
      .prepare('SELECT * FROM Orders')
      .all()

    return Response.json(results)
  }

  return new Response('Not found', { status: 404 })
}</code></pre>
            <p>To use the Session API, you first need to create a session using the <code>withSession</code> method (<b><i>step A</i></b>).  The <code>withSession</code> method takes a bookmark as a parameter, or a constraint.  The provided constraint instructs D1 where to forward the first query of the session. Using <code>first-unconstrained</code> allows the first query to be processed by any replica without any restriction on how up-to-date it is. Using <code>first-primary</code> ensures that the first query of the session will be forwarded to the primary.</p>
            <pre><code>// A. Create the session.
const bookmark = request.headers.get('x-d1-bookmark') ?? 'first-unconstrained'
const session = env.DB.withSession(bookmark)</code></pre>
            <p>Providing an explicit bookmark instructs D1 that whichever database instance processes the query has to be at least as up-to-date as the provided bookmark (in case of a replica; the primary database is always up-to-date by definition).  Explicit bookmarks are how we can continue from previously-created sessions and maintain sequential consistency across user requests.</p><p>Once you’ve created the session, make queries like you normally would with D1.  The session object ensures that the queries you make are sequentially consistent with regards to each other.</p>
            <pre><code>// C. Session read query.
const { results } = await session.prepare('SELECT * FROM Orders').all()</code></pre>
            <p>For example, in the code example above, the session read query for listing the orders (<b><i>step C</i></b>) will return results that are at least as up-to-date as the bookmark used to create the session (<b><i>step A</i></b><i>)</i>.</p><p>More interesting is the write query to add a new order (<b><i>step D</i></b>) followed by the read query to list all orders (<b><i>step E</i></b>). Because both queries are executed on the same session, it is guaranteed that the read query will observe a database copy that includes the write query, thus maintaining sequential consistency.</p>
            <pre><code>// D. Session write query.
await session
  .prepare('INSERT INTO Orders VALUES (?, ?, ?)')
  .bind(order.orderId, order.customerId, order.quantity)
  .run()

// E. Session read-after-write query.
const { results } = await session
  .prepare('SELECT * FROM Orders')
  .all()</code></pre>
            <p>Note that we could make a single batch query to the primary including both the write and the list, but the benefit of using the new Sessions API is that you can use the extra read replica databases for your read queries and allow the primary database to handle more write queries.</p><p>The session object does the necessary bookkeeping to maintain the latest bookmark observed across all queries executed using that specific session, and always includes that latest bookmark in requests to D1. Note that any query executed without using the session object is not guaranteed to be sequentially consistent with the queries executed in the session.</p><p>When possible, we suggest continuing sessions across requests by including bookmarks in your responses to clients (<b><i>step B</i></b>), and having clients passing previously received bookmarks in their future requests.</p>
            <pre><code>// B. Return the bookmark so we can continue the session in another request.
response.headers.set('x-d1-bookmark', session.getBookmark())</code></pre>
            <p>This allows <i>all</i> of a client’s requests to be in the same session. You can do this by grabbing the session’s current bookmark at the end of the request (<code>session.getBookmark()</code>) and sending the bookmark in the response back to the client in HTTP headers, in HTTP cookies, or in the response body itself.</p>
    <div>
      <h3>Consistency with and without Sessions API</h3>
      <a href="#consistency-with-and-without-sessions-api">
        
      </a>
    </div>
    <p>In this section, we will explore the classic scenario of a read-after-write query to showcase how using the new D1 Sessions API ensures that we get sequential consistency and avoid any issues with inconsistent results in our application.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1zIBf3V1YIogYJKeWm1kDn/f484faf38cc0f8d7227f9db1fa386354/1.png" />
          </figure><p>The Client, a user Worker, sends a D1 write query that gets processed by the database primary and gets the results back. However, the subsequent read query ends up being processed by a database replica. If the database replica is lagging far enough behind the database primary, such that it does not yet include the first write query, then the returned results will be inconsistent, and probably incorrect for your application business logic.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1w81ec5tNGWJ7sQyFBZQ6l/d487ccf225a097a0e48054d88df0ba1f/2.png" />
          </figure><p>Using the Sessions API fixes the inconsistency issue. The first write query is again processed by the database primary, and this time the response includes “<b>Bookmark 100</b>”. The session object will store this bookmark for you transparently.</p><p>The subsequent read query is processed by database replica as before, but now since the query includes the previously received “<b>Bookmark 100</b>”, the database replica will wait until its database copy is at least up-to-date as “<b>Bookmark 100</b>”. Only once it’s up-to-date, the read query will be processed and the results returned, including the replica’s latest bookmark “<b>Bookmark 104</b>”.</p><p>Notice that the returned bookmark for the read query is “<b>Bookmark 104</b>”, which is different from the one passed in the query request. This can happen if there were other writes from other client requests that also got replicated to the database replica in-between the two queries our own client executed.</p>
    <div>
      <h2>Enabling read replication</h2>
      <a href="#enabling-read-replication">
        
      </a>
    </div>
    <p>To start using D1 read replication:</p><ol><li><p>Update your Worker to use the D1 Sessions API to tell D1 what queries are part of the same database session. The Sessions API works with databases that do not have read replication enabled as well, so it’s safe to ship this code even before you enable replicas. Here’s <a href="http://developers.cloudflare.com/d1/best-practices/read-replication/"><u>an example</u></a>.</p></li><li><p><a href="https://developers.cloudflare.com/d1/best-practices/read-replication/#enable-read-replication"><u>Enable replicas</u></a> for your database via <a href="https://dash.cloudflare.com/?to=/:account/workers/d1"><u>Cloudflare dashboard</u></a> &gt; Select D1 database &gt; Settings.</p></li></ol><p>D1 read replication is built into D1, and you don’t pay extra storage or compute costs for replicas. You incur the exact same D1 usage with or without replicas, based on <code>rows_read</code> and <code>rows_written</code> by your queries. Unlike other traditional database systems with replication, you don’t have to manually create replicas, including where they run, or decide how to route requests between the primary database and read replicas. Cloudflare handles this when using the Sessions API while ensuring sequential consistency.</p><p>Since D1 read replication is in beta, we recommend trying D1 read replication on a non-production database first, and migrate to your production workloads after validating read replication works for your use case.</p><p>If you don’t have a D1 database and want to try out D1 read replication, <a href="https://dash.cloudflare.com/?to=/:account/workers/d1/create"><u>create a test database</u></a> in the Cloudflare dashboard.</p>
    <div>
      <h3>Observing your replicas</h3>
      <a href="#observing-your-replicas">
        
      </a>
    </div>
    <p>Once you’ve enabled D1 read replication, read queries will start to be processed by replica database instances. The response of each query includes information in the nested <code>meta</code> object relevant to read replication, like <code>served_by_region</code> and <code>served_by_primary</code>. The first denotes the region of the database instance that processed the query, and the latter will be <code>true</code> if-and-only-if your query was processed by the primary database instance.</p><p>In addition, the <a href="https://dash.cloudflare.com/?to=/:account/workers/d1/"><u>D1 dashboard overview</u></a> for a database now includes information about the database instances handling your queries. You can see how many queries are handled by the primary instance or by a replica, and a breakdown of the queries processed by region. The example screenshots below show graphs displaying the number of queries executed  and number of rows read by each region.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ChIlqQ5xgJfiftOHw9Egg/b583d00d22dcea60e7439dfbfa1761df/image10.png" />
          </figure>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4Zze5y22759fOIYPOqrK1Y/6cd3c684006ca8234db20924cae8b960/image1.png" />
          </figure>
    <div>
      <h2>Under the hood: how D1 read replication is implemented</h2>
      <a href="#under-the-hood-how-d1-read-replication-is-implemented">
        
      </a>
    </div>
    <p>D1 is implemented on top of SQLite-backed Durable Objects running on top of Cloudflare’s <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/#under-the-hood-storage-relay-service"><u>Storage Relay Service</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3GWWL8goIzrGTmkH54O416/aabd47fcd94bfc73492556b19ac6069f/5.png" />
          </figure><p>D1 is structured with a 3-layer architecture.  First is the binding API layer that runs in the customer’s Worker.  Next is a stateless Worker layer that routes requests based on database ID to a layer of Durable Objects that handle the actual SQL operations behind D1.  This is similar to how <a href="https://developers.cloudflare.com/durable-objects/what-are-durable-objects/#durable-objects-in-cloudflare"><u>most applications using Cloudflare Workers and Durable Objects are structured</u></a>.</p><p>For a non-replicated database, there is exactly one Durable Object per database.  When a user’s Worker makes a request with the D1 binding for the database, that request is first routed to a D1 Worker running in the same location as the user’s Worker.  The D1 Worker figures out which D1 Durable Object backs the user’s D1 database and fetches an RPC stub to that Durable Object.  The Durable Objects routing layer figures out where the Durable Object is located, and opens an RPC connection to it.  Finally, the D1 Durable Object then handles the query on behalf of the user’s Worker using the Durable Objects SQL API.</p><p>In the Durable Objects SQL API, all queries go to a SQLite database on the local disk of the server where the Durable Object is running.  Durable Objects run <a href="https://www.sqlite.org/wal.html"><u>SQLite in WAL mode</u></a>.  In WAL mode, every write query appends to a write-ahead log (the WAL).  As SQLite appends entries to the end of the WAL file, a database-specific component called the Storage Relay Service <i>leader</i> synchronously replicates the entries to 5 <i>durability followers</i> on servers in different datacenters.  When a quorum (at least 3 out of 5) of the durability followers acknowledge that they have safely stored the data, the leader allows SQLite’s write queries to commit and opens the Durable Object’s output gate, so that the Durable Object can respond to requests.</p><p>Our implementation of WAL mode allows us to have a complete log of all of the committed changes to the database. This enables a couple of important features in SQLite-backed Durable Objects and D1:</p><ul><li><p>We identify each write with a <a href="https://en.wikipedia.org/wiki/Lamport_timestamp"><u>Lamport timestamp</u></a> we call a <a href="https://developers.cloudflare.com/d1/reference/time-travel/#bookmarks"><u>bookmark</u></a>.</p></li><li><p>We construct databases anywhere in the world by downloading all of the WAL entries from cold storage and replaying each WAL entry in order.</p></li><li><p>We implement <a href="https://developers.cloudflare.com/d1/reference/time-travel/"><u>Point-in-time recovery (PITR)</u></a> by replaying WAL entries up to a specific bookmark rather than to the end of the log.</p></li></ul><p>Unfortunately, having the main data structure of the database be a log is not ideal.  WAL entries are in write order, which is often neither convenient nor fast.  In order to cut down on the overheads of the log, SQLite <i>checkpoints</i> the log by copying the WAL entries back into the main database file.  Read queries are serviced directly by SQLite using files on disk — either the main database file for checkpointed queries, or the WAL file for writes more recent than the last checkpoint.  Similarly, the Storage Relay Service snapshots the database to cold storage so that we can replay a database by downloading the most recent snapshot and replaying the WAL from there, rather than having to download an enormous number of individual WAL entries.</p><p>WAL mode is the foundation for implementing read replication, since we can stream writes to locations other than cold storage in real time.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/ezp8gcf3gXqkvumzufGfP/1a54fc6f434290968c7e695c2e5bb0c9/6.png" />
          </figure><p>We implemented read replication in 5 major steps.</p><p>First, we made it possible to make replica Durable Objects with a read-only copy of the database.  These replica objects boot by fetching the latest snapshot and replaying the log from cold storage to whatever bookmark primary database’s leader last committed. This basically gave us point-in-time replicas, since without continuous updates, the replicas never updated until the Durable Object restarted.</p><p>Second, we registered the replica leader with the primary’s leader so that the primary leader sends the replicas every entry written to the WAL at the same time that it sends the WAL entries to the durability followers.  Each of the WAL entries is marked with a bookmark that uniquely identifies the WAL entry in the sequence of WAL entries.  We’ll use the bookmark later.</p><p>Note that since these writes are sent to the replicas <i>before</i> a quorum of durability followers have confirmed them, the writes are actually unconfirmed writes, and the replica leader must be careful to keep the writes hidden from the replica Durable Object until they are confirmed.  The replica leader in the Storage Relay Service does this by implementing enough of SQLite’s <a href="https://www.sqlite.org/walformat.html#the_wal_index_file_format"><u>WAL-index protocol</u></a>, so that the unconfirmed writes coming from the primary leader look to SQLite as though it’s just another SQLite client doing unconfirmed writes.  SQLite knows to ignore the writes until they are confirmed in the log.  The upshot of this is that the replica leader can write WAL entries to the SQLite WAL <i>immediately,</i> and then “commit” them when the primary leader tells the replica that the entries have been confirmed by durability followers.</p><p>One neat thing about this approach is that writes are sent from the primary to the replica as quickly as they are generated by the primary, helping to minimize lag between replicas.  In theory, if the write query was proxied through a replica to the primary, the response back to the replica will arrive at almost the same time as the message that updates the replica.  In such a case, it looks like there’s no replica lag at all!</p><p>In practice, we find that replication is really fast.  Internally, we measure <i>confirm lag</i>, defined as the time from when a primary confirms a change to when the replica confirms a change.  The table below shows the confirm lag for two D1 databases whose primaries are in different regions.</p><div>
    <figure>
        <table>
            <colgroup>
                <col></col>
                <col></col>
                <col></col>
            </colgroup>
            <tbody>
                <tr>
                    <td>
                        <p><br /><span><span>Replica Region</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Database A</span></span></p>
                        <p><span><span>(Primary region: ENAM)</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Database B</span></span><br /><span><span>(Primary region: WNAM)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>ENAM</span></span></p>
                    </td>
                    <td>
                        <p><span><span>N/A</span></span></p>
                    </td>
                    <td>
                        <p><span><span>30 ms</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>WNAM</span></span></p>
                    </td>
                    <td>
                        <p><span><span>45 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>N/A</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>WEUR</span></span></p>
                    </td>
                    <td>
                        <p><span><span>55 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>75 ms</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>EEUR</span></span></p>
                    </td>
                    <td>
                        <p><span><span>67 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>75 ms</span></span></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </figure>
</div><p><sup><i>Confirm lag for 2 replicated databases.  N/A means that we have no data for this combination.  The region abbreviations are the same ones used for </i></sup><a href="https://developers.cloudflare.com/durable-objects/reference/data-location/#supported-locations-1"><sup><i><u>Durable Object location hints</u></i></sup></a><sup><i>.</i></sup></p><p>The table shows that confirm lag is correlated with the network round-trip time between the data centers hosting the primary databases and their replicas.  This is clearly visible in the difference between the confirm lag for the European replicas of the two databases.  As airline route planners know, EEUR is <a href="http://www.gcmap.com/mapui?P=ewr-lhr,+ewr-waw"><u>appreciably further away</u></a> from ENAM than WEUR is, but from WNAM, both European regions (WEUR and EEUR) are <a href="http://www.gcmap.com/mapui?P=sjc-lhr,+sjc-waw"><u>about equally as far away</u></a>.  We see that in our replication numbers.</p><p>The exact placement of the D1 database in the region matters too.  Regions like ENAM and WNAM are quite large in themselves.  Database A’s placement in ENAM happens to be further away from most data centers in WNAM compared to database B’s placement in WNAM relative to the ENAM data centers.  As such, database B sees slightly lower confirm lag.</p><p>Try as we might, we can’t beat the speed of light!</p><p>Third, we updated the Durable Object routing system to be aware of Durable Object replicas.  When read replication is enabled on a Durable Object, two things happen.  First, we create a set of replicas according to a replication policy.  The current replication policy that D1 uses is simple: a static set of replicas in <a href="https://developers.cloudflare.com/d1/configuration/data-location/#available-location-hints"><u>every region that D1 supports</u></a>.  Second, we turn on a routing policy for the Durable Object.  The current policy that D1 uses is also simple: route to the Durable Object replica in the region close to where the user request is.  With this step, we have updateable read-only replicas, and can route requests to them!</p><p>Fourth, we updated D1’s Durable Object code to handle write queries on replicas. D1 uses SQLite to figure out whether a request is a write query or a read query.  This means that the determination of whether something is a read or write query happens <i>after</i> the request is routed.  Read replicas will have to handle write requests!  We solve this by instantiating each replica D1 Durable Object with a reference to its primary.  If the D1 Durable Object determines that the query is a write query, it forwards the request to the primary for the primary to handle. This happens transparently, keeping the user code simple.</p><p>As of this fourth step, we can handle read and write queries at every copy of the D1 Durable Object, whether it's a primary or not.  Unfortunately, as outlined above, if a user's requests get routed to different read replicas, they may see different views of the database, leading to a very weak consistency model.  So the last step is to implement the Sessions API across the D1 Worker and D1 Durable Object.  Recall that every WAL entry is marked with a bookmark.  These bookmarks uniquely identify a point in (logical) time in the database.  Our bookmarks are strictly monotonically increasing; every write to a database makes a new bookmark with a value greater than any other bookmark for that database.</p><p>Using bookmarks, we implement the Sessions API with the following algorithm split across the D1 binding implementation, the D1 Worker, and D1 Durable Object.</p><p>First up in the D1 binding, we have code that creates the <code>D1DatabaseSession</code> object and code within the <code>D1DatabaseSession</code> object to keep track of the latest bookmark.</p>
            <pre><code>// D1Binding is the binding code running within the user's Worker
// that provides the existing D1 Workers API and the new withSession method.
class D1Binding {
  // Injected by the runtime to the D1 Binding.
  d1Service: D1ServiceBinding

  function withSession(initialBookmark) {
    return D1DatabaseSession(this.d1Service, this.databaseId, initialBookmark);
  }
}

// D1DatabaseSession holds metadata about the session, most importantly the
// latest bookmark we know about for this session.
class D1DatabaseSession {
  constructor(d1Service, databaseId, initialBookmark) {
    this.d1Service = d1Service;
    this.databaseId = databaseId;
    this.bookmark = initialBookmark;
  }

  async exec(query) {
    // The exec method in the binding sends the query to the D1 Worker
    // and waits for the the response, updating the bookmark as
    // necessary so that future calls to exec use the updated bookmark.
    var resp = await this.d1Service.handleUserQuery(databaseId, query, bookmark);
    if (isNewerBookmark(this.bookmark, resp.bookmark)) {
      this.bookmark = resp.bookmark;
    }
    return resp;
  }

  // batch and other SQL APIs are implemented similarly.
}</code></pre>
            <p>The binding code calls into the D1 stateless Worker (<code>d1Service</code> in the snippet above), which figures out which Durable Object to use, and proxies the request to the Durable Object.</p>
            <pre><code>class D1Worker {
  async handleUserQuery(databaseId, query) {
    var doId = /* look up Durable Object for databaseId */;
    return await this.D1_DO.get(doId).handleWorkerQuery(query, bookmark)
  }
}</code></pre>
            <p>Finally, we reach the Durable Objects layer, which figures out how to actually handle the request.</p>
            <pre><code>class D1DurableObject {
  async handleWorkerQuery(queries, bookmark) {
    var bookmark = bookmark ?? "first-primary";
    var results = {};

    if (this.isPrimaryDatabase()) {
      // The primary always has the latest data so we can run the
      // query without checking the bookmark.
      var result = /* execute query directly */;
      bookmark = getCurrentBookmark();
      results = result;
    } else {
      // This is running on a replica.
      if (bookmark === "first-primary" || isWriteQuery(query)) {
        // The primary must handle this request, so we'll proxy the
        // request to the primary.
        var resp = await this.primary.handleWorkerQuery(query, bookmark);
        bookmark = resp.bookmark;
        results = resp.results;
      } else {
        // The replica can handle this request, but only after the
        // database is up-to-date with the bookmark.
        if (bookmark !== "first-unconstrained") {
          await waitForBookmark(bookmark);
        }
        var result = /* execute query locally */;
        bookmark = getCurrentBookmark();
        results = result;
      }
    }
    return { results: results, bookmark: bookmark };
  }
}</code></pre>
            <p>The D1 Durable Object first figures out if this instance can handle the query, or if the query needs to be sent to the primary.  If the Durable Object can execute the query, it ensures that we execute the query with a bookmark at least as up-to-date as the bookmark requested by the binding.</p><p>The upshot is that the three pieces of code work together to ensure that all of the queries in the session see the database in a sequentially consistent order, because each new query will be blocked until it has seen the results of previous queries within the same session.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>D1’s new read replication feature is a significant step towards making globally distributed databases easier to use without sacrificing consistency. With automatically provisioned replicas in every region, your applications can now serve read queries faster while maintaining strong sequential consistency across requests, and keeping your application Worker code simple.</p><p>We’re excited for developers to explore this feature and see how it improves the performance of your applications. The public beta is just the beginning—we’re actively refining and expanding D1’s capabilities, including evolving replica placement policies, and your feedback will help shape what’s next.</p><p>Note that the Sessions API is only available through the <a href="https://developers.cloudflare.com/d1/worker-api/"><u>D1 Worker Binding</u></a> for now, and support for the HTTP REST API will follow soon.</p><p>Try out D1 read replication today by clicking the “Deploy to Cloudflare" button, check out <a href="http://developers.cloudflare.com/d1/best-practices/read-replication/"><u>documentation and examples</u></a>, and let us know what you build in the <a href="https://discord.com/channels/595317990191398933/992060581832032316"><u>D1 Discord channel</u></a>!</p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/d1-starter-sessions-api-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[D1]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[Edge Database]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[SQL]]></category>
            <guid isPermaLink="false">2qUAO70BqnRBomg83fCRPe</guid>
            <dc:creator>Justin Mazzola Paluska</dc:creator>
            <dc:creator>Lambros Petrou</dc:creator>
        </item>
        <item>
            <title><![CDATA[Just landed: streaming ingestion on Cloudflare with Arroyo and Pipelines]]></title>
            <link>https://blog.cloudflare.com/cloudflare-acquires-arroyo-pipelines-streaming-ingestion-beta/</link>
            <pubDate>Thu, 10 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ We’ve just shipped our new streaming ingestion service, Pipelines — and we’ve acquired Arroyo, enabling us to bring new SQL-based, stateful transformations to Pipelines and R2. ]]></description>
            <content:encoded><![CDATA[ <p>Today, we’re launching the open beta of Pipelines, our streaming ingestion product. Pipelines allows you to ingest high volumes of structured, real-time data, and load it into our <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>object storage service, R2</u></a>. You don’t have to manage any of the underlying infrastructure, worry about scaling shards or metadata services, and you pay for the data processed (and not by the hour). Anyone on a Workers paid plan can start using it to ingest and batch data — at tens of thousands of requests per second (RPS) — directly into R2.</p><p>But this is just the tip of the iceberg: you often want to transform the data you’re ingesting, hydrate it on-the-fly from other sources, and write it to an open table format (such as Apache Iceberg), so that you can efficiently query that data once you’ve landed it in object storage.</p><p>The good news is that we’ve thought about that too, and we’re excited to announce that we’ve acquired <a href="https://www.arroyo.dev/"><u>Arroyo</u></a>, a cloud-native, distributed stream processing engine, to make that happen.</p><p>With Arroyo <i>and </i>our just announced <a href="https://blog.cloudflare.com/r2-data-catalog-public-beta/">R2 Data Catalog</a>, we’re getting increasingly serious about building a data platform that allows you to ingest data across the planet, store it at scale, and <i>run compute over it</i>. </p><p>To get started, you can dive into the <a href="http://developers.cloudflare.com/pipelines/"><u>Pipelines developer docs</u></a> or just run this <a href="https://developers.cloudflare.com/workers/wrangler/"><u>Wrangler</u></a> command to create your first pipeline:</p>
            <pre><code>$ npx wrangler@latest pipelines create my-clickstream-pipeline --r2-bucket my-bucket

...
✅ Successfully created Pipeline my-clickstream-pipeline with ID 0e00c5ff09b34d018152af98d06f5a1xv</code></pre>
            <p>… and then write your first record(s):</p>
            <pre><code>$ curl -d '[{"payload": [],"id":"abc-def"}]' 
"https://0e00c5ff09b34d018152af98d06f5a1xvc.pipelines.cloudflarestorage.com/"</code></pre>
            <p>However, the true power comes from the processing of data streams between ingestion and when they’re written to sinks like R2. Being able to write SQL that acts on windows of data <i>as it’s being ingested</i>, that can transform &amp; aggregate it, and even extract insights from the data in real-time, turns out to be extremely powerful.</p><p>This is where Arroyo comes in, and we’re going to be bringing the best parts of Arroyo into Pipelines and deeply integrate it with Workers, R2, and the rest of our Developer Platform.</p>
    <div>
      <h2>The Arroyo origin story </h2>
      <a href="#the-arroyo-origin-story">
        
      </a>
    </div>
    <p><i>(By Micah Wylde, founder of Arroyo)</i></p><p>We started Arroyo in 2023 to bring real-time (<i>stream</i>) processing to everyone who works with data. Modern companies rely on data pipelines to power their applications and businesses — from user customization, recommendations, and anti-fraud, to the emerging world of AI agents.</p><p>But today, most of these pipelines operate in batch, running once per hour, day, or even month. After spending many years working on stream processing at companies like Lyft and Splunk, it was no mystery why: it was just too hard for developers and data scientists to build correct, performant, and reliable pipelines. Large tech companies hire streaming experts to build and operate these systems, but everyone else is stuck waiting for batches to arrive. </p><p>When we started, the dominant solution for streaming pipelines — and what we ran at Lyft and Splunk — was Apache Flink. Flink was the first system that successfully combined a fault-tolerant (able to recover consistently from failures), distributed (across multiple machines), stateful (and remember data about past events) dataflow with a graph-construction API. This combination of features meant that we could finally build powerful real-time data applications, with capabilities like windows, aggregations, and joins. But while Flink had the necessary power, in practice the API proved too hard and low-level for non-expert users, and the stateful nature of the resulting services required endless operations.</p><p>We realized we would need to build a new streaming engine — one with the power of Flink, but designed for product engineers and data scientists and to run on modern cloud infrastructure. We started with SQL as our API because it’s easy to use, widely known, and declarative. We built it in Rust for speed and operational simplicity (no JVM tuning required!). We constructed an object-storage-native state backend, simplifying the challenge of running stateful pipelines — which each are like a weird, specialized database. And then in the summer of 2023, we open-sourced it. Today, dozens of companies are running Arroyo pipelines with use cases including data ingestion, anti-fraud, IoT observability, and financial trading. </p><p>But we always knew that the engine was just one piece of the puzzle. To make streaming as easy as batch, users need to be able to develop and test query logic, backfill on historical data, and deploy serverlessly without having to worry about cluster sizing or ongoing operations. Democratizing streaming ultimately meant building a complete data platform. And when we started talking with Cloudflare, we realized they already had all of the pieces in place: R2 provides object storage for state and data at rest, Cloudflare <a href="https://developers.cloudflare.com/queues/"><u>Queues</u></a> for data in transit, and Workers to safely and efficiently run user code. And Cloudflare, uniquely, allows us to push these systems all the way to the edge, enabling a new paradigm of local stream processing that will be key for a future of data sovereignty and AI.</p><p>That’s why we’re incredibly excited to join with the Cloudflare team to make this vision a reality.</p>
    <div>
      <h2>Ingestion at scale</h2>
      <a href="#ingestion-at-scale">
        
      </a>
    </div>
    <p>While transformations and a streaming SQL API are on the way for Pipelines, it already solves two critical parts of the data journey: globally distributed, high-throughput ingestion and efficient loading into object storage. </p><p>Creating a pipeline is as simple as running one command: </p>
            <pre><code>$ npx wrangler@latest pipelines create my-clickstream-pipeline --r2-bucket my-bucket

🌀 Creating pipeline named "my-clickstream-pipeline"
✅ Successfully created pipeline my-clickstream-pipeline with ID 
0e00c5ff09b34d018152af98d06f5a1xvc

Id:    0e00c5ff09b34d018152af98d06f5a1xvc
Name:  my-clickstream-pipeline
Sources:
  HTTP:
    Endpoint:        https://0e00c5ff09b34d018152af98d06f5a1xvc.pipelines.cloudflare.com/
    Authentication:  off
    Format:          JSON
  Worker:
    Format:  JSON
Destination:
  Type:         R2
  Bucket:       my-bucket
  Format:       newline-delimited JSON
  Compression:  GZIP
Batch hints:
  Max bytes:     100 MB
  Max duration:  300 seconds
  Max records:   100,000

🎉 You can now send data to your pipeline!

Send data to your pipeline's HTTP endpoint:
curl "https://0e00c5ff09b34d018152af98d06f5a1xvc.pipelines.cloudflare.com/" -d '[{ ...JSON_DATA... }]'</code></pre>
            <p>By default, a pipeline can ingest data from two sources – Workers and an HTTP endpoint – and load batched events into an R2 bucket. This gives you an out-of-the-box solution for streaming raw event data into object storage. If the defaults don’t work, you can configure pipelines during creation or anytime after. Options include: adding authentication to the HTTP endpoint, configuring CORS to allow browsers to make cross-origin requests, and specifying output file compression and batch settings.</p><p>We’ve built Pipelines for high ingestion volumes from day 1. Each pipeline can scale to ~100,000 records per second (and we’re just getting started here). Once records are written to a Pipeline, they are then durably stored, batched, and written out as files in an R2 bucket. Batching is critical here: if you’re going to act on and query that data, you don’t want your query engine querying millions (or tens of millions) of tiny files. It’s slow (per-file &amp; request overheads), inefficient (more files to read), and costly (more operations). Instead, you want to find the right balance between batch size for your query engine and latency (not waiting too long for a batch): Pipelines allows you to configure this.</p><p>To further optimize queries, output files are partitioned by date and time, using the standard Hive partitioning scheme. This can optimize queries even further, because your query engine can just skip data that is irrelevant to the query you’re running. The output in your R2 bucket might look like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7q63u2kRoYBAZJtgfcF874/2a7341e1cba6e371e0eed311e89fec6a/image1.png" />
          </figure><p><sup><i>Hive-partioned files from Pipelines in an R2 bucket</i></sup></p><p>Output files are stored as new-line delimited JSON (NDJSON) — which makes it easy to materialize a stream from these files (hint: in the future you’ll be able to use R2 as a pipeline source too). Finally, the file names are <a href="https://github.com/ulid/spec"><u>ULIDs</u></a> - so they’re sorted by time by default.</p>
    <div>
      <h2>First you shard, then you shard some more</h2>
      <a href="#first-you-shard-then-you-shard-some-more">
        
      </a>
    </div>
    <p>What makes Pipelines so horizontally scalable <i>and</i> able to acknowledge writes quickly is how we built it: we use Durable Objects and the <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/"><u>embedded, zero-latency SQLite</u></a> storage within each Durable Object to immediately persist data as it’s written, before then processing it and writing it to R2.</p><p>For example: imagine you’re an e-commerce or SaaS site and need to ingest website usage data (known as <i>clickstream data</i>), and make it available to your data science team to query. The infrastructure which handles this workload has to be resilient to several failure scenarios. The ingestion service needs to maintain high availability in the face of bursts in traffic. Once ingested, the data needs to be buffered, to minimize downstream invocations and thus downstream cost. Finally, the buffered data needs to be delivered to a sink, with appropriate retry &amp; failure handling if the sink is unavailable. Each step of this process needs to signal backpressure upstream when overloaded. It also needs to scale: up during major sales or events, and down during the quieter periods of the day.</p><p>Data engineers reading this post might be familiar with the status quo of using Kafka and the associated ecosystem to handle this. But if you’re an application engineer: you use Pipelines to build an ingestion service <i>without </i>learning about Kafka, Zookeeper, and Kafka streams.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/eRIUocbyvY2oHwEK34pzE/e2ef72b2858c02e890446cfd34accb45/image3.png" />
          </figure><p><sup><i>Pipelines horizontal sharding</i></sup></p><p>The diagram above shows how Pipelines splits the control plane, which is responsible for accounting, tracking shards, and Pipelines lifecycle events, and the data path, which is a scalable group of Durable Objects shards.</p><p>When a record (or batch of records) is written to Pipelines:</p><ol><li><p>The Pipelines Worker receives the records either through the fetch handler or worker binding.</p></li><li><p>Contacts the Coordinator, based upon the <code>pipeline_id</code> to get the execution plan: subsequent reads are cached to reduce pressure on the coordinator.</p></li><li><p>Executes the plan, which first shards to a set of Executors, while are primarily serving to scale read request handling</p></li><li><p>These then re-shard to another set of executors that are actually handling the writes, beginning with persisting to Durable Object storage, which will be replicated for durability and availability by the <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/#under-the-hood-storage-relay-service"><u>Storage Relay Service</u></a> (SRS). </p></li><li><p>After SRS, we pass to any configured Transform Workers to customize the data.</p></li><li><p>The data is batched, written to output files, and compressed (if applicable).</p></li><li><p>The files are compressed, data is packaged into the final batches, and written to the configured R2 bucket.</p></li></ol><p>Each step of this pipeline can signal backpressure upstream. We do this by leveraging <a href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream"><u>ReadableStreams</u></a> and responding with <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/429"><u>429s</u></a> when the total number of bytes awaiting write exceeds a threshold. Each ReadableStream is able to cross Durable Object boundaries by using <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/"><u>JSRPC</u></a> calls between Durable Objects. To improve performance, we use RPC stubs for connection reuse between Durable Objects. Each step is also able to retry operations, to handle any temporary unavailability in the Durable Objects or R2.</p><p>We also guarantee delivery even while updating an existing pipeline. When you update an existing pipeline, we create a new deployment, including all the shards and Durable Objects described above. Requests are gracefully re-routed to the new pipeline. The old pipeline continues to write data into R2, until all the Durable Object storage is drained. We spin down the old pipeline only after all the data has been written out. This way, you won’t lose data even while updating a pipeline.</p><p>You’ll notice there’s one interesting part in here — the Transform Workers — which we haven’t yet exposed. As we work to integrate Arroyo’s streaming engine with Pipelines, this will be a key part of how we hand over data for Arroyo to process.</p>
    <div>
      <h2>So, what’s it cost?</h2>
      <a href="#so-whats-it-cost">
        
      </a>
    </div>
    <p>During the first phase of the open beta, there will be no additional charges beyond standard R2 storage and operation costs incurred when loading and accessing data. And as always, egress directly from R2 buckets is free, so you can process and query your data from any cloud or region without worrying about data transfer costs adding up.</p><p>In the future, we plan to introduce pricing based on volume of data ingested into Pipelines and delivered from Pipelines:</p><table><tr><td><p>
</p></td><td><p><b>Workers Paid ($5 / month)</b></p></td></tr><tr><td><p><b>Ingestion</b></p></td><td><p>First 50 GB per month included</p><p>\$0.02 per additional GB</p></td></tr><tr><td><p><b>Delivery to R2</b></p></td><td><p>First 50 GB per month included</p><p>\$0.02 per additional GB</p></td></tr></table><p>We’re also planning to make Pipelines available on the Workers Free plan as the beta progresses.</p><p>We’ll be sharing more as we bring transformations and additional sinks to Pipelines. We’ll provide at least 30 days notice before we make any changes or start charging for usage, which we expect to do by September 15, 2025.</p>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>There’s a lot to build here, and we’re keen to build on a lot of the powerful components that Arroyo has built: integrating Workers as UDFs (User-Defined Functions), adding new sources like Kafka clients, and extending Pipelines with new sinks (beyond R2).</p><p>We’ll also be integrating Pipelines with our just-launched <a href="https://blog.cloudflare.com/r2-data-catalog-public-beta/">R2 Data Catalog</a>: enabling you ingest streams of data directly into Iceberg tables and immediately query them, without needing to rely on other systems.</p><p>In the meantime, you can:</p><ul><li><p>Get started and <a href="http://developers.cloudflare.com/pipelines/getting-started/"><u>create your first Pipeline</u></a></p></li><li><p><a href="http://developers.cloudflare.com/pipelines/"><u>Read the docs</u></a></p></li><li><p>Join the <code>#pipelines-beta</code> channel on <a href="http://discord.cloudflare.com/"><u>our Developer Discord</u></a></p></li></ul><p>… or deploy the example project directly: </p>
            <pre><code>$ npm create cloudflare@latest -- pipelines-starter 
--template="cloudflare/pipelines-starter"</code></pre>
            <p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[R2]]></category>
            <category><![CDATA[Pipelines]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <guid isPermaLink="false">7rKz4iUFCDuhtjGXVbgFzl</guid>
            <dc:creator>Micah Wylde</dc:creator>
            <dc:creator>Matt Silverlock</dc:creator>
            <dc:creator>Pranshu Maheshwari</dc:creator>
        </item>
        <item>
            <title><![CDATA[Make your apps truly interactive with Cloudflare Realtime and RealtimeKit ]]></title>
            <link>https://blog.cloudflare.com/introducing-cloudflare-realtime-and-realtimekit/</link>
            <pubDate>Wed, 09 Apr 2025 14:05:00 GMT</pubDate>
            <description><![CDATA[ Announcing Cloudflare Realtime and RealtimeKit, a complete toolkit for shipping real-time audio and video apps in days with SDKs for Kotlin, React Native, Swift, JavaScript, and Flutter. ]]></description>
            <content:encoded><![CDATA[ <p>Over the past few years, we’ve seen developers push the boundaries of what’s possible with real-time communication — tools for collaborative work, massive online watch parties, and interactive live classrooms are all exploding in popularity.</p><p>We use AI more and more in our daily lives. Text-based interactions are evolving into something more natural: voice and video. When users interact with the applications and tools that AI developers create, we have high expectations for response time and connection quality. Complex applications of AI are built on not just one tool, but a combination of tools, often from different providers which requires a well connected cloud to sit in the middle for the coordination of different AI tools.</p><p>Developers already use <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a>, <a href="https://developers.cloudflare.com/workers-ai/"><u>Workers AI</u></a>, and our WebRTC <a href="https://developers.cloudflare.com/calls/"><u>SFU</u></a> and <a href="https://developers.cloudflare.com/calls/turn/"><u>TURN</u></a> services to build powerful apps without needing to think about coordinating compute or media services to be closest to their user. It’s only natural for there to be a singular <a href="https://blog.cloudflare.com/best-place-region-earth-inference/"><u>"Region: Earth"</u></a> for real-time applications.</p><p>We're excited to introduce <a href="https://realtime.cloudflare.com"><u>Cloudflare Realtime</u></a> — a suite of products to help you make your apps truly interactive with real-time audio and video experiences. Cloudflare Realtime now brings together our SFU, STUN, and TURN services, along with the new RealtimeKit.</p>
    <div>
      <h2>Say hello to RealtimeKit</h2>
      <a href="#say-hello-to-realtimekit">
        
      </a>
    </div>
    <p>RealtimeKit is a collection of mobile SDKs (iOS, Android, React Native, Flutter), SDKs for the Web (React, Angular, vanilla JS, WebComponents), and server side services (recording, coordination, transcription) that make it easier than ever to build real-time voice, video, and AI applications. RealtimeKit also includes user interface components to build interfaces quickly. </p><p>The amazing team behind <a href="https://dyte.io/"><u>Dyte</u></a>, a leading company in the real-time ecosystem, joined Cloudflare to accelerate the development of RealtimeKit. The Dyte team spent years focused on making real-time experiences accessible to developers of all skill levels, and had a deep understanding of the developer journey — they built abstractions that hid WebRTC's complexity without removing its power.</p><p>Already a user of Cloudflare’s products, Dyte was a perfect complement to Cloudflare’s existing real-time infrastructure spanning 300+ cities worldwide. They built a developer experience layer that made complex media capabilities accessible. We’re incredibly excited for their team to join Cloudflare as we help developers define the future of user interaction for real-time applications as one team.</p>
    <div>
      <h2>Interactive applications shouldn't require WebRTC expertise </h2>
      <a href="#interactive-applications-shouldnt-require-webrtc-expertise">
        
      </a>
    </div>
    <p>For many developers, what starts as "let's add video chat" can quickly escalate into weeks of technical deep dives into WebSockets and WebRTC. While we are big believers in the <a href="https://blog.cloudflare.com/tag/webrtc/"><u>potential of WebRTC</u></a>, we also know that it comes with real challenges when building for the first time. Debugging WebRTC sessions can require developers to learn about esoteric new concepts such as navigating <a href="https://webrtcforthecurious.com/docs/03-connecting/#ice"><u>ICE candidate failures</u></a>, <a href="https://webrtcforthecurious.com/docs/03-connecting/#turn"><u>TURN server configurations</u></a>, and <a href="https://webrtcforthecurious.com/docs/03-connecting/#turn"><u>SDP negotiation issues</u></a>.</p><p>The challenges of building a WebRTC app for the first time don’t stop there. Device management adds another layer of complexity. Inconsistent camera and microphone APIs across browsers and mobile platforms introduce unexpected behaviors in production. Chrome handles resolution switching one way, Safari another, and Android WebViews break in uniquely frustrating ways. We regularly see applications that function perfectly in testing environments fail mysteriously when deployed to certain devices or browsers.</p><p>Systems that work flawlessly with 5 test users collapse under the load of 50 real-world participants. Bandwidth adaptation falters, connection management becomes unwieldy, and maintaining consistent quality across diverse network conditions proves nearly impossible without specialized expertise. </p><p>What starts as a straightforward feature becomes a multi-month project requiring low-level engineering to solve problems that aren’t core to your business.</p><p>We realized that we needed to extend our products to client devices to help solve these problems.</p>
    <div>
      <h2>RealtimeKit SDKs for Kotlin, React Native, Swift, JavaScript, Flutter</h2>
      <a href="#realtimekit-sdks-for-kotlin-react-native-swift-javascript-flutter">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/20EM65tMDpRznldLcfRYSo/90db0a5576bcecf0eaa3d28f7feaa65e/Final.png" />
          </figure><p>RealtimeKit is our toolkit for building real-time applications without common WebRTC headaches. The core of RealtimeKit is a set of cross-platform SDKs that handle all the low-level complexities, from session establishment and media permissions to NAT traversal and connection management. Instead of spending weeks implementing and debugging these foundations, you can focus entirely on creating unique experiences for your users.</p><p>Recording capabilities come built-in, eliminating one of the most commonly requested yet difficult-to-implement features in real-time applications. Whether you need to capture meetings for compliance, save virtual classroom sessions for students who couldn't attend live, or enable content creators to archive their streams, RealtimeKit handles the entire media pipeline. No more wrestling with MediaRecorder APIs or building custom recording infrastructure — it just works, scaling alongside your user base.</p><p>We've also integrated voice AI capabilities from providers like ElevenLabs directly into the platform. Adding AI participants to conversations becomes as simple as a function call, opening up entirely new interaction models. These AI voices operate with the same low latency as human participants — tens of milliseconds across our global network — creating truly synchronous experiences where AI and humans converse naturally. Combined with RealtimeKit's ability to scale to millions of concurrent participants, this enables entirely new categories of applications that weren't feasible before.</p>
    <div>
      <h2>The Developer Experience</h2>
      <a href="#the-developer-experience">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7GAxgCMn36QUgxSlF7m0xL/34574d1d1ba3da305e46b41bc455e769/2.png" />
          </figure><p>RealtimeKit focuses on what developers want to accomplish, rather than how the underlying protocols work. Adding participants or turning on recording are just an API call away. SDKs handle device enumeration, permission requests, and UI rendering across platforms. Behind the scenes, we’re solving the thorny problems of media orchestration and state management that can be challenging to debug.</p><p>We’ve been quietly working towards launching the Cloudflare RealtimeKit for years. From the very beginning, our global network has been optimized for minimizing latency between our network and end users, which is where the majority of network disruptions are introduced.</p><p>We developed a <a href="https://blog.cloudflare.com/cloudflare-calls-anycast-webrtc/"><u>Selective Forwarding Unit (SFU)</u></a> that intelligently routes media streams between participants, dynamically adjusting quality based on network conditions. Our <a href="https://blog.cloudflare.com/lt-lt/webrtc-turn-using-anycast/"><u>TURN infrastructure</u></a> solves the <a href="https://webrtchacks.com/an-intro-to-webrtcs-natfirewall-problem/"><u>complex problem of NAT traversal</u></a>, allowing connections to be established reliably behind firewalls. With Workers AI, we brought inference capabilities to the edge, minimizing latency for AI-powered interactions. Workers and Durable Objects provided the WebSockets coordination layer necessary for maintaining consistent state across participants.</p>
    <div>
      <h2>SFU and TURN services are now Generally Available</h2>
      <a href="#sfu-and-turn-services-are-now-generally-available">
        
      </a>
    </div>
    <p>We’re also announcing the General Availability of our SFU and TURN services for WebRTC developers that need more control and a low-level integration with the Cloudflare network.</p><p>SFU now supports simulcast, a very common feature request. Simulcast allows developers to select media streams from multiple options, similar to selecting the quality level of an online video, but for WebRTC. Users with different network qualities are now able to receive different levels of quality, either automatically defined by the SFU or manually selected.</p><p>Our TURN service now offers advanced analytics with insight into regional, country, and city level usage metrics. Together with <a href="https://developers.cloudflare.com/calls/turn/replacing-existing/#tag-users-with-custom-identifiers"><u>Custom Identifiers</u></a>, and revocable tokens, Cloudflare’s TURN service offers an in-depth view into usage and helps avoid abuse.</p><p>Our SFU and TURN products continue to be one of the most affordable ways to build WebRTC apps at scale, at 5 cents per GB after 1,000 GB of free usage each month.</p>
    <div>
      <h2>Partnering with Hugging Face to make realtime AI communication seamless</h2>
      <a href="#partnering-with-hugging-face-to-make-realtime-ai-communication-seamless">
        
      </a>
    </div>
    <p><a href="https://fastrtc.org/"><u>FastRTC</u></a> is a lightweight Python library from Hugging Face that makes it easy to stream real-time audio and video into and out of AI models using WebRTC. TURN servers are a critical part of WebRTC infrastructure and ensure that media streams can reliably connect across firewalls and NATs. For users of FastRTC, setting up a globally distributed TURN server can be complex and expensive.  </p><p>Through our new partnership with Hugging Face, FastRTC users now have free access to Cloudflare’s TURN Server product, giving them reliable connectivity out of the box. Developers get 10 GB of TURN bandwidth each month using just a Hugging Face access token — no setup, no credit card, no servers to manage. As projects grow, they can easily switch to a Cloudflare account for more capacity and a larger free tier.</p><p>This integration allows AI developers to focus on building voice interfaces, video pipelines, and multimodal apps without worrying about NAT traversal or network reliability. FastRTC simplifies the code, and Cloudflare ensures it works everywhere. See these <a href="https://huggingface.co/fastrtc"><u>demos</u></a> to get started.</p>
    <div>
      <h2>Ship AI-powered realtime apps in days, not weeks</h2>
      <a href="#ship-ai-powered-realtime-apps-in-days-not-weeks">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1b4lK5Qvq1ImlBa3lFEH7l/bf212f51a1f178285747e759c1365ec9/3.png" />
          </figure><p>With RealtimeKit, developers can now implement complex real-time experiences in hours. The SDKs abstract away the most time-consuming aspects of WebRTC development while providing APIs tailored to common implementation patterns. Here are a few of the possibilities: </p><ul><li><p><b>Video conferencing</b>: Add multi-participant video calls to your application with just a few lines of code. RealtimeKit handles the connection management, bandwidth adaptation, and device permissions that typically consume weeks of development time.</p></li><li><p><b>Live streaming</b>: Build interactive broadcasts where hosts can stream to thousands of viewers while selectively bringing participants on-screen. The SFU automatically optimizes media routing based on participant roles and network conditions.</p></li><li><p><b>Real-time synchronization</b>: Implement watch parties or collaborative viewing experiences where content playback stays synchronized across all participants. The timing API handles the complex delay calculations and adjustments traditionally required.</p></li><li><p><b>Voice AI integrations</b>: Add transcription and AI voice participants without building custom media pipelines. RealtimeKit's media processing APIs integrate with your existing authentication and storage systems rather than requiring separate infrastructure.</p></li></ul><p>When we’ve seen our early testers use the RealtimeKit, it doesn't just accelerate their existing projects, it fundamentally changes which projects become viable. </p>
    <div>
      <h2>Get started with RealtimeKit</h2>
      <a href="#get-started-with-realtimekit">
        
      </a>
    </div>
    <p>Starting today, you'll notice a new <a href="https://dash.cloudflare.com/?to=/:account/realtime"><u>Realtime section in your Cloudflare Dashboard</u></a>. This section includes our TURN and SFU products alongside our latest product, RealtimeKit. </p><p>RealtimeKit is currently in a closed beta ready for select customers to start kicking the tires. There is currently no cost to test it out during the beta. Request early access <a href="https://www.cloudflare.com/cloudflare-realtimekit-signup/"><u>here</u></a> or via the link in your <a href="https://dash.cloudflare.com/?to=/:account/realtime"><u>Cloudflare dashboard</u></a>. We can’t wait to see what you build. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/53RI7RZhs5Y0zHMHKg6fLh/e155081853355a7714e052ff23db6269/4.png" />
          </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[WebRTC]]></category>
            <category><![CDATA[Cloudflare Calls]]></category>
            <category><![CDATA[Real-time]]></category>
            <category><![CDATA[TURN Server]]></category>
            <guid isPermaLink="false">opC8hYtVRkyCEv7Yze4R0</guid>
            <dc:creator>Zaid Farooqui</dc:creator>
            <dc:creator>Will Allen</dc:creator>
            <dc:creator>Abhishek Kankani</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing Cloudflare Secrets Store (Beta): secure your secrets, simplify your workflow]]></title>
            <link>https://blog.cloudflare.com/secrets-store-beta/</link>
            <pubDate>Wed, 09 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Securely store, manage and deploy account level secrets to Cloudflare Workers through Cloudflare Secrets Store, available in beta – with role-based access control, audit logging and Wrangler support. ]]></description>
            <content:encoded><![CDATA[ <p>Every cloud platform needs a secure way to store API tokens, keys, and credentials — welcome, Cloudflare Secrets Store! Today, we are very excited to announce and launch Secrets Store in beta. We built <a href="https://developers.cloudflare.com/secrets-store/"><u>Cloudflare Secrets Store</u></a> to help our customers centralize management, improve security, and restrict access to sensitive values on the Cloudflare platform. </p><p>Wherever secrets exist at Cloudflare – from our <a href="https://developers.cloudflare.com/learning-paths/workers/devplat/intro-to-devplat/"><u>developer platform</u></a>, to <a href="https://developers.cloudflare.com/products/?product-group=AI"><u>AI products</u></a>, to <a href="https://blog.cloudflare.com/cloudflare-one/"><u>Cloudflare One</u></a> –  we’ve built a centralized platform that allows you to manage them in one place. </p><p>We are excited to integrate Cloudflare Secrets Store with the whole portfolio of Cloudflare products, starting today with Cloudflare Workers. </p>
    <div>
      <h2>Securing your secrets across Workers</h2>
      <a href="#securing-your-secrets-across-workers">
        
      </a>
    </div>
    <p>If you have a secret you want to use across multiple Workers, you can now use the Cloudflare Secrets Store to do so. You can spin up your store from the dashboard or by using Wrangler CLI:</p>
            <pre><code>wrangler secrets-store store create &lt;name&gt;
</code></pre>
            <p>Then, create a secret:</p>
            <pre><code>wrangler secrets-store secret create &lt;store-id&gt;
</code></pre>
            <p>Once the secret is created, you can specify the binding to deploy in a Worker immediately. </p>
            <pre><code>secrets_store_secrets = [
{ binding = "'open_AI_KEY'", store_id= "abc123", secret_name = "open_AI_key"},
]
</code></pre>
            <p>Last step – you can now reference the secret in code!</p>
            <pre><code>const openAIkey = await env.open_AI_key.get();
</code></pre>
            <p><a href="https://blog.cloudflare.com/workers-secrets-environment/"><u>Environment variables and secrets</u></a> were first launched in Cloudflare Workers back in 2020. Now, there are millions of local secrets deployed on Workers scripts. However, these are not all <i>unique</i>. Many of these secrets have duplicate values within a customer’s account. For example, a customer may reuse the same API token in ten different scripts, but since each secret is accessible only on the per-Worker level, that value would be stored in ten different local secrets. Plus, if you need to roll that secret, there is no seamless way to do so that preserves a single source of truth.</p><p>With thousands of secrets duplicated across scripts — each requiring manual creation and updates  — scoping secrets to individual Workers has created significant friction for developers. Additionally, because Workers secrets are created and deployed locally, any secret is accessible – in terms of creation, editing, and deletion – to anyone who has access to that script. </p><p>Now, you can create account-level secrets and variables that can be shared across all Workers scripts, centrally managed and protected within the Secrets Store. </p>
    <div>
      <h2>Building a secure secrets manager</h2>
      <a href="#building-a-secure-secrets-manager">
        
      </a>
    </div>
    <p>The most important feature of a Secret Store, of course, is to make sure that your secrets are stored securely. </p><p>Once the secret is created, its value will not be readable by anyone, be it developers, admins, or Cloudflare employees. Only the permitted service will be able to use the value at runtime. </p><p>This is why the first thing that happens when you deploy a new secret to Cloudflare is encrypting the secret prior to storing it in our database. We make sure your tokens are safe and protected using a two-level key hierarchy, where the root key never leaves a secure system. This is done by making use of DEKs (Data Encryption Keys) to encrypt your secrets and a separate KEK (Key Encryption Key) to encrypt the DEKs themselves. The data encryption keys are refreshed frequently, making the possibility and impact scope of a single DEK exposure very small. In the future, we will introduce periodic key rotations for our KEK and also provide a way for customers to have their own account-specific DEKs.</p><p>After the secrets are encrypted, there are two permissions checks when deploying a secret from the Secrets Store to a Worker. First, the user must have sufficient permissions to create the binding. Second, when the Worker makes a <code>fetch</code> call to retrieve the secret value, we verify that the Worker has an appropriate binding to access that secret. </p><p>The secrets are automatically propagated across our network using <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale/"><u>Quicksilver</u></a> – so that every secret is on every server– to ensure they’re immediately accessible and ready for the Worker to use. Wherever your Worker is deployed, your secrets will be, too. </p><p>If you’d like to use a secret to secure your AI model keys before passing on to AI Gateway: </p>
            <pre><code>export default {
 async fetch(request, env, ctx) {
   const prompt = "Write me a pun about Cloudflare";
   const openAIkey = await env.open_AI_key.get();

   const response = await fetch("https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_TAG/openai/chat/completions", {
     method: "POST",
     headers: {
       "Content-Type": "application/json",
       "Authorization": `Bearer ${openAIkey}`,
     },
     body: JSON.stringify({
       model: "gpt-3.5-turbo",
       messages: [
         { role: "user", content: prompt }
       ],
       temperature: 0.8,
       max_tokens: 100,
     }),
   });

   const data = await response.json();
   const answer = data.choices?.[0]?.message?.content || "No pun found 😢";

   return new Response(answer, {
     headers: { "Content-Type": "text/plain" },
   });
 }
};
</code></pre>
            
    <div>
      <h2>Cloudflare Secrets Store, with built-in RBAC</h2>
      <a href="#cloudflare-secrets-store-with-built-in-rbac">
        
      </a>
    </div>
    <p>Now, a secret’s value can be updated once and applied everywhere — but not by everyone. Cloudflare Secrets Store uses <a href="https://www.cloudflare.com/learning/access-management/role-based-access-control-rbac/"><u>role-based access control (RBAC)</u></a> to ensure that only those with permission can view, create, edit, or delete secrets. Additionally, any changes to the Secrets Store are recorded in the <a href="https://developers.cloudflare.com/logs/reference/log-fields/account/audit_logs/"><u>audit logs</u></a>, allowing you to track changes. </p><p>Whereas per-Worker secrets are tied to the Workers account role, meaning that anyone who can modify the Worker can modify the secret, access to account-level secrets is restricted with more granular controls. This allows for differentiation between security admins who manage secrets and developers who use them in the code.</p><div>
    <figure>
        <table>
            <colgroup>
                <col></col>
                <col></col>
                <col></col>
                <col></col>
            </colgroup>
            <tbody>
                <tr>
                    <td> </td>
                    <td>
                        <p><span><span>Secrets Store Admin</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Secrets Store Reporter</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Secrets Store Deployer</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Create secrets</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                    <td> </td>
                    <td> </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Update secrets</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                    <td> </td>
                    <td> </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Delete secrets</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                    <td> </td>
                    <td> </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>View secrets metadata</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Deploy secrets (i.e. bind to a Worker)</span></span></p>
                    </td>
                    <td>
                        <p> </p>
                    </td>
                    <td> </td>
                    <td>
                        <p><span><span>✓</span></span></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </figure>
</div><p>Each secret can also be scoped to a particular Cloudflare product to ensure the value is only used where it is meant to be. Today, the secrets are restricted to Workers by default, but once the Secrets Store supports multiple products, you’ll be able to specify where the secret can be used (e.g. “I only want this secret to be accessible through Firewall Rules”). </p>
    <div>
      <h2>What’s next for Secrets Store</h2>
      <a href="#whats-next-for-secrets-store">
        
      </a>
    </div>
    <p>Secrets Store will support all secrets across Cloudflare, including:</p><ul><li><p>Cloudflare Access has <a href="https://developers.cloudflare.com/cloudflare-one/identity/service-tokens/"><u>service tokens</u></a> to authenticate against your Zero Trust policies.</p></li><li><p><a href="https://developers.cloudflare.com/rules/transform/"><u>Transform Rules</u></a> require sensitive values in the request headers to grant access or pass onto to something else.</p></li><li><p><a href="https://developers.cloudflare.com/ai-gateway/"><u>AI Gateway</u></a> relies upon secret keys from each provider to position Cloudflare between the end user and the AI model. </p></li></ul><p>…and more! </p><p>Right now, to use a secret within a Worker, you have to create a binding for that specific secret. In the future, we’ll allow you to create a binding to the store itself so that the Worker can access any secret within that store. We’ll also allow customers to create multiple secret stores within their account so that they can manage secrets by group when creating access policies. </p><p>Every Cloudflare account can create up to twenty secrets for free. We’re currently finalizing our pricing and will publish more details for each tier soon.</p><p>We’re thrilled to get Secrets Store into our customers’ hands and are excited to continue building it out to support more products and features as we work towards making Secrets Store GA.</p>
    <div>
      <h2>Try it out today! </h2>
      <a href="#try-it-out-today">
        
      </a>
    </div>
    <p>Cloudflare Secrets Store with the Workers integration is <a href="http://dash.cloudflare.com/?to=/:account/secrets-store"><u>available for all customers via UI</u></a> and API today. For instructions to get started in the Cloudflare dashboard, take a look at our <a href="https://developers.cloudflare.com/secrets-store/"><u>developer documentation</u></a>. </p><p>If you have any feedback or feature requests, we’d love for you to share those with us on this <a href="https://docs.google.com/forms/d/e/1FAIpQLSejhdh-0x2C0OHdVz9xabGYww3PWtOOZ1MwNLARZIt3s5ioYg/viewform?usp=header"><u>Google form</u></a>. </p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Beta]]></category>
            <category><![CDATA[Secrets Store]]></category>
            <guid isPermaLink="false">3ctRz9zcwJFS3GuxmXchlS</guid>
            <dc:creator>Mia Malden</dc:creator>
            <dc:creator>Mitali Rawat</dc:creator>
            <dc:creator>James Vaughan</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Snippets are now Generally Available]]></title>
            <link>https://blog.cloudflare.com/snippets/</link>
            <pubDate>Wed, 09 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Snippets are now generally available, enabling fast, cost-free JavaScript-based HTTP traffic modifications across all paid plans.  ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>Program your traffic at the edge — fast, flexible, and free</h2>
      <a href="#program-your-traffic-at-the-edge-fast-flexible-and-free">
        
      </a>
    </div>
    <p><a href="https://developers.cloudflare.com/rules/snippets/"><u>Cloudflare Snippets</u></a> are <b>now generally available (GA)</b> for all paid plans, giving you a fast, flexible way to control HTTP traffic using lightweight JavaScript “code rules” — at no extra cost.</p><p>Need to transform headers dynamically, fine-tune <a href="https://www.cloudflare.com/learning/cdn/what-is-caching/">caching</a>, rewrite URLs, retry failed requests, replace expired links, throttle suspicious traffic, or validate authentication tokens? Snippets provide a production-ready solution built for performance, security, and control.</p><p>With GA, we’re introducing a new <a href="https://developers.cloudflare.com/changelog/2025-01-29-snippets-code-editor/"><u>code editor</u></a> to streamline writing and testing logic. This summer, we’re also rolling out an integration with <a href="https://blog.cloudflare.com/secrets-store-beta/"><u>Secrets Store</u></a> — enabling you to bind and manage sensitive values like API keys directly in Snippets, securely and at scale.</p>
    <div>
      <h2>What are Snippets?</h2>
      <a href="#what-are-snippets">
        
      </a>
    </div>
    <p>Snippets bring the power of JavaScript to <a href="https://developers.cloudflare.com/rules/"><u>Cloudflare Rules</u></a>, letting you write logic that runs before a request reaches your origin or after a response returns from upstream. They’re ideal when built-in rule actions aren’t quite enough. While Cloudflare Rules let you define traffic logic without code, Snippets extend that model with greater flexibility for advanced scenarios.</p><p>Think of Snippets as the ultra-fast <b>“code layer” </b>of <a href="https://developers.cloudflare.com/rules/"><u>Cloudflare Rules</u></a>: the <a href="https://developers.cloudflare.com/ruleset-engine/"><u>Ruleset Engine</u></a> evaluates your rules and invokes your code, which then runs on the <a href="https://developers.cloudflare.com/workers/runtime-apis/"><u>Workers runtime</u></a>.</p>
    <div>
      <h3>Key capabilities of Snippets:</h3>
      <a href="#key-capabilities-of-snippets">
        
      </a>
    </div>
    <ul><li><p><b>Ultra-fast execution</b>: optimized for speed with the <a href="https://developers.cloudflare.com/ruleset-engine/"><u>Ruleset Engine</u></a> and <a href="https://developers.cloudflare.com/workers/runtime-apis/"><u>Workers runtime</u></a>.</p></li><li><p><b>Granular request matching</b>: trigger Snippets based on <a href="https://developers.cloudflare.com/ruleset-engine/rules-language/fields/reference/http.request.full_uri/"><u>URI</u></a>, <a href="https://developers.cloudflare.com/ruleset-engine/rules-language/fields/reference/http.user_agent/"><u>user-agent</u></a>, <a href="https://developers.cloudflare.com/ruleset-engine/rules-language/fields/reference/http.request.cookies/"><u>cookies</u></a>, headers and more.</p></li><li><p><b>Sequential execution</b>: <a href="https://developers.cloudflare.com/rules/snippets/how-it-works/"><u>run</u></a> multiple Snippets on the same request, applying modifications step by step.</p></li><li><p>Native <a href="https://developers.cloudflare.com/rules/"><u>Cloudflare Rules</u></a> integration: Snippets <a href="https://developers.cloudflare.com/ruleset-engine/reference/phases-list/#request-phases"><u>inherit</u></a> request modifications from other Cloudflare products.</p></li><li><p>JavaScript and Web APIs support, plus essential <a href="https://developers.cloudflare.com/workers/runtime-apis/"><u>Workers runtime</u></a> features:</p><ul><li><p><a href="https://developers.cloudflare.com/workers/runtime-apis/fetch/"><u>fetch API</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers/runtime-apis/cache/"><u>cache API</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties"><u>request.cf object</u></a></p></li><li><p><a href="https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/"><u>HTMLRewriter</u></a></p></li></ul></li><li><p>Automated deployment and versioning via <a href="https://developers.cloudflare.com/rules/snippets/create-terraform/"><u>Terraform</u></a>.</p></li></ul><p>Best of all? Snippets are <a href="https://developers.cloudflare.com/rules/snippets/#availability"><u>included</u></a> at no extra cost for Pro, Business, and Enterprise plans — with <b>no usage-based fees</b>.</p>
    <div>
      <h2>The journey to GA: How Snippets became production-grade</h2>
      <a href="#the-journey-to-ga-how-snippets-became-production-grade">
        
      </a>
    </div>
    <p>Cloudflare Snippets started as a bold idea: bring the power of JavaScript-based logic to Cloudflare Rules, without the complexity of a full-stack developer platform.</p><p>Over the past two years, Snippets have evolved into a production-ready “code rules” solution, shaping the future of HTTP traffic control.</p><p><b>2022:</b> Cloudflare Snippets were <a href="https://blog.cloudflare.com/snippets-announcement/"><u>announced</u></a> during Developer Week as a solution for users needing flexible HTTP traffic modifications without a full Worker.</p><p><b>2023:</b> <b>Alpha launch </b>— hundreds of users tested Snippets for high-performance traffic logic.</p><p><b>2024:</b> <b>7x traffic growth</b>, processing 17,000 requests per second. Terraform support and production-grade backend were released.</p><p><b>2025:</b> <b>General Availability </b>— Snippets introduces a <a href="https://developers.cloudflare.com/changelog/2025-01-29-snippets-code-editor/"><u>new code editor</u></a>, <a href="https://developers.cloudflare.com/changelog/2025-02-12-rules-upgraded-limits/"><u>increased limits</u></a> alongside other Cloudflare Rules products, integration with <a href="https://developers.cloudflare.com/fundamentals/trace-request/#_top"><u>Trace</u></a>, and a production-grade experience built for scale, handling <b>over 2 million requests per second</b> at peak. Integration with the Secrets Store is rolling out this summer.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1fZv7cpSGntSQadS761Zkv/31707075a85d393f4883b190599581f7/1.png" />
          </figure>
    <div>
      <h2>New: Snippets + Trace</h2>
      <a href="#new-snippets-trace">
        
      </a>
    </div>
    <p><a href="https://developers.cloudflare.com/fundamentals/trace-request/#_top"><u>Cloudflare Trace</u></a> now shows exactly which Snippets were triggered on a request. This makes it easier to debug traffic behavior, verify logic execution, and understand how your Snippets interact with other products in the request pipeline.</p><p>Whether you’re fine-tuning header logic or troubleshooting a routing issue, Trace gives you real-time insight into how your edge logic behaves in production.</p><div>
  
</div>
<p></p>
    <div>
      <h2>Coming soon: Snippets + Secrets Store</h2>
      <a href="#coming-soon-snippets-secrets-store">
        
      </a>
    </div>
    <p>In the third quarter, you’ll be able to securely access API keys, authentication tokens, and other sensitive values from <a href="https://blog.cloudflare.com/secrets-store-beta/"><u>Secrets Store</u></a> directly in your Snippets. No more plaintext secrets in your code, no more workarounds.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/51QyhWrvtGEQ568lzuqRD/f8eb60c0258d28ef1431fad7f5d03b12/2.png" />
          </figure><p>Once rolled out, secrets can be configured for Snippets via the <a href="https://developers.cloudflare.com/rules/snippets/create-dashboard/"><u>dashboard</u></a> or <a href="https://developers.cloudflare.com/rules/snippets/create-api/"><u>API</u></a> under the new <b>“Settings”</b> button.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2ipprc9qz2mPpp7XGOURgH/47112dde677906746ef4f798d5c082c0/3.png" />
          </figure>
    <div>
      <h2>When to use Snippets vs. Cloudflare Workers</h2>
      <a href="#when-to-use-snippets-vs-cloudflare-workers">
        
      </a>
    </div>
    <p>Snippets are fast, flexible, and free, but how do they compare to Cloudflare Workers? Both allow you to programmatically control traffic. However, they solve different problems:</p><div>
    <figure>
        <table>
            <colgroup>
                <col></col>
                <col></col>
                <col></col>
            </colgroup>
            <tbody>
                <tr>
                    <td>
                        <p><span><span><strong>Feature</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>Snippets</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>Workers</strong></span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Execute scripts based on request attributes (headers, geolocation, cookies, etc.)</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                    <td>
                        <p><span><span>❌</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Modify HTTP requests/responses or serve a different response</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Add, remove, or rewrite headers dynamically</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Cache</span></span><span><span> assets at the edge</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Route traffic dynamically between origins</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Authenticate</span></span><span><span> requests, pre-sign URLs, run A/B testing</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Perform compute-intensive tasks (e.g., AI inference, image processing)</span></span></p>
                    </td>
                    <td>
                        <p><span><span>❌</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Store persistent data (e.g., KV, Durable Objects, D1)</span></span></p>
                    </td>
                    <td>
                        <p><span><span>❌</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Deploy via CLI (Wrangler)</span></span></p>
                    </td>
                    <td>
                        <p><span><span>❌</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>Use TypeScript, Python, Rust or other programming languages</span></span></p>
                    </td>
                    <td>
                        <p><span><span>❌</span></span></p>
                    </td>
                    <td>
                        <p><span><span>✅</span></span></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </figure>
</div><p><b>Use Snippets when:</b></p><ul><li><p>You need ultra-fast conditional traffic modifications directly on Cloudflare’s network.</p></li><li><p>You want to extend Cloudflare Rules beyond built-in actions.</p></li><li><p>You need free, unlimited invocations within the <a href="https://developers.cloudflare.com/rules/snippets/#limits"><u>execution limits</u></a>.</p></li><li><p>You are migrating from VCL, Akamai’s EdgeWorkers, or on-premise logic.</p></li></ul><p><b>Use Workers when:</b></p><ul><li><p>Your application requires state management, Developer Platform product integrations, or high compute limits.</p></li><li><p>You are building APIs, full-stack applications, or complex workflows.</p></li><li><p>You need logging, debugging tools, CLI support, and gradual rollouts.</p></li></ul><p>Still unsure? Check out our <a href="https://developers.cloudflare.com/rules/snippets/when-to-use/"><u>detailed guide</u></a> for best practices.</p>
    <div>
      <h2>Snippets in action: real-world use cases</h2>
      <a href="#snippets-in-action-real-world-use-cases">
        
      </a>
    </div>
    <p>Below are practical use cases demonstrating Snippets. Each script can be dynamically triggered using our powerful <a href="https://developers.cloudflare.com/ruleset-engine/rules-language/"><u>Rules</u></a> language, so you can granularly control which requests your Snippets will be applied to.</p>
    <div>
      <h3>1. Dynamically modify headers</h3>
      <a href="#1-dynamically-modify-headers">
        
      </a>
    </div>
    <p>Inject custom headers, remove unnecessary ones, and tweak values on the fly:</p>
            <pre><code>export default {
  async fetch(request) {
    const timestamp = Date.now().toString(16); // convert timestamp to HEX
    const modifiedRequest = new Request(request, { headers: new Headers(request.headers) });
    modifiedRequest.headers.set("X-Hex-Timestamp", timestamp); // send HEX timestamp to upstream

    const response = await fetch(modifiedRequest);
    const newResponse = new Response(response.body, response); // make response from upstream mutable

    newResponse.headers.append("x-snippets-hello", "Hello from Cloudflare Snippets"); // add new response header
    newResponse.headers.delete("x-header-to-delete"); // delete response header
    newResponse.headers.set("x-header-to-change", "NewValue"); // replace the value of existing response header

    return newResponse;
  },
};</code></pre>
            
    <div>
      <h3>2. Serve a custom maintenance page</h3>
      <a href="#2-serve-a-custom-maintenance-page">
        
      </a>
    </div>
    <p>Route traffic to a maintenance page when your origin is undergoing planned maintenance:</p>
            <pre><code>export default {
    async fetch(request) { // for all matching requests, return predefined HTML response with 503 status code
        return new Response(`
            &lt;!DOCTYPE html&gt;
            &lt;html lang="en"&gt;
            &lt;head&gt;
                &lt;meta charset="UTF-8"&gt;
                &lt;title&gt;We'll Be Right Back!&lt;/title&gt;
                &lt;style&gt; body { font-family: Arial, sans-serif; text-align: center; padding: 20px; } &lt;/style&gt;
            &lt;/head&gt;
            &lt;body&gt;
                &lt;h1&gt;We'll Be Right Back!&lt;/h1&gt;
                &lt;p&gt;Our site is undergoing maintenance. Check back soon!&lt;/p&gt;
            &lt;/body&gt;
            &lt;/html&gt;
        `, { status: 503, headers: { "Content-Type": "text/html" } });
    }
};</code></pre>
            
    <div>
      <h3>3. Retry failed requests to a backup origin</h3>
      <a href="#3-retry-failed-requests-to-a-backup-origin">
        
      </a>
    </div>
    <p>Ensure reliability by automatically rerouting requests when your primary origin returns an unexpected response:</p>
            <pre><code>export default {
  async fetch(request) {
    const response = await fetch(request); // send original request to the origin

    if (!response.ok &amp;&amp; !response.redirected) { // if response is not 200 OK or a redirect, send to another origin
      const newRequest = new Request(request); // clone the original request to construct a new request
      newRequest.headers.set("X-Rerouted", "1"); // add a header to identify a re-routed request at the new origin
      const url = new URL(request.url); // clone the original URL
      url.hostname = "backup.example.com"; // send request to a different origin / hostname
      return await fetch(url, newRequest); // serve response from the backup origin
    }

    return response; // otherwise, serve response from the primary origin
  },
};</code></pre>
            
    <div>
      <h3>4. Redirect users based on their location</h3>
      <a href="#4-redirect-users-based-on-their-location">
        
      </a>
    </div>
    <p>Send visitors to region-specific sites for better localization:</p>
            <pre><code>export default {
    async fetch(request) {
        const country = request.cf.country; // identify visitor's country using request.cf property
        const redirectMap = { US: "https://example.com/us", EU: "https://example.com/eu" }; // define redirects for each country
        if (redirectMap[country]) return Response.redirect(redirectMap[country], 301); // redirect on match
        return fetch(request); // otherwise, proceed to upstream normally
    }
};</code></pre>
            
    <div>
      <h2>Getting started with Snippets</h2>
      <a href="#getting-started-with-snippets">
        
      </a>
    </div>
    <p>Snippets are available right now in the Cloudflare dashboard under <b>Rules &gt; Snippets</b>:</p><ol><li><p>Go to Rules → Snippets.</p></li><li><p>Use prebuilt <a href="https://developers.cloudflare.com/rules/examples/"><u>templates</u></a> or write your own JavaScript code.</p></li><li><p>Configure a flexible rule to trigger your Snippet.</p></li><li><p>Test and deploy instantly.</p></li><li><p>Automate via <a href="https://developers.cloudflare.com/rules/snippets/create-api/"><u>API</u></a> or <a href="https://developers.cloudflare.com/rules/snippets/create-terraform/"><u>Terraform</u></a>.</p></li></ol>
    <div>
      <h2>Try Snippets today</h2>
      <a href="#try-snippets-today">
        
      </a>
    </div>
    <p>Cloudflare Snippets are now generally available, bringing fast, cost-free, and intelligent HTTP traffic control to all paid plans.</p><p>With native integration into Cloudflare Rules and Terraform — and Secrets Store integration coming this summer — Snippets provide the most efficient way to manage advanced traffic logic at scale.</p><p>Explore Snippets in the Cloudflare Dashboard and start optimizing your traffic with lightweight, flexible rules that enhance performance and reduce complexity.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Snippets]]></category>
            <category><![CDATA[General Availability]]></category>
            <category><![CDATA[JavaScript]]></category>
            <category><![CDATA[CDN]]></category>
            <category><![CDATA[Edge Rules]]></category>
            <category><![CDATA[Developers]]></category>
            <guid isPermaLink="false">6rBnmM6UiFX8ca7XXlOU2X</guid>
            <dc:creator>Nikita Cano</dc:creator>
        </item>
        <item>
            <title><![CDATA[Network performance update: Developer Week 2025]]></title>
            <link>https://blog.cloudflare.com/network-performance-update-developer-week-2025/</link>
            <pubDate>Wed, 09 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare has been tracking and comparing our speed with other top networks since 2021. Let’s take a look at how things have changed since our last update. ]]></description>
            <content:encoded><![CDATA[ <p>As the Internet has become enmeshed in our everyday lives, so has our need for speed. No one wants to wait when adding shoes to our shopping carts, or accessing corporate assets from across the globe. And as the Internet supports more and more of our critical infrastructure, speed becomes more than just a measure of how quickly we can place a takeout order. It becomes the connective tissue between the systems that keep us safe, healthy, and organized. Governments, financial institutions, healthcare ecosystems, transit — they increasingly rely on the Internet. This is why at Cloudflare, building the fastest network is our north star. </p><p>We’re happy to announce that we are the fastest network in 48% of the top 1000 networks by 95th percentile TCP connection time between November 2024, and March 2025, up from 44% in September 2024.</p><p>In this post, we’re going to share with you how our network performance has changed since our <a href="https://blog.cloudflare.com/network-performance-update-birthday-week-2024/"><u>last post in September 2024</u></a>, and talk about what makes us faster than other networks.  But first, let’s talk a little bit about how we get this data.</p>
    <div>
      <h2>How does Cloudflare get this data?</h2>
      <a href="#how-does-cloudflare-get-this-data">
        
      </a>
    </div>
    <p>It’s happened to all of us — you casually click on a site, and suddenly you’ve reached a Cloudflare-branded error page. While you are shaking your fist at the sky, something interesting is happening on the back end. Cloudflare is using <a href="https://www.w3.org/TR/user-timing/"><u>Real User Monitoring (RUM)</u></a> to collect the data used to compare our performance against other networks. The monitoring we do is slightly different than the <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/"><u>RUM Cloudflare offers</u></a> to customers. When the error page loads, a 100 KB file is fetched and loaded. This file is hosted on networks like Cloudflare, Akamai, Amazon CloudFront, Fastly, and Google Cloud CDN. Your browser processes the performance data, and sends it to Cloudflare, where we use it to get a clear view of how these different networks stack up in terms of speed. </p><p>We’ve been collecting and refining this data since June 2021.  You can read more about how we collect that data <a href="https://blog.cloudflare.com/benchmarking-edge-network-performance/"><u>here</u></a>, and we regularly <a href="https://blog.cloudflare.com/tag/network-performance-update/"><u>track our performance</u></a> during Innovation Weeks to hold ourselves accountable to you that we are always in pursuit of being the fastest network in the world.</p>
    <div>
      <h2>How are we doing?</h2>
      <a href="#how-are-we-doing">
        
      </a>
    </div>
    <p>In order to evaluate Cloudflare’s speed relative to others, we measure performance across the top 1000 “eyeball” networks using the list provided by the <a href="https://stats.labs.apnic.net/cgi-bin/aspop?c=IN"><u>Asia Pacific Network Information Centre (APNIC)</u></a>. So-called “eyeball” networks are those with a large concentration of subscribers/end users.  This information is important, because it gives us signals for where we can expand our presence or peering, or optimize our traffic engineering. When benchmarking, we assess the 95th percentile TCP connection time. This is the time it takes a user to establish a TCP connection to the server they are trying to reach. This metric helps us illustrate how Cloudflare’s network makes your traffic faster by serving your customers as locally as possible. </p><p>When we look at Cloudflare’s performance across the top 1000 networks, we can see that we’re fastest in 487, or over 48%, of these networks, between November 2024 and March 2025:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2vkfABpKwZtd7FJf5BU4lz/c2a778435be9b2c47656753cdb39e8f0/1.png" />
          </figure><p>In <a href="https://blog.cloudflare.com/network-performance-update-birthday-week-2024/"><u>September 2024</u></a>, we ranked #1 in 44% of these networks:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/105vHx9riLNO4Fgm5XvxnL/4b7d106b84d90bcc674c3fb54043593c/2.png" />
          </figure><p>So why did we jump?  To get a better understanding of why, let’s take a look at the countries where we improved, which will give us a better sense of where to dive in.  This is what our network map looked like in <a href="https://blog.cloudflare.com/network-performance-update-birthday-week-2024/"><u>September 2024</u></a> (grey countries mean we do not have enough data or users to derive insights):</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5IfSvKcdYDsTE2Rl2WPLpE/1814ef571b8622c83ff6817b41102cf5/3.png" />
          </figure><p>(September 2024)</p><p>Today, using those same 95th percentile TCP connect times, we rank #1 in 48% of networks and the network map looks like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/xYWPvT0dQH7eCxbqNSrqv/e758b2961faad0cd5e1d1d6a72351131/4.png" />
          </figure><p>(March 2025)</p><p>We made most of our gains in Africa, where countries that previously didn’t have enough samples saw an increase in samples, and Cloudflare pulled ahead. This could mean that there was either an increase in Cloudflare users, or an increase in error pages shown. These countries got faster almost exclusively due to the presence of our <a href="https://blog.cloudflare.com/how-cloudflare-helps-next-generation-markets/"><u>Edge Partner deployments</u></a>, which are Cloudflare locations embedded in last mile networks.  In next-generation markets like many African countries, these locations are crucial towards being faster as connectivity to end users tends to fall back to places like South Africa or London if in-country peering does not exist.</p><p>But let’s take a look at a couple of other places and see why we got faster.</p><p>In Canada, we were not the fastest in September 2024, but we are the fastest today. Today, we are the fastest in 40% of networks, which is the most out of all of our competitors:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6bWdN0wG9g1LhujV4lY5Ne/5cdaa76a27cacc487622c45ab0ea38cd/5.png" />
          </figure><p>But when you look at the overall country numbers, we see that the race for the fastest network is quite close:</p><div>
    <figure>
        <table>
            <colgroup>
                <col></col>
                <col></col>
                <col></col>
                <col></col>
            </colgroup>
            <tbody>
                <tr>
                    <td>
                        <p><span><span><strong>Canada 95th Percentile TCP Connect Time by Provider</strong></span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span><strong>Rank</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>Entity</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>Connect Time (P95)</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>#1 Diff</strong></span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>1</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Cloudflare</span></span></p>
                    </td>
                    <td>
                        <p><span><span>179 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>-</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>2</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Fastly</span></span></p>
                    </td>
                    <td>
                        <p><span><span>180 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>+0.48% (+0.87 ms)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>3</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Google</span></span></p>
                    </td>
                    <td>
                        <p><span><span>180 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>+0.74% (+1.32 ms)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>4</span></span></p>
                    </td>
                    <td>
                        <p><span><span>CloudFront</span></span></p>
                    </td>
                    <td>
                        <p><span><span>182 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>+1.74% (+3.11 ms)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>5</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Akamai</span></span></p>
                    </td>
                    <td>
                        <p><span><span>215 ms </span></span></p>
                    </td>
                    <td>
                        <p><span><span>+20% (+36 ms)</span></span></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </figure>
</div><p>The difference between Cloudflare and the third-fastest network is a little over a millisecond!  As we’ve <a href="https://blog.cloudflare.com/network-performance-update-birthday-week-2024/"><u>pointed out previously</u></a>, such fluctuations are quite common, especially at higher percentiles.  But there is still a significant difference between us and the slowest network; we’re around 20% faster.</p><p>However, looking at a place like Japan where were not the fastest in September 2024 but are now the fastest, there is a significant difference between Cloudflare and the number two network:</p><div>
    <figure>
        <table>
            <colgroup>
                <col></col>
                <col></col>
                <col></col>
                <col></col>
            </colgroup>
            <tbody>
                <tr>
                    <td>
                        <p><span><span><strong>Japan 95th Percentile TCP Connect Time by Provider</strong></span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span><strong>Rank</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>Entity</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>Connect Time (P95)</strong></span></span></p>
                    </td>
                    <td>
                        <p><span><span><strong>#1 Diff</strong></span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>1</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Cloudflare</span></span></p>
                    </td>
                    <td>
                        <p><span><span>116 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>-</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>2</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Fastly</span></span></p>
                    </td>
                    <td>
                        <p><span><span>122 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>+5.23% (+6.08 ms)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>3</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Google</span></span></p>
                    </td>
                    <td>
                        <p><span><span>124 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>+6.21% (+7.22 ms)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>4</span></span></p>
                    </td>
                    <td>
                        <p><span><span>CloudFront</span></span></p>
                    </td>
                    <td>
                        <p><span><span>127 ms</span></span></p>
                    </td>
                    <td>
                        <p><span><span>+8.91% (+10 ms)</span></span></p>
                    </td>
                </tr>
                <tr>
                    <td>
                        <p><span><span>5</span></span></p>
                    </td>
                    <td>
                        <p><span><span>Akamai</span></span></p>
                    </td>
                    <td>
                        <p><span><span>153 ms </span></span></p>
                    </td>
                    <td>
                        <p><span><span>+32% (+37 ms)</span></span></p>
                    </td>
                </tr>
            </tbody>
        </table>
    </figure>
</div><p>Why is this? We are in more locations in Japan than our competitors and added more Edge Partner deployments in these locations, bringing us even closer to end-users. Edge Partner deployments are collaborations with ISPs, where we take space in their data centers, and peer with them directly. </p>
    <div>
      <h2>Why?</h2>
      <a href="#why">
        
      </a>
    </div>
    <p>Why do we track our network performance like this? The answer is simple: to improve user experience. This data allows us to track a key performance metric for Cloudflare and the other networks. When we see that we’re lagging in a region, it serves as a signal to dig deeper into our network. </p><p>This data is a gold mine for the teams tasked with improving Cloudflare’s network. When there are countries where Cloudflare is behind, it gives us signals for where we should expand or investigate. If we’re slow, we may need to invest in additional peering. If a region we have invested in heavily is slower, we may need to investigate our hardware.  The example from Japan shows exactly how this can benefit: we took a location where we were previously on par with our competitors, added peering in new locations, and we pulled ahead. </p><p>On top of this map, we have <a href="https://www.cloudflare.com/learning/network-layer/what-is-an-autonomous-system/"><u>autonomous system (ASN)</u></a> level granularity on how we are performing on each one of the top 1000 eyeball networks, and we continuously optimize our traffic flow with each of them.  This allows us to track individual networks that may lag and improve the customer experience in those networks through turning up peering, or even adding new deployments in those regions. </p>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We’re sharing our updates on our journey to become #1 everywhere so that you can see what goes into running the fastest network in the world. From here, our plan is the same as always: identify where we’re slower, fix it, and then tell you how we’ve gotten faster.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[Network Performance Update]]></category>
            <guid isPermaLink="false">2O9xvScPSeNZVBqldw8qgs</guid>
            <dc:creator>Emily Music</dc:creator>
            <dc:creator>Onur Karaagaoglu</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing Workers Observability: logs, metrics, and queries – all in one place]]></title>
            <link>https://blog.cloudflare.com/introducing-workers-observability-logs-metrics-and-queries-all-in-one-place/</link>
            <pubDate>Wed, 09 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ We’ve improved Observability for Workers by announcing the General Availability of Workers Logs and the introduction of the Query Builder to help you investigate log events across all of your Workers. ]]></description>
            <content:encoded><![CDATA[ <p>We’re excited to announce <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/observability"><u>Workers Observability</u></a> – a new section in the Cloudflare Dashboard that allows you to query detailed log events across all Workers in your account to extract deeper insights.</p><p>In 2024, <a href="https://blog.cloudflare.com/cloudflare-acquires-baselime-expands-observability-capabilities/"><u>we set out to build</u></a> the best first-party <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a> for any cloud platform. Since then, we’ve improved metrics reporting for all resources, launched <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/#enable-workers-logs"><u>Workers Logs</u></a> to automatically ingest and store logs for Workers, and rebuilt real-time logs with improved filtering. However, observability insights have been limited to a single Worker.</p><p>Starting today, you can use Workers Observability to understand what is happening across all of your Workers:</p><ul><li><p><b>Workers Metrics Dashboard (Beta)</b>: A single dashboard to view metrics and logs from all of your Workers </p></li><li><p><b>Query Builder (Beta)</b>: Construct structured queries to explore your logs, extract metrics from logs, create graphical and tabular visualizations, and save queries for faster future investigations.</p></li><li><p><b>Workers Logs: </b>Now Generally Available, with a public API and improved invocation-based grouping.</p></li></ul>
    <div>
      <h2>Building queries </h2>
      <a href="#building-queries">
        
      </a>
    </div>
    <p>The <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/observability/investigate"><u>Query Builder</u></a> allows you to interact with your logs, and answer the “why” to any question you have. You can find it by navigating to <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/observability/investigate"><u>Workers &amp; Pages &gt; Observability</u></a> in the dashboard.</p><p>Using the Query Builder, you can now answer more questions than ever. For example, this query shows the p90 wall time for 200 OK responses from the <code>/reference</code> endpoint is 6 milliseconds.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5e9jb6m6GC8FGTKo5rBL5b/062e49ab6fb103fb04eecb310564d9be/1.png" />
          </figure><p>The key components to structuring a query in the Query Builder are:</p><ul><li><p><b>Visualizations</b>: An aggregate function like average, count, percentile, or unique that performs a calculation on a group of values to return a single value. Each aggregate function returns a graph visualization and a summary table.</p></li><li><p><b>Filters</b>: A condition that allows you to exclude data not matching the criteria.</p></li><li><p><b>Search</b>: A condition that only returns the data matching the specified string.</p></li><li><p><b>Group by</b>: A function to collapse a field into only its distinct values, allowing you to more granularly apply aggregate functions.</p></li><li><p><b>Order by</b>: A sorting function to order the returned rows.</p></li><li><p><b>Limits</b>: A cap on the number of returned rows, allowing you to focus on what is important.</p></li></ul><p>The Query Builder relies on structured logs for efficient indexed queries and extracting metrics from logs. Workers Observability natively supports and encourages structured logs. Structured logs store context-rich metadata as key-value pairs in the form of distinct fields (<i>high dimensionality</i>), each with many potential unique values (<i>high cardinality</i>). <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/#invocation-logs"><u>Invocation Logs</u></a>, which can be enabled in your Worker, contain deep insights from Cloudflare’s network, and are a great example of a structured log. By logging important metadata as a structured log, you empower yourself to answer questions about your system that you couldn’t predict when writing the code. </p><p>Internally at Cloudflare, we’ve already found tremendous value from this new product. During development, the Workers Observability team was able to use the Query Builder to discover a bug in the Workers Observability team’s staging environment. A query on the number of the events per script returned the following response:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1sRNUDTGbXApY2Q3uZQtjY/1cbbaaaddd20fcdbda917c83bf4aa8ac/2.png" />
          </figure><p>After mapping this drop in recorded events against recent staging deployments, the team was able to isolate and root cause the introduction of the bug. Along with fixing the bug, the team also introduced new staging alerts to prevent errors like this from going unnoticed.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1TwPVhwUMQ8GxnmBPqT3iw/506a218e2032af50af044497d45cccf4/3.png" />
          </figure><p>Queries built with the Query Builder or Workers Logs can be saved with a custom name and description. You can star your favorite queries, and also share them with your teammates using a shareable link, making it easier than ever to debug together and invest in developing visualizations from your telemetry data.</p>
    <div>
      <h2>CPU time and wall time</h2>
      <a href="#cpu-time-and-wall-time">
        
      </a>
    </div>
    <p>You can now monitor CPU time and wall time for every Workers invocation across all of our observability offerings, including <a href="https://developers.cloudflare.com/workers/observability/logs/tail-workers/"><u>Tail Workers</u></a>, <a href="https://developers.cloudflare.com/workers/observability/logs/logpush/"><u>Workers Logpush</u></a>, and <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/"><u>Workers Logs</u></a>. These metrics help show how much time is spent executing code compared to the total elapsed time for the invocation, including I/O time.</p><p>For example, using the CPU time and wall time surfaced in the <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/#invocation-logs"><u>Invocation Log</u></a>, you can use the Query Builder to show the p90 CPU time and wall time traffic for a single Worker script.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2648VpAWbtcgv03G7ebprZ/341c8ccae1968bffc4d1b7465fac46e9/4.png" />
          </figure>
    <div>
      <h2>Revamped Workers metrics</h2>
      <a href="#revamped-workers-metrics">
        
      </a>
    </div>
    <p>In February, we <a href="https://developers.cloudflare.com/changelog/2025-02-03-workers-metrics-revamp/"><u>released</u></a> a new view into your Workers’ metrics to help you monitor your gradual deployments with improved visualizations. Today, we are also launching a new Workers Metrics overview page in the <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/observability"><u>Observability tab</u></a>. Now you can easily compare metrics across Workers and understand the current state of your deployments, all from a single view.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1bTzYhOR4ZsgSzHLuH3HaR/375e092634b158c531861bc926c5a95f/observability.png" />
          </figure>
    <div>
      <h2>Invocations view</h2>
      <a href="#invocations-view">
        
      </a>
    </div>
    <p>Invocations are mechanisms to trigger the execution of a Worker or Durable Object in response to an event, such as an alarm, cron job, or a fetch. </p><p>When the Worker or Durable Object executes, log events are emitted. To date, we have surfaced logs in an events view where each log is ordered by the time it was published. </p><p>We’re now introducing an Invocations View, so you can group and view all logs from each invocation. These views are available in each Worker’s view and the <a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages/observability"><u>Workers Observability tab</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/tHySCeAAAQkR90hiKFA5m/c78c432516cdb7f0efa78657ad3f06b6/6.png" />
          </figure>
    <div>
      <h2>Workers Observability API</h2>
      <a href="#workers-observability-api">
        
      </a>
    </div>
    <p>You can now use the <a href="https://developers.cloudflare.com/api/resources/workers/subresources/observability/"><u>Workers Observability API</u></a> to programmatically retrieve your telemetry data and populate the tool of your choice.</p><p>The API allows you to automate, integrate, and customize in ways that our dashboard may not. For example, you may want to analyze your logs in a notebook or correlate your Workers logs with logs from a different source.  Leveraging the Workers Observability API can help you optimize your monitoring strategy, automate repetitive tasks, and improve flexibility in how you interact with your telemetry data.</p>
    <div>
      <h2>Enable Workers Logs today</h2>
      <a href="#enable-workers-logs-today">
        
      </a>
    </div>
    <p>To use <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/#enable-workers-logs"><u>Workers Logs</u></a>, enable it in your <a href="https://dash.cloudflare.com/?to=/:account/workers/services/view/:worker/production/observability/logs/"><u>Workers’ settings</u></a> in the dashboard or add the following configuration to your Workers’ wrangler file:</p>
            <pre><code># wrangler.jsonc

{
  "observability": {
    "enabled": true,
    "logs": {
      "invocation_logs": true,
      "head_sampling_rate": 1
    }
  }
}</code></pre>
            <p>We’re just getting started. We have lots in store to help make Cloudflare’s developer observability best-in-class. Join us in <a href="https://discord.gg/MynNkhX8"><u>Discord</u></a> in the #workers-observability channel for feedback and feature requests.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[General Availability]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Workers Logs]]></category>
            <category><![CDATA[Workers Observability]]></category>
            <guid isPermaLink="false">65QaUAKt8EldeRcxwpefN2</guid>
            <dc:creator>Rohin Lohe</dc:creator>
        </item>
        <item>
            <title><![CDATA[Your frontend, backend, and database — now in one Cloudflare Worker]]></title>
            <link>https://blog.cloudflare.com/full-stack-development-on-cloudflare-workers/</link>
            <pubDate>Tue, 08 Apr 2025 14:05:00 GMT</pubDate>
            <description><![CDATA[ You can now deploy static sites and full-stack applications on Cloudflare Workers. Framework support for React Router v7, Astro, Vue and more are generally available today and Cloudflare Vite plugin.  ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://blog.cloudflare.com/builder-day-2024-announcements/#static-asset-hosting"><u>In September 2024</u></a>, we introduced beta support for <a href="https://www.cloudflare.com/developer-platform/solutions/hosting/">hosting</a>, storing, and serving <a href="https://developers.cloudflare.com/workers/static-assets/"><u>static assets</u></a> for free on <a href="https://www.cloudflare.com/developer-platform/products/workers/">Cloudflare Workers</a> — something that was previously only possible on <a href="https://blog.cloudflare.com/cloudflare-pages/"><u>Cloudflare Pages</u></a>. Being able to host these assets — your client-side JavaScript, HTML, CSS, fonts, and images — was a critical missing piece for developers looking to build a full-stack application within a <b>single Worker</b>. </p><p>Today we’re announcing ten big improvements to building apps on Cloudflare. All together, these new additions allow you to build and host projects ranging from simple static sites to full-stack applications, all on Cloudflare Workers:</p><ul><li><p>Cloudflare Workers now provides production ready, <b>generally available</b> (GA) support for <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/remix/"><u>React Router v7 (Remix)</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/astro/"><u>Astro</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/hono/"><u>Hono</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/vue/"><u>Vue.js</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/nuxt/"><u>Nuxt</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/svelte/"><u>Svelte (SvelteKit)</u></a>, and <a href="https://developers.cloudflare.com/workers/frameworks/"><u>more</u></a>, with GA support for more frameworks including <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/"><u>Next.js</u></a>, <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/angular/"><u>Angular</u></a>, and <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/solid/"><u>SolidJS</u></a> (SolidStart) to follow in Q2 2025. </p></li><li><p>You can build complete full-stack apps on Workers without a framework: you can “<a href="https://blog.cloudflare.com/introducing-the-cloudflare-vite-plugin/"><u>just use Vite</u></a>" and React together, and build a backend API in the same Worker. See our <a href="https://github.com/cloudflare/templates/tree/staging/vite-react-template"><u>Vite + React template</u></a> for an example.</p></li><li><p>The adapter for Next.js — <a href="https://opennext.js.org/cloudflare"><u>@opennextjs/cloudflare</u></a>, introduced in September 2024 as an early alpha, <a href="https://blog.cloudflare.com/deploying-nextjs-apps-to-cloudflare-workers-with-the-opennext-adapter"><u>is now v1.0-beta</u></a>, and will be GA in the coming weeks. Those using the OpenNext adapter will also be able to easily upgrade to the <a href="https://github.com/vercel/next.js/discussions/77740"><u>recently announced Next.js Deployments API</u></a>. </p></li><li><p>The <a href="https://blog.cloudflare.com/introducing-the-cloudflare-vite-plugin"><u>Cloudflare Vite plugin</u></a> is now v1.0 and generally available. The Vite plugin allows you to run Vite’s development server in the Workers runtime (<code>workerd</code>), meaning you get all the benefits of Vite, including <a href="https://vite.dev/guide/features.html#hot-module-replacement"><u>Hot Module Replacement</u></a>, while still being able to use features that are exclusive to Workers (like Durable Objects).</p></li><li><p>You can now use static <a href="https://developers.cloudflare.com/workers/static-assets/headers/"><u>_headers</u></a> and <a href="https://developers.cloudflare.com/workers/static-assets/redirects/"><u>_redirects</u></a> configuration files for your applications on Workers, something that was previously only available on Pages. These files allow you to add simple headers and configure redirects without executing any Worker code. </p></li><li><p>In addition to <a href="https://developers.cloudflare.com/hyperdrive/configuration/connect-to-postgres/"><u>PostgreSQL</u></a>, you can now connect to <a href="https://blog.cloudflare.com/building-global-mysql-apps-with-cloudflare-workers-and-hyperdrive"><u>MySQL databases in addition from Cloudflare Workers, via Hyperdrive</u></a>. Bring your existing Planetscale, AWS, GCP, Azure, or other MySQL database, and Hyperdrive will take care of pooling connections to your database and eliminating unnecessary roundtrips by caching queries.</p></li><li><p><a href="#node-js-compatibility"><u>More Node.js APIs are available</u></a> in the Workers Runtime — including APIs from the <code>crypto</code>, <code>tls</code>, <code>net</code>, and <code>dns </code>modules. We’ve also increased the maximum CPU time for a Workers request from 30 seconds to 5 minutes.</p></li><li><p>You can now <a href="https://blog.cloudflare.com/deploy-workers-applications-in-seconds"><u>bring any repository from GitHub or GitLab that contains a Worker application</u></a>, and <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a> will take care of deploying the app as a new Worker on your account. <a href="#workers-builds"><u>Workers Builds is also starting much more quickly</u></a> (by up to 6 seconds for every build). </p></li><li><p>You can now set up Workers Builds to <a href="https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds"><u>run on non-production branches</u></a>, and preview URLs will be <a href="https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment"><u>posted back to GitHub as a comment</u></a>. </p></li><li><p>The <a href="https://developers.cloudflare.com/images/transform-images/bindings/"><u>Images binding in Workers</u></a> is generally available, allowing you to build more flexible, programmatic workflows. </p></li></ul><p>These improvements allow you to build both simple static sites and more complex server-side rendered applications. Like <a href="https://www.cloudflare.com/developer-platform/products/pages/">Pages</a>, you only get charged when your Worker code runs, meaning you can host and serve static sites for free. When you want to do any rendering on the server or need to build an API, simply add a Worker to handle your backend. And when you need to read or write data in your app, you can connect to an existing database with <a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a>, or use any of our storage solutions: <a href="https://developers.cloudflare.com/kv/"><u>Workers KV</u></a>, <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>, <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, or <a href="https://developers.cloudflare.com/d1/"><u>D1</u></a>. </p><p>If you'd like to dive straight into code, you can deploy a single-page application built with Vite and React, with the option to connect to a hosted database with Hyperdrive, by clicking this “Deploy to Cloudflare” button: </p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/react-postgres-fullstack-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/56hrgUpOebvOdzbI8j6liw/bc9e7d01dde8cb6a8a4623aec3abc883/1.jpg" />
          </figure>
    <div>
      <h2>Start with Workers</h2>
      <a href="#start-with-workers">
        
      </a>
    </div>
    <p>Previously, you needed to choose between building on Cloudflare Pages or Workers (or use Pages for one part of your app, and Workers for another) just to get started. This meant figuring out what your app needed from the start, and hoping that if your project evolved, you wouldn’t be stuck with the wrong platform and architecture. Workers was designed to be a flexible platform, allowing developers to evolve projects as needed — and so, we’ve <a href="https://blog.cloudflare.com/pages-and-workers-are-converging-into-one-experience/"><u>worked to bring pieces of Pages into Workers</u></a> over the years.  </p><p>Now that Workers supports both serving static assets <b>and </b>server-side rendering, you should <b>start with Workers</b>. Cloudflare Pages will continue to be supported, but, going forward, all of our investment, optimizations, and feature work will be dedicated to improving Workers. We aim to make Workers the best platform for building full-stack apps, building upon your feedback of what went well with Pages and what we could improve. </p><p>Before, building an app on Pages meant you got a really easy, opinionated on-ramp, but you’d eventually hit a wall if your application got more complex. If you wanted to use Durable Objects to manage state, you would need to set up an entirely separate Worker to do so, ending up with a complicated deployment and more overhead. You also were limited to real-time logs, and could only roll out changes all in one go. </p><p>When you build on Workers, you can immediately bind to any other Developer Platform service (including <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>, <a href="https://developers.cloudflare.com/email-routing/email-workers/"><u>Email Workers</u></a>, and more), and manage both your front end and back end in a single project — all with a single deployment. You also get the whole suite of <a href="https://developers.cloudflare.com/workers/observability/"><u>Workers observability</u></a> tooling built into the platform, such as <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/"><u>Workers Logs</u></a>. And if you want to rollout changes to only a certain percentage of traffic, you can do so with <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/"><u>Gradual Deployments</u></a>.  </p><p>These latest improvements are part of our goal to bring the best parts of Pages into Workers. For example, we now support static  <a href="https://developers.cloudflare.com/workers/static-assets/headers/"><u>_headers</u></a> and <a href="https://developers.cloudflare.com/workers/static-assets/redirects/"><u>_redirects</u></a> config files, so that you can easily take an existing project from Pages (or another platform) and move it over to Workers, without needing to change your project. We also directly integrate with GitHub and GitLab with <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a>, providing automatic builds and deployments. And starting today, <a href="https://developers.cloudflare.com/workers/configuration/previews/"><u>Preview URLs</u></a> are <a href="https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment"><u>posted back to your repository as a comment</u></a>, with feature branch aliases and environments coming soon. </p><p>To learn how to migrate an existing project from Pages to Workers, read our <a href="https://developers.cloudflare.com/workers/static-assets/migrate-from-pages/"><u>migration guide</u></a>. </p><p>Next, let’s talk about how you can build applications with different rendering modes on Workers.  </p>
    <div>
      <h2>Building static sites, SPAs, and SSR on Workers</h2>
      <a href="#building-static-sites-spas-and-ssr-on-workers">
        
      </a>
    </div>
    <p>As a quick primer, here are all the architectures and rendering modes we’ll be discussing that are supported on Workers: </p><ul><li><p><b>Static sites</b>: When you visit a static site, the server immediately returns pre-built static assets — HTML, CSS, JavaScript, images, and fonts. There’s no dynamic rendering happening on the server at request-time. Static assets are typically generated at build-time and served directly from a <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/"><u>CDN</u></a>, making static sites fast and easily cacheable. This approach works well for sites with content that rarely changes. </p></li><li><p><b>Single-Page Applications (SPAs)</b>:  When you load an SPA, the server initially sends a minimal HTML shell and a JavaScript bundle (served as static assets). Your browser downloads this JavaScript, which then takes over to render the entire user interface client-side. After the initial load, all navigation occurs without full-page refreshes, typically via client-side routing. This creates a fast, app-like experience. </p></li><li><p><b>Server-Side Rendered (SSR) applications</b>: When you first visit a site that uses SSR, the server generates a fully-rendered HTML page on-demand for that request. Your browser immediately displays this complete HTML, resulting in a fast first page load. Once loaded, JavaScript "<a href="https://en.wikipedia.org/wiki/Hydration_(web_development)"><u>hydrates</u></a>" the page, adding interactivity. Subsequent navigations can either trigger new server-rendered pages or, in many modern frameworks, transition into client-side rendering similar to an SPA.</p></li></ul><p>Next, we’ll dive into how you can build these kinds of applications on Workers, starting with setting up your development environment. </p>
    <div>
      <h3>Setup: build and dev</h3>
      <a href="#setup-build-and-dev">
        
      </a>
    </div>
    <p>Before uploading your application, you need to bundle all of your client-side code into a directory of <b>static assets</b>. Wrangler bundles and builds your code when you run <code>wrangler dev</code>, but we also now support Vite with our <a href="https://www.npmjs.com/package/@cloudflare/vite-plugin"><u>new Vite plugin</u></a>. This is a great option for those already using Vite’s build tooling and development server — you can continue developing (and testing with <a href="https://developers.cloudflare.com/workers/testing/vitest-integration/"><u>Vitest</u></a>) using Vite’s development server, all using the Workers runtime. </p><p>To get started using the Cloudflare Vite plugin, you can scaffold a React application using Vite and our plugin, by running: </p>
            <pre><code>npm create cloudflare@latest my-react-app -- --framework=react</code></pre>
            <p>When you open the project, you should see a directory structure like this: </p>
            <pre><code>...
├── api
│   └── index.ts
├── public
│   └── ...
├── src
│   └── ...
...
├── index.html
├── package.json
├── vite.config.ts
└── wrangler.jsonc</code></pre>
            <p>If you run <code>npm run build</code>, you’ll see a new folder appear, named <code>/dist</code>. </p>
            <pre><code>...
├── api
│   └── index.ts
├── dist
│   └── ...
├── public
│   └── ...
├── src
│   └── ...
...
├── index.html
├── package.json
├── vite.config.ts
└── wrangler.jsonc</code></pre>
            <p>The Vite plugin informs Wrangler that this <code>/dist</code> directory contains the project’s built static assets — which, in this case, includes client-side code, some CSS files, and images. </p><p>Once deployed, this single-page application (SPA) architecture will look something like this: </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6syBGxC8udJi8wlsqvLTXW/526d05095a953cb6d29526abfd4f4b3a/2.jpg" />
          </figure><p>When a request comes in, Cloudflare looks at the pathname and automatically serves any static assets that match that pathname. For example, if your static assets directory includes a <code>blog.html</code> file, requests for <code>example.com/blog</code> get that file. </p>
    <div>
      <h3>Static sites</h3>
      <a href="#static-sites">
        
      </a>
    </div>
    <p>If you have a static site created by a static site generator (SSG) like <a href="https://docs.astro.build/en/concepts/why-astro/"><u>Astro</u></a>, all you need to do is create a <code>wrangler.jsonc</code> file (or <code>wrangler.toml</code>) and tell Cloudflare where to find your built assets: </p>
            <pre><code>// wrangler.jsonc 

{
  "name": "my-static-site",
  "compatibility_date": "2025-04-01",
  "assets": {
    "directory": "./dist",
  }
}</code></pre>
            <p>Once you’ve added this configuration, you can simply build your project and run wrangler deploy.  Your entire site will then be uploaded and ready for traffic on Workers. Once deployed and requests start flowing in, your static site will be <a href="https://developers.cloudflare.com/workers/static-assets/#caching-behavior"><u>cached across Cloudflare’s network</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/OaFnoUfvhzfjwv537fh5S/763524c2ed3b2beb61304576723667ea/3.jpg" />
          </figure><p>You can try starting a fresh Astro project on Workers today by running:</p>
            <pre><code>npm create cloudflare@latest my-astro-app -- --framework=astro</code></pre>
            <p>You can see our other supported Frameworks and how to get started in our <a href="https://developers.cloudflare.com/workers/frameworks/"><u>framework guides</u></a>. </p>
    <div>
      <h3>Single-page applications (SPAs) </h3>
      <a href="#single-page-applications-spas">
        
      </a>
    </div>
    <p>If you have a single-page application, you can explicitly enable <code>single-page-application</code> mode in your Wrangler configuration: </p>
            <pre><code>{
 "name": "example-spa-worker-hyperdrive",
 "main": "api/index.js",
 "compatibility_flags": ["nodejs_compat"],
 "compatibility_date": "2025-04-01",
 },
 "assets": {
   "directory": "./dist",
   "binding": "ASSETS",
   "not_found_handling": "single-page-application"
 },
 "hyperdrive": [
   {
     "binding": "HYPERDRIVE",
     "id": "d9c9cfb2587f44ee9b0730baa692ffec",
     "localConnectionString": "postgresql://myuser:mypassword@localhost:5432/mydatabase"
   }
 ],
 "placement": {
   "mode": "smart"
 }
}</code></pre>
            <p>By enabling this, the platform assumes that any navigation request (requests which include a <code>Sec-Fetch-Mode: navigate</code> header) are intended for static assets and will serve up <code>index.html</code> whenever a matching static asset match cannot be found. For non-navigation requests (such as requests for data) that don't match a static asset, Cloudflare will invoke the Worker script. With this setup, you can render the frontend with React, use a Worker to handle back-end operations, and use Vite to help stitch the two together. This is a great option for porting over older SPAs built with <code>create-react-app</code>, <a href="https://react.dev/blog/2025/02/14/sunsetting-create-react-app"><u>which was recently sunset</u></a>. </p><p>Another thing to note in this Wrangler configuration file: we’ve defined a Hyperdrive binding and enabled <a href="https://developers.cloudflare.com/workers/configuration/smart-placement/"><u>Smart Placement</u></a>. Hyperdrive lets us use an existing database<i> and</i> handles connection pooling. This solves a long-standing challenge of connecting Workers (which run in a highly distributed, serverless environment) directly to traditional databases. By design, Workers operate in lightweight V8 isolates with no persistent TCP sockets and a strict CPU/memory limit. This isolation is great for security and speed, but it makes it difficult to hold open database connections. Hyperdrive addresses these constraints by acting as a “bridge” between Cloudflare’s network and your database, taking care of the heavy lifting of maintaining stable connections or pools so that Workers can reuse them.  By turning on Smart Placement, we also ensure that if requests to our Worker originate far from the database (causing latency), Cloudflare can choose to relocate both the Worker—which handles the database connection—and the Hyperdrive “bridge” to a location closer to the database, ​​reducing round-trip times. </p>
    <div>
      <h4>SPA example: Worker code</h4>
      <a href="#spa-example-worker-code">
        
      </a>
    </div>
    <p>Let’s look at the <a href="https://github.com/korinne/example-spa-worker"><u>“Deploy to Cloudflare” example</u></a> at the top of this blog. In <code>api/index.js</code>, we’ve defined an API (using Hono) which connects to a hosted database through Hyperdrive. </p>
            <pre><code>import { Hono } from "hono";
import postgres from "postgres";
import booksRouter from "./routes/books";
import bookRelatedRouter from "./routes/book-related";

const app = new Hono();

// Setup SQL client middleware
app.use("*", async (c, next) =&gt; {
 // Create SQL client
 const sql = postgres(c.env.HYPERDRIVE.connectionString, {
   max: 5,
   fetch_types: false,
 });

 c.env.SQL = sql;

 // Process the request
 await next();

 // Close the SQL connection after the response is sent
 c.executionCtx.waitUntil(sql.end());
});

app.route("/api/books", booksRouter);
app.route("/api/books/:id/related", bookRelatedRouter);


export default {
 fetch: app.fetch,
};</code></pre>
            <p>When deployed, our app’s architecture looks something like this: </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/56hrgUpOebvOdzbI8j6liw/bc9e7d01dde8cb6a8a4623aec3abc883/1.jpg" />
          </figure><p>If Smart Placement moves the placement of my Worker to run closer to my database, it could look like this: </p><div>
  
</div>
<p></p>
    <div>
      <h3>Server-Side Rendering (SSR)</h3>
      <a href="#server-side-rendering-ssr">
        
      </a>
    </div>
    <p>If you want to handle rendering on the server, we support a number of popular full-stack <a href="https://developers.cloudflare.com/workers/frameworks/"><u>frameworks</u></a>. </p><p>Here’s a version of our previous example, now using React Router v7’s server-side rendering:</p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/react-router-postgres-ssr-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p><p>You could also use Next.js with the <a href="https://opennext.js.org/cloudflare"><u>OpenNext adapter</u></a>, or any other <a href="https://developers.cloudflare.com/workers/frameworks/"><u>framework listed in our framework guides</u></a>. </p>
    <div>
      <h2>Deploy to Workers, with as few changes as possible</h2>
      <a href="#deploy-to-workers-with-as-few-changes-as-possible">
        
      </a>
    </div>
    
    <div>
      <h3>Node.js compatibility</h3>
      <a href="#node-js-compatibility">
        
      </a>
    </div>
    <p>We’ve also continued to make progress supporting Node.js APIs, recently adding support for the <code>crypto</code>, <code>tls</code>, <code>net</code>, and <code>dns</code> modules. This allows existing applications and libraries that rely on these Node.js modules to run on Workers. Let’s take a look at an example:</p><p>Previously, if you tried to use the <code>mongodb</code> package, you encountered the following error:</p>
            <pre><code>Error: [unenv] dns.resolveTxt is not implemented yet!</code></pre>
            <p>This occurred when <code>mongodb</code> used the <code>node:dns</code> module to do a DNS lookup of a hostname. Even if you avoided that issue, you would have encountered another error when <code>mongodb</code> tried to use <code>node:tls</code> to securely connect to a database.</p><p>Now, you can use <code>mongodb</code> as expected because <code>node:dns</code> and <code>node:tls</code> are supported. The same can be said for libraries relying on <code>node:crypto</code> and <code>node:net</code>.</p><p>Additionally, Workers <a href="https://developers.cloudflare.com/changelog/2025-03-11-process-env-support/"><u>now expose environment variables and secrets on the process.env object</u></a> when the <code>nodejs_compat</code> compatibility flag is on and the compatibility date is set to <code>2025-04-01</code> or beyond. Some libraries (and developers) assume that this object will be populated with variables, and rely on it for top-level configuration. Without the tweak, libraries may have previously broken unexpectedly and developers had to write additional logic to handle variables on Cloudflare Workers.</p><p>Now, you can just access your variables as you would in Node.js.</p>
            <pre><code>const LOG_LEVEL = process.env.LOG_LEVEL || "info";</code></pre>
            
    <div>
      <h3>Additional Worker CPU time</h3>
      <a href="#additional-worker-cpu-time">
        
      </a>
    </div>
    <p>We have also <a href="https://developers.cloudflare.com/changelog/2025-03-25-higher-cpu-limits/"><u>raised the maximum CPU time per Worker request</u></a> from 30 seconds to 5 minutes. This allows for compute-intensive operations to run for longer without timing out. Say you want to use the newly supported <code>node:crypto</code> module to hash a very large file, you can now do this on Workers without having to rely on external compute for CPU-intensive operations.</p>
    <div>
      <h3>Workers Builds </h3>
      <a href="#workers-builds">
        
      </a>
    </div>
    <p>We’ve also made improvements to <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a>, which allows you to connect a Git repository to your Worker, so that you can have automatic builds and deployments on every pushed change. Workers Builds was introduced during <a href="https://blog.cloudflare.com/builder-day-2024-announcements/#continuous-integration-and-delivery"><u>Builder Day 2024</u></a>, and initially only allowed you to connect a repository to an existing Worker. Now, you can bring a repository and <a href="https://blog.cloudflare.com/deploy-workers-applications-in-seconds/"><u>immediately deploy it as a new Worker</u></a>, reducing the amount of setup and button clicking needed to bring a project over. We’ve improved the performance of Workers Builds by reducing the latency of build starts by <b>6 seconds</b> — they now start within <b>10 seconds</b> on average. We also boosted API responsiveness, achieving a <b>7x </b>latency improvement thanks to Smart Placement. </p><ul><li><p><b>Note</b>: On April 2, 2025, Workers Builds transitioned to a new pricing model, as announced during <a href="https://blog.cloudflare.com/builder-day-2024-announcements/"><u>Builder Day 2024</u></a>. Free plan users are now capped at 3,000 minutes of build time, and Workers Paid subscription users will have a new usage-based model with 6,000 free minutes included and $0.005 per build minute pricing after. To better support concurrent builds, Paid plans will also now get six (6) concurrent builds, making it easier to work across multiple projects and monorepos. For more information on pricing, see the <a href="https://developers.cloudflare.com/workers/ci-cd/builds/limits-and-pricing/"><u>documentation</u></a>.</p></li></ul><p>You can also set up Workers Builds to <a href="https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/#configure-non-production-branch-builds"><u>run on non-production branches</u></a>, and preview URLs will be <a href="https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/github-integration/#pull-request-comment"><u>posted back to GitHub as a comment</u></a>. </p>
    <div>
      <h3>Bind the Images API to your Worker</h3>
      <a href="#bind-the-images-api-to-your-worker">
        
      </a>
    </div>
    <p>Last week, we wrote a <a href="https://blog.cloudflare.com/improve-your-media-pipelines-with-the-images-binding-for-cloudflare-workers/"><u>blog post</u></a> that covers how the Images binding enables more flexible, programmatic workflows for image optimization.</p><p>Previously, you could access image optimization features by calling <code>fetch()</code> in your Worker. This method requires the original image to be retrievable by URL. However, you may have cases where images aren’t accessible from a URL, like when you want to compress user-uploaded images before they are uploaded to your storage. With the Images binding, you can directly optimize an image by operating on its body as a stream of bytes.</p><p>To learn more, read our guide on <a href="https://developers.cloudflare.com/images/tutorials/optimize-user-uploaded-image"><u>transforming an image before it gets uploaded to R2</u></a>.</p>
    <div>
      <h2>Start building today</h2>
      <a href="#start-building-today">
        
      </a>
    </div>
    <p>We’re excited to see what you’ll build, and are focused on new features and improvements to make it  easier to create any application on Workers. Much of this work was made even better by community feedback, and we encourage everyone to <a href="https://discord.com/invite/cloudflaredev"><u>join our Discord</u></a> to participate in the discussion. </p><p><b>Helpful resources to get you started:</b></p><ul><li><p><a href="https://developers.cloudflare.com/workers/frameworks/"><u>Framework guides</u></a> </p></li><li><p><a href="https://developers.cloudflare.com/workers/static-assets/migrate-from-pages/"><u>Migration guide</u></a> </p></li><li><p><a href="https://developers.cloudflare.com/workers/static-assets/"><u>Static assets documentation</u></a> </p></li><li><p><a href="https://developers.cloudflare.com/workers/vite-plugin"><u>Cloudflare Vite plugin documentation</u></a></p></li></ul><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Front End]]></category>
            <category><![CDATA[Full Stack]]></category>
            <category><![CDATA[General Availability]]></category>
            <category><![CDATA[Cloudflare Pages]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[MySQL]]></category>
            <category><![CDATA[Hyperdrive]]></category>
            <guid isPermaLink="false">67CgcpMED2Rw0BozjKbdUz</guid>
            <dc:creator>Korinne Alpers</dc:creator>
        </item>
        <item>
            <title><![CDATA[Deploy your Next.js app to Cloudflare Workers with the Cloudflare adapter for OpenNext]]></title>
            <link>https://blog.cloudflare.com/deploying-nextjs-apps-to-cloudflare-workers-with-the-opennext-adapter/</link>
            <pubDate>Tue, 08 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ With the 1.0-beta release of the Cloudflare adapter for OpenNext, you can host your Next.js 14 and 15 applications on Cloudflare Workers. ]]></description>
            <content:encoded><![CDATA[ <p>We first announced the Cloudflare adapter for OpenNext at <a href="https://blog.cloudflare.com/builder-day-2024-announcements/#cloudflare-joins-opennext"><u>Builder Day 2024</u></a>. It transforms Next.js applications to enable them to run on Cloudflare’s infrastructure.</p><p>Over the seven months since that September announcement, we have been working hard to improve the adapter. It is now more tightly integrated with OpenNext to enable supporting many more Next.js features. We kept improving the <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>Node.js compatibility</u></a> of Workers and <a href="https://github.com/unjs/unenv"><u>unenv</u></a> was also improved to <a href="https://developer.mozilla.org/en-US/docs/Glossary/Polyfill"><u>polyfill</u></a> the Node.js features not yet implemented by the runtime.</p><p>With all of this work, we are proud to announce the 1.0.0-beta release of <a href="https://www.npmjs.com/package/@opennextjs/cloudflare"><u>@opennextjs/cloudflare</u></a>. Using the Cloudflare adapter is now the preferred way to deploy Next applications to the Cloudflare platform, instead of <a href="https://blog.cloudflare.com/next-on-pages/"><u>Next on Pages</u></a>.</p><p>Read on to learn what is possible today, and about our plans for the coming months.</p>
    <div>
      <h2>OpenNext</h2>
      <a href="#opennext">
        
      </a>
    </div>
    <p><a href="https://opennext.js.org/"><u>OpenNext</u></a> is a build tool designed to transform Next.js applications into packages optimized for deployment across various platforms. Initially created for serverless environments on AWS Lambda, OpenNext has expanded its capabilities to support a wider range of environments, including Cloudflare Workers and traditional Node.js servers.</p><p>By integrating with the OpenNext codebase, the Cloudflare adapter is now able to support many more features than its original version. We are also leveraging the end-to-end (e2e) test suite of OpenNext to validate the implementation of these features.

Being part of OpenNext allows us to support future Next.js features shortly after they are released. We intend to support the latest minor version of Next.js 14 and all the minor versions of Next.js 15.</p>
    <div>
      <h2>Features</h2>
      <a href="#features">
        
      </a>
    </div>
    <p>Most of the Next.js 15 features are supported in <a href="https://www.npmjs.com/package/@opennextjs/cloudflare"><code><u>@opennextjs/cloudflare</u></code></a>. You can find an exhaustive list on the <a href="https://opennext.js.org/cloudflare#supported-nextjs-features"><u>OpenNext website</u></a>, but here are a few highlights:</p><table><tr><td><p><a href="https://nextjs.org/docs/app/building-your-application/caching"><u>Caching</u></a></p></td><td><p>The Cloudflare adapter provides a cache handler for Next.js, optimizing the management of ISR/SSG and the data cache to speed up your apps.</p></td></tr><tr><td><p><a href="https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering"><u>Partial Prerendering (PPR)</u></a></p></td><td><p>PPR immediately sends the pre-rendered HTML and begins streaming from the server in parallel.</p></td></tr><tr><td><p><a href="https://nextjs.org/docs/app/building-your-application/routing/middleware"><u>Middleware</u></a></p></td><td><p>Middleware allows modifying the response by rewriting, redirecting, or modifying the request and response headers, or responding directly before the request hits the app.</p></td></tr><tr><td><p><a href="https://nextjs.org/docs/app"><u>App</u></a> and <a href="https://nextjs.org/docs/pages"><u>Pages</u></a> routers</p></td><td><p>Both the new App router and Pages routers are supported.</p></td></tr><tr><td><p><a href="https://nextjs.org/docs/pages/building-your-application/optimizing/images"><u>Image Optimization</u></a></p></td><td><p>The adapter easily integrates with <a href="https://developers.cloudflare.com/images/"><u>Cloudflare Images</u></a> to deliver optimized images.</p></td></tr></table><p>We are working on adding more features:</p><ul><li><p>Microsoft Windows is not yet fully supported by the adapter. We plan to fully support Windows for development in the 1.0 release.</p></li><li><p>The adapter currently only supports the Node runtime of Next.js. You can opt-out of the <a href="https://nextjs.org/docs/app/api-reference/edge"><u>Edge runtime</u></a> by removing <code>export const runtime = "edge"</code> from your application. We plan to add support for the edge runtime in the next major release. Note that applications deployed to Cloudflare Workers run close to the user, whatever the Next.js runtime used, giving similar performance.</p></li><li><p>Composable caching (<code>use cache</code>) should also be supported in the next major release. It is a canary feature of Next.js that is still in development. It will be supported in OpenNext once it stabilizes.</p></li></ul>
    <div>
      <h2>Evolution in the ecosystem</h2>
      <a href="#evolution-in-the-ecosystem">
        
      </a>
    </div>
    <p>While the adapter has vastly improved over the last several months, we should also mention the updates to the ecosystem that are enabling more applications to be supported.</p><p><a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>NodeJS compatibility</u></a> for Workers is becoming more comprehensive with the <code>crypto</code>, <code>dns</code>, <code>timers</code>, <code>tls</code>, and <code>net</code> NodeJS modules now being natively implemented by the Workers runtime. The remaining modules that are not yet implemented are supported through <a href="https://github.com/unjs/unenv"><u>unenv</u></a>.</p><p>The Worker size limit <a href="https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits"><u>was bumped</u></a> from 1 MiB to 3 MiB on free plans and from 10 MiB to 15 MiB for paid plans. </p>
    <div>
      <h2>1.0 and the road ahead</h2>
      <a href="#1-0-and-the-road-ahead">
        
      </a>
    </div>
    <p>With the release of 1.0-beta, we expect most Next.js 14 and 15 applications to be able to run seamlessly on Cloudflare.</p><p>We have already tackled a lot of the issues reported on <a href="https://github.com/opennextjs/opennextjs-cloudflare"><u>GitHub</u></a> by early adopters, and once the adapter stabilizes, we will release the 1.0 version.</p><p>After that, we are planning a v2 release with a focus on:</p><ul><li><p>Reducing the bundle size.</p></li><li><p>Improving the application performance. The reduced bundle size and more work on the caching layer will make applications faster.</p></li><li><p>Allowing users to deploy to multiple Workers.</p></li></ul>
    <div>
      <h2>Deploy your first application to Workers</h2>
      <a href="#deploy-your-first-application-to-workers">
        
      </a>
    </div>
    <p>Developing and deploying a Next.js app on Workers is pretty simple, and you can do it today by following these steps:</p><p>Start by creating your application from a template:</p>
            <pre><code>npm create cloudflare@latest -- my-next-app --framework=next 
--platform=workers</code></pre>
            <p>You can then iterate on your application using the Next.js dev server by running <code>npm run dev</code>.</p><p>Once you are happy with your application in the development server, you can run the application on Workers locally by executing <code>npm run preview</code>, or deploy the application with <code>npm run deploy</code>.</p><div>
  
</div>
<p></p><p>You can find more details in the documentation, on both the <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/"><u>Cloudflare site</u></a> and the <a href="https://opennext.js.org/cloudflare"><u>OpenNext site</u></a>.</p><p>We want your feedback! Report issues and contribute code at <a href="https://github.com/opennextjs/opennextjs-cloudflare/"><u>opennextjs/opennextjs-cloudflare on Github</u></a>, and join the discussion on the <a href="https://discord.gg/WUNsBM69"><u>OpenNext Discord</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <guid isPermaLink="false">2xXeB9HQ7cEwSs5NT84Wrx</guid>
            <dc:creator>Dario Piotrowicz</dc:creator>
            <dc:creator>Victor Berchet</dc:creator>
            <dc:creator>Nicolas (Guest Author)</dc:creator>
            <dc:creator>James Anderson (Guest Author)</dc:creator>
        </item>
        <item>
            <title><![CDATA[Pools across the sea: how Hyperdrive speeds up access to databases and why we’re making it free]]></title>
            <link>https://blog.cloudflare.com/how-hyperdrive-speeds-up-database-access/</link>
            <pubDate>Tue, 08 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Hyperdrive, Cloudflare's global connection pooler, relies on some key innovations to make your database connections work. Let's dive deeper, in celebration of its availability for Free Plan customers. ]]></description>
            <content:encoded><![CDATA[ <p></p><p></p>
    <div>
      <h2>Free as in beer</h2>
      <a href="#free-as-in-beer">
        
      </a>
    </div>
    <p>In acknowledgement of its pivotal role in building distributed applications that rely on regional databases, we’re making Hyperdrive available on the free plan of Cloudflare Workers!</p><p><a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a> enables you to build performant, global apps on Workers with <a href="https://developers.cloudflare.com/hyperdrive/examples/"><u>your existing SQL databases</u></a>. Tell it your database connection string, bring your existing drivers, and Hyperdrive will make connecting to your database faster. No major <a href="https://www.cloudflare.com/learning/cloud/how-to-refactor-applications/">refactors</a> or convoluted configuration required.</p><p>Over the past year, Hyperdrive has become a key service for teams that want to build their applications on Workers and connect to SQL databases. This includes our own engineering teams, with Hyperdrive serving as the tool of choice to connect from Workers to our own Postgres clusters for many of the control-plane actions of our billing, <a href="https://www.cloudflare.com/developer-platform/products/d1/"><u>D1</u></a>, <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>R2</u></a>, and <a href="https://www.cloudflare.com/developer-platform/products/workers-kv/"><u>Workers KV</u></a> teams (just to name a few). </p><p>This has highlighted for us that Hyperdrive is a fundamental building block, and it solves a common class of problems for which there isn’t a great alternative. We want to make it possible for everyone building on Workers to connect to their database of choice with the best performance possible, using the drivers and frameworks they already know and love.</p>
    <div>
      <h3>Performance is a feature</h3>
      <a href="#performance-is-a-feature">
        
      </a>
    </div>
    <p>To illustrate how much Hyperdrive can improve your application’s performance, let’s write the world’s simplest benchmark. This is obviously not production code, but is meant to be reflective of a common application you’d bring to the Workers platform. We’re going to use a simple table, a very popular OSS driver (<a href="https://github.com/porsager/postgres"><u>postgres.js</u></a>), and run a standard OLTP workload from a Worker. We’re going to keep our origin database in London, and query it from Chicago (those locations will come back up later, so keep them in mind).</p>
            <pre><code>// This is the test table we're using
// CREATE TABLE IF NOT EXISTS test_data(userId bigint, userText text, isActive bool);

import postgres from 'postgres';

let direct_conn = '&lt;direct connection string here!&gt;';
let hyperdrive_conn = env.HYPERDRIVE.connectionString;

async function measureLatency(connString: string) {
	let beginTime = Date.now();
	let sql = postgres(connString);

	await sql`INSERT INTO test_data VALUES (${999}, 'lorem_ipsum', ${true})`;
	await sql`SELECT userId, userText, isActive FROM test_data WHERE userId = ${999}`;

	let latency = Date.now() - beginTime;
	ctx.waitUntil(sql.end());
	return latency;
}

let directLatency = await measureLatency(direct_conn);
let hyperdriveLatency = await measureLatency(hyperdrive_conn);</code></pre>
            <p>The code above</p><ol><li><p>Takes a standard database connection string, and uses it to create a database connection.</p></li><li><p>Loads a user record into the database.</p></li><li><p>Queries all records for that user.</p></li><li><p>Measures how long this takes to do with a direct connection, and with Hyperdrive.</p></li></ol><p>When connecting directly to the origin database, this set of queries takes an average of 1200 ms. With absolutely no other changes, just swapping out the connection string for <code>env.HYPERDRIVE.connectionString</code>, this number is cut down to 500 ms (an almost 60% reduction). If you enable Hyperdrive’s caching, so that the SELECT query is served from cache, this takes only 320 ms. With this one-line change, Hyperdrive will reduce the latency of this Worker by almost 75%! In addition to this speedup, you also get secure auth and transport, as well as a connection pool to help protect your database from being overwhelmed when your usage scales up. See it for yourself using our <a href="https://hyperdrive-demo.pages.dev/"><u>demo application</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6TTbEV9d7NClGk0iRmkEG3/4d5e4fdeb195337a92942bc7e13dbb6f/image7.png" />
          </figure><p><sup><i>A demo application comparing latencies between Hyperdrive and direct-to-database connections.</i></sup></p><p>Traditional SQL databases are familiar and powerful, but they are designed to be colocated with long-running compute. They were not conceived in the era of modern serverless applications, and have connection models that don't take the constraints of such an environment into account. Instead, they require highly stateful connections that do not play well with Workers’ global and stateless model. Hyperdrive solves this problem by maintaining database connections across Cloudflare’s network ready to be used at a moment’s notice, caching your queries for fast access, and eliminating round trips to minimize network latency.</p><p>With this announcement, many developers are going to be taking a look at Hyperdrive for the first time over the coming weeks and months. To help people dive in and try it out, we think it’s time to talk about how Hyperdrive actually works.</p>
    <div>
      <h2>Staying warm in the pool</h2>
      <a href="#staying-warm-in-the-pool">
        
      </a>
    </div>
    <p>Let’s talk a bit about database connection poolers, how they work, and what problems they already solve. They are <a href="https://github.com/pgbouncer/pgbouncer/commit/a0d2b294e0270f8a246e5b98f0700716c0672b0d"><u>hardly a new technology</u></a>, after all. </p><p>The point of any connection pooler, Hyperdrive or others, is to minimize the overhead of establishing and coordinating database connections. Every new database connection requires additional <a href="https://blog.anarazel.de/2020/10/07/measuring-the-memory-overhead-of-a-postgres-connection/"><u>memory</u></a> and CPU time from the database server, and this can only scale just so well as the number of concurrent connections climbs. So the question becomes, how should database connections be shared across clients? </p><p>There are three <a href="https://www.pgbouncer.org/features.html"><u>commonly-used approaches</u></a> for doing so. These are:</p><ul><li><p><b>Session mode:</b> whenever a client connects, it is assigned a connection of its own until it disconnects. This dramatically reduces the available concurrency, in exchange for much simpler implementation and a broader selection of supported features</p></li><li><p><b>Transaction mode:</b> when a client is ready to send a query or open a transaction, it is assigned a connection on which to do so. This connection will be returned to the pool when the query or transaction concludes. Subsequent queries during the same client session may (or may not) be assigned a different connection.</p></li><li><p><b>Statement mode:</b> Like transaction mode, but a connection is given out and returned for each statement. Multi-statement transactions are disallowed.</p></li></ul><p>When building Hyperdrive, we had to decide which of these modes we wanted to use. Each of the approaches implies some <a href="https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html"><u>fairly serious tradeoffs</u></a>, so what’s the right choice? For a service intended to make using a database from Workers as pleasant as possible we went with the choice that balances features and performance, and designed Hyperdrive as a transaction-mode pooler. This best serves the goals of supporting a large number of short-lived clients (and therefore very high concurrency), while still supporting the transactional semantics that cause so many people to reach for an RDBMS in the first place.</p><p>In terms of this part of its design, Hyperdrive takes its cues from many pre-existing popular connection poolers, and manages operations to allow our users to focus on designing their full-stack applications. There is a configured limit to the number of connections the pool will give out, limits to how long a connection will be held idle until it is allowed to drop and return resources to the database, bookkeeping around <a href="https://blog.cloudflare.com/elephants-in-tunnels-how-hyperdrive-connects-to-databases-inside-your-vpc-networks/"><u>prepared statements</u></a> being shared across pooled connections, and other traditional concerns of the management of these resources to help ensure the origin database is able to run smoothly. These are all described in <a href="https://developers.cloudflare.com/hyperdrive/platform/limits/"><u>our documentation</u></a>.</p>
    <div>
      <h2>Round and round we go</h2>
      <a href="#round-and-round-we-go">
        
      </a>
    </div>
    <p>Ok, so why build Hyperdrive then? Other poolers that solve these problems already exist — couldn’t developers using Workers just run one of those and call it a day? It turns out that connecting to regional poolers from Workers has the same major downside as connecting to regional databases: network latency and round trips.</p><p>Establishing a connection, whether to a database or a pool, requires many exchanges between the client and server. While this is true for all fully-fledged client-server databases (e.g. <a href="https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html"><u>MySQL</u></a>, <a href="https://github.com/mongodb/specifications/blob/master/source/auth/auth.md"><u>MongoDB</u></a>), we are going to focus on the <a href="https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-START-UP"><u>PostgreSQL</u></a> connection protocol flow in this post. As we work through all of the steps involved, what we most want to keep track of is how many round trips it takes to accomplish. Note that we’re mostly concerned about having to wait around while these happen, so “half” round trips such as in the first diagram are not counted. This is because we can send off the message and then proceed without waiting.</p><p>The first step to establishing a connection between Postgres client and server is very familiar ground to anyone who’s worked much with networks: <a href="https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/"><u>a TCP startup handshake</u></a>. Postgres uses TCP for its underlying transport, and so we must have that connection before anything else can happen on top of it.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/58qrxBsbOXbFCFBzkZFIff/19caf62d24cdbf9c4ad69bfd8286e022/image5.png" />
          </figure><p>With our transport layer in place, the next step is to <a href="https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-SSL"><u>encrypt</u></a> the connection. The <a href="https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/"><u>TLS Handshake</u></a> involves some back-and-forth in its own right, though this has been reduced to just one round trip for TLS 1.3. Below is the simplest and fastest version of this exchange, but there are certainly scenarios where it can be much more complex.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5q7fVVQkB9Q43X3eaE76GP/b69c0ce964df370bd0609242f8e3de0c/image4.png" />
          </figure><p>After the underlying transport is established and secured, the application-level traffic can actually start! However, we’re not quite ready for queries, the client still needs to authenticate to a specific user and database. Again, there are multiple supported approaches that offer varying levels of speed and security. To make this comparison as fair as possible, we’re again going to consider the version that offers the fastest startup (password-based authentication).</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7KU6NHgZAW95nQyobo9Zwn/5f61d6e9ab6233186c865a9093a7f352/image8.png" />
          </figure><p>So, for those keeping score, establishing a new connection to your database takes a bare minimum of 5 round trips, and can very quickly climb from there. </p><p>While the latency of any given network round trip is going to vary based on so many factors that “it depends” is the only meaningful measurement available, some quick benchmarking during the writing of this post shows ~125 ms from Chicago to London. Now multiply that number by 5 round trips and the problem becomes evident: 625 ms to start up a connection is not viable in a distributed serverless environment. So how does Hyperdrive solve it? What if I told you the trick is that we do it all twice? To understand Hyperdrive’s secret sauce, we need to dive into Hyperdrive’s architecture.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/291ua8XgVnowWDOfEm05eR/a2674a9a393fcaaef8e2cfe64dd57402/image1.png" />
          </figure>
    <div>
      <h2>Impersonating a database server</h2>
      <a href="#impersonating-a-database-server">
        
      </a>
    </div>
    <p>The rest of this post is a deep dive into answering the question of how Hyperdrive does what it does. To give the clearest picture, we’re going to talk about some internal subsystems by name. To help keep everything straight, let’s start with a short glossary that you can refer back to if needed. These descriptions may not make sense yet, but they will by the end of the article.
</p><table><tr><td><p><b>Hyperdrive subsystem name</b></p></td><td><p><b>Brief description</b></p></td></tr><tr><td><p>Client</p></td><td><p>Lives on the same server as your Worker, talks directly to your database driver. This caches query results and sends queries to Endpoint if needed.</p></td></tr><tr><td><p>Endpoint</p></td><td><p>Lives in the data center nearest to your origin database, talks to your origin database. This caches query results and houses a pool of connections to your origin database.</p></td></tr><tr><td><p>Edge Validator</p></td><td><p>Sends a request to a Cloudflare data center to validate that Hyperdrive can connect to your origin database at time of creation.</p></td></tr><tr><td><p>Placement</p></td><td><p>Builds on top of Edge Validator to connect to your origin database from all eligible data centers, to identify which have the fastest connections.</p></td></tr></table><p>The first subsystem we want to dig into is named <code>Client. Client</code>’s first job is to pretend to be a database server. When a user’s Worker wants to connect to their database via Hyperdrive, they use a special connection string that the Worker runtime generates on the fly. This tells the Worker to reach out to a Hyperdrive process running on the same Cloudflare server, and direct all traffic to and from the database client to it.</p>
            <pre><code>import postgres from "postgres";

// Connect to Hyperdrive
const sql = postgres(env.HYPERDRIVE.connectionString);

// sql will now talk over an RPC channel to Hyperdrive, instead of via TCP to Postgres</code></pre>
            <p>Once this connection is established, the database driver will perform the usual handshake expected of it, with our <code>Client</code> playing the role of a database server and sending the appropriate responses. All of this happens on the same Cloudflare server running the Worker, and we observe that the p90 for all this is 4 ms (p50 is 2 ms). Quite a bit better than 625 ms, but how does that help? The query still needs to get to the database, right?</p><p><code>Client</code>’s second main job is to inspect the queries sent from a Worker, and decide whether they can be served from Cloudflare’s cache. We’ll talk more about that later on. Assuming that there are no cached query results available, <code>Client</code> will need to reach out to our second important subsystem, which we call <code>Endpoint</code>.</p>
    <div>
      <h2>In for the long haul</h2>
      <a href="#in-for-the-long-haul">
        
      </a>
    </div>
    <p>Before we dig into the role <code>Endpoint</code> plays, it’s worth talking more about how the <code>Client→Endpoint</code> connection works, because it’s a key piece of our solution. We have already talked a lot about the price of network round trips, and how a Worker might be quite far away from the origin database, so how does Hyperdrive handle the long trip from the <code>Client</code> running alongside their Worker to the <code>Endpoint</code> running near their database without expensive round trips?</p><p>This is accomplished with a very handy bit of Cloudflare’s networking infrastructure. When <code>Client</code> gets a cache miss, it will submit a request to our networking platform for a connection to whichever data center <code>Endpoint</code> is running on. This platform keeps a pool of ready TCP connections between all of Cloudflare’s data centers, such that we don’t need to do any preliminary handshakes to begin sending application-level traffic. You might say we put a connection pooler in our connection pooler.</p><p>Over this TCP connection, we send an initialization message that includes all of the buffered query messages the Worker has sent to <code>Client</code> (the mental model would be something like a <code>SYN</code> and a payload all bundled together). <code>Endpoint</code> will do its job processing this query, and respond by streaming the response back to <code>Client</code>, leaving the streaming channel open for any followup queries until <code>Client</code> disconnects. This approach allows us to send queries around the world with zero wasted round trips.</p>
    <div>
      <h2>Impersonating a database client</h2>
      <a href="#impersonating-a-database-client">
        
      </a>
    </div>
    <p><code>Endpoint</code> has a couple different jobs it has to do. Its first job is to pretend to be a database client, and to do the client half of the handshake shown above. Second, it must also do the same query processing that <code>Client</code> does with query messages. Finally, <code>Endpoint</code> will make the same determination on when it needs to reach out to the origin database to get uncached query results.</p><p>When <code>Endpoint</code> needs to query the origin database, it will attempt to take a connection out of a limited-size pool of database connections that it keeps. If there is an unused connection available, it is handed out from the pool and used to ferry the query to the origin database, and the results back to <code>Endpoint</code>. Once <code>Endpoint</code> has these results, the connection is immediately returned to the pool so that another <code>Client</code> can use it. These warm connections are usable in a matter of microseconds, which is obviously a dramatic improvement over the round trips from one region to another that a cold startup handshake would require.</p><p>If there are no currently unused connections sitting in the pool, it may start up a new one (assuming the pool has not already given out as many connections as it is allowed to). This set of handshakes looks exactly the same as the one <code>Client</code> does, but it happens across the network between a Cloudflare data center and wherever the origin database happens to be. These are the same 5 round trips as our original example, but instead of a full Chicago→London path on every single trip, perhaps it’s Virginia→London, or even London→London. Latency here will depend on which data center <code>Endpoint</code> is being housed in.</p>
    <div>
      <h2>Distributed choreography</h2>
      <a href="#distributed-choreography">
        
      </a>
    </div>
    <p>Earlier, we mentioned that Hyperdrive is a transaction-mode pooler. This means that when a driver is ready to send a query or open a transaction it must get a connection from the pool to use. The core challenge for a transaction-mode pooler is in aligning the state of the driver with the state of the connection checked out from the pool. For example, if the driver thinks it’s in a transaction, but the database doesn’t, then you might get errors or even corrupted results.</p><p>Hyperdrive achieves this by ensuring all connections are in the same state when they’re checked out of the pool: idle and ready for a query. Where Hyperdrive differs from other transaction-mode poolers is that it does this dance of matching up the states of two different connections across machines, such that there’s no need to share state between <code>Client</code> and <code>Endpoint</code>! Hyperdrive can terminate the incoming connection in <code>Client</code> on the same machine running the Worker, and pool the connections to the origin database wherever makes the most sense.</p><p>The job of a transaction-mode pooler is a hard one. Database connections are fundamentally stateful and keeping track of that state is important to maintain our guise when impersonating either a database client or a server. As an example, one of the trickier pieces of state to manage are <a href="https://www.postgresql.org/docs/current/protocol-overview.html#PROTOCOL-QUERY-CONCEPTS"><u>prepared statements</u></a>. When a user creates a new prepared statement, the prepared statement is only created on whichever database connection happened to be checked out at that time. Once the user finishes the transaction or query they are processing, the connection holding that statement is returned to the pool. From the user’s perspective they’re still connected using the same database connection, so a new query or transaction can reasonably expect to use that previously prepared statement. If a different connection is handed out for the next query and the query wants to make use of this resource, the pooler has to do something about it. We went into some depth on this topic in a <a href="https://blog.cloudflare.com/postgres-named-prepared-statements-supported-hyperdrive/"><u>previous blog post</u></a> when we released this feature, but in sum, the process looks like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2ibtnO4URpLJ6m3Nyd2kpW/331059a6fd18c7d70b95a15af8f57cd6/image2.png" />
          </figure><p>Hyperdrive implements this by keeping track of what statements have been prepared by a given client, as well as what statements have been prepared on each origin connection in the pool. When a query comes in expecting to re-use a particular prepared statement (#8 above), Hyperdrive checks if it’s been prepared on the checked-out origin connection. If it hasn’t, Hyperdrive will replay the wire-protocol message sequence to prepare it on the newly-checked-out origin connection (#10 above) before sending the query over it. Many little corrections like this are necessary to keep the client’s connection to Hyperdrive and Hyperdrive’s connection to the origin database lined up so that both sides see what they expect.</p>
    <div>
      <h2>Better, faster, smarter, closer</h2>
      <a href="#better-faster-smarter-closer">
        
      </a>
    </div>
    <p>This “split connection” approach is the founding innovation of Hyperdrive, and one of the most vital aspects of it is how it affects starting up new connections. While the same 5+ round trips must always happen on startup, the actual time spent on the round trips can be dramatically reduced by conducting them over the smallest possible distances. This impact of distance can be so big that there is still a huge latency reduction even though the startup round trips must now happen <i>twice</i> (once each between the Worker and <code>Client</code>, and <code>Endpoint</code> and your origin database). So how do we decide where to run everything, to lean into that advantage as much as possible?</p><p>The placement of <code>Client</code> has not really changed since the original design of Hyperdrive. Sharing a server with the Worker sending the queries means that the Worker runtime can connect directly to Hyperdrive with no network hop needed. While there is always room for microoptimizations, it’s hard to do much better than that from an architecture perspective.  By far the bigger piece of the latency puzzle is where to run <code>Endpoint</code>.</p><p>Hyperdrive keeps a list of data centers that are eligible to house <code>Endpoint</code>s, requiring that they have sufficient capacity and the best routes available for pooled connections to use. The key challenge to overcome here is that a <a href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS"><u>database connection string</u></a> does not tell you where in the world a database actually is. The reality is that reliably going from a hostname to a precise (enough) geographic location is a hard problem, even leaving aside the additional complexity of doing so <a href="https://blog.cloudflare.com/elephants-in-tunnels-how-hyperdrive-connects-to-databases-inside-your-vpc-networks/"><u>within a private network</u></a>. So how do we pick from that list of eligible data centers?</p><p>For much of the time since its launch, Hyperdrive solved this with a regional pool approach. When a Worker connected to Hyperdrive, the location of the Worker was used to infer what region the end user was connecting from (e.g. ENAM, WEUR, APAC, etc. — see a rough breakdown <a href="https://www.cloudflare.com/network/"><u>here</u></a>). Data centers to house <code>Endpoint</code>s for any given Hyperdrive were deterministically selected from that region’s list of eligible options using <a href="https://en.wikipedia.org/wiki/Rendezvous_hashing"><u>rendezvous hashing</u></a>, resulting in one pool of connections <i>per region</i>.</p><p>This approach worked well enough, but it had some severe shortcomings. The first and most obvious is that there’s no guarantee that the data center selected for a given region is actually closer to the origin database than the user making the request. This means that, while you’re getting the benefit of the excellent routing available on <a href="https://www.cloudflare.com/network/"><u>Cloudflare's network</u></a>, you may be going significantly out of your way to do so. The second downside is that, in the scenario where a new connection must be created, the round trips to do so may be happening over a significantly larger distance than is necessary if the origin database is in a different region than the <code>Endpoint</code> housing the regional connection pool. This increases latency and reduces throughput for the query that needs to instantiate the connection.</p><p>The final key downside here is an unfortunate interaction with <a href="https://developers.cloudflare.com/workers/configuration/smart-placement/"><u>Smart Placement</u></a>, a feature of Cloudflare Workers that analyzes the duration of your Worker requests to identify the data center to run your Worker in. With regional <code>Endpoint</code>s, the best Smart Placement can possibly do is to put your requests close to the <code>Endpoint</code> for whichever region the origin database is in. Again, there may be other data centers that are closer, but Smart Placement has no way to do better than where the <code>Endpoint</code> is because all Hyperdrive queries must route through it.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3y9r3Dwn6APp5Pw6kqkg0Z/3a9e202670b6c65a22294fe777064add/image6.png" />
          </figure><p>We recently <a href="https://developers.cloudflare.com/changelog/2025-03-04-hyperdrive-pooling-near-database-and-ip-range-egress/"><u>shipped some improvements</u></a> to this system that significantly enhanced performance. The new system discards the concept of regional pools entirely, in favor of a single global <code>Endpoint</code> for each Hyperdrive that is in the eligible data center as close as possible to the origin database.</p><p>The way we solved locating the origin database such that we can accomplish this was ultimately very straightforward. We already had a subsystem to confirm, at the time of creation, that Hyperdrive could connect to an origin database using the provided information. We call this subsystem our <code>Edge Validator</code>.</p><p>It’s bad user experience to allow someone to create a Hyperdrive, and then find out when they go to use it that they mistyped their password or something. Now they’re stuck trying to debug with extra layers in the way, with a Hyperdrive that can’t possibly work. Instead, whenever a Hyperdrive is created, the <code>Edge Validator</code> will send a request to an arbitrary data center to use its instance of Hyperdrive to connect to the origin database. If this connection fails, the creation of the Hyperdrive will also fail, giving immediate feedback to the user at the time it is most helpful.</p><p>With our new subsystem, affectionately called <code>Placement</code>, we now have a solution to the geolocation problem. After <code>Edge Validator</code> has confirmed that the provided information works and the Hyperdrive is created, an extra step is run in the background. <code>Placement</code> will perform the exact same connection routine, except instead of being done once from an arbitrary data center, it is run a handful of times from every single data center that is eligible to house <code>Endpoints</code>. The latency of establishing these connections is collected, and the average is sent back to a central instance of <code>Placement</code>. The data centers that can connect to the origin database the fastest are, by definition, where we want to run <code>Endpoint</code> for this Hyperdrive. The list of these is saved, and at runtime is used to select the <code>Endpoint</code> best suited to housing the pool of connections to the origin database.</p><p>Given that the secret sauce of Hyperdrive is in managing and minimizing the latency of establishing these connections, moving <code>Endpoint</code>s right next to their origin databases proved to be pretty impactful.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1MZpaxXjj4tlOAinZkDqOF/e1a555a47141ac11aa391d27806cbfbc/image9.png" />
          </figure><p><sup><i>Pictured: query latency as measured from Endpoint to origin databases. The backfill of Placement to existing customers was done in stages on 02/22 and 02/25.</i></sup></p>
    <div>
      <h2>Serverless drivers exist, though?</h2>
      <a href="#serverless-drivers-exist-though">
        
      </a>
    </div>
    <p>While we went in a different direction, it’s worth acknowledging that other teams have <a href="https://neon.tech/blog/quicker-serverless-postgres"><u>solved this same problem</u></a> with a very different approach. Custom database drivers, usually called “serverless drivers”, have made several optimization efforts to reduce both the number of round trips and how quickly they can be conducted, while still connecting directly from your client to your database in the traditional way. While these drivers are impressive, we chose not to go this route for a couple of reasons.</p><p>First off, a big part of the appeal of using Postgres is its <a href="https://www.lastweekinaws.com/podcast/screaming-in-the-cloud/the-ever-growing-ecosystem-of-postgres-with-alvaro-hernandez/"><u>vibrant ecosystem</u></a>. Odds are good you’ve used Postgres before, and it can probably help solve whichever problem you’re tackling with your newest project. This familiarity and shared knowledge across projects is an absolute superpower. We wanted to lean into this advantage by supporting the most popular drivers already in this ecosystem, instead of fragmenting it by adding a competing one.</p><p>Second, Hyperdrive also functions as a cache for individual queries (a bit of trivia: its name while still in Alpha was actually <code>sql-query-cache</code>). Doing this as effectively as possible for distributed users requires some clever positioning of where exactly the query results should be cached. One of the unique advantages of running a distributed service on Cloudflare’s network is that we have a lot of flexibility on where to run things, and can confidently surmount challenges like those. If we’re going to be playing three-card monte with where things are happening anyway, it makes the most sense to favor that route for solving the other problems we’re trying to tackle too.</p>
    <div>
      <h2>Pick your favorite cache pun</h2>
      <a href="#pick-your-favorite-cache-pun">
        
      </a>
    </div>
    <p>As we’ve <a href="https://blog.cloudflare.com/postgres-named-prepared-statements-supported-hyperdrive/"><u>talked about</u></a> in the past, Hyperdrive buffers protocol messages until it has enough information to know whether a query can be served from cache. In a post about how Hyperdrive works it would be a shame to skip talking about how exactly we cache query results, so let’s close by diving into that.</p><p>First and foremost, Hyperdrive uses <a href="https://developers.cloudflare.com/cache/"><u>Cloudflare's cache</u></a>, because when you have technology like that already available to you, it’d be silly not to use it. This has some implications for our architecture that are worth exploring.</p><p>The cache exists in each of Cloudflare’s data centers, and by default these are separate instances. That means that a <code>Client</code> operating close to the user has one, and an <code>Endpoint</code> operating close to the origin database has one. However, historically we weren’t able to take full advantage of that, because the logic for interacting with cache was tightly bound to the logic for managing the pool of connections.</p><p>Part of our recent architecture refactoring effort, where we switched to global <code>Endpoint</code>s, was to split up this logic such that we can take advantage of <code>Client</code>’s cache too. This was necessary because, with <code>Endpoint</code> moving to a single location for each Hyperdrive, users from other regions would otherwise have gotten cache hits served from almost as far away as the origin.</p><p>With the new architecture, the role of <code>Client</code> during active query handling transitioned from that of a “dumb pipe” to more like what <code>Endpoint</code> had always been doing. It now buffers protocol messages, and serves results from cache if possible. In those scenarios, Hyperdrive’s traffic never leaves the data center that the Worker is running in, reducing query latencies from 20-70 ms to an average of around 4 ms. As a side benefit, it also substantially reduces the network bandwidth Hyperdrive uses to serve these queries. A win-win!</p><p>In the scenarios where query results can’t be served from the cache in <code>Client</code>’s data center, all is still not lost. <code>Endpoint</code> may also have cached results for this query, because it can field traffic from many different <code>Client</code>s around the world. If so, it will provide these results back to <code>Client</code>, along with how much time is remaining before they expire, such that <code>Client</code> can both return them and store them correctly into its own cache. Likewise, if <code>Endpoint</code> does need to go to the origin database for results, they will be stored into both <code>Client</code> and <code>Endpoint</code> caches. This ensures that followup queries from that same <code>Client</code> data center will get the happy path with single-digit ms response times, and also reduce load on the origin database from any other <code>Client</code>’s queries. This functions similarly to how <a href="https://developers.cloudflare.com/cache/how-to/tiered-cache/"><u>Cloudflare's Tiered Cache</u></a> works, with <code>Endpoint</code>’s cache functioning as a final layer of shielding for the origin database.</p>
    <div>
      <h2>Come on in, the water’s fine!</h2>
      <a href="#come-on-in-the-waters-fine">
        
      </a>
    </div>
    <p>With this announcement of a Free Plan for Hyperdrive, and newly armed with the knowledge of how it works under the hood, we hope you’ll enjoy building your next project with it! You can get started with a single Wrangler command (or using the dashboard):</p>
            <pre><code>wrangler hyperdrive create postgres-hyperdrive 
--connection-string="postgres://user:password@db-host.example.com:5432/defaultdb"</code></pre>
            <p>We’ve also included a Deploy to Cloudflare button below to let you get started with a sample Worker app using Hyperdrive, just bring your existing Postgres database! If you have any questions or ideas for future improvements, please feel free to visit our <a href="https://discord.com/channels/595317990191398933/1150557986239021106"><u>Discord channel!</u></a></p><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/templates/tree/main/postgres-hyperdrive-template"><img src="https://deploy.workers.cloudflare.com/button" /></a>
<p></p><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Hyperdrive]]></category>
            <category><![CDATA[Smart Placement]]></category>
            <category><![CDATA[SQL]]></category>
            <guid isPermaLink="false">3YedZXQKWaCm2jUQPvAeQv</guid>
            <dc:creator>Andrew Repp</dc:creator>
            <dc:creator>Matt Alonso</dc:creator>
        </item>
    </channel>
</rss>