
<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>Tue, 14 Apr 2026 21:43:31 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Durable Objects in Dynamic Workers: Give each AI-generated app its own database]]></title>
            <link>https://blog.cloudflare.com/durable-object-facets-dynamic-workers/</link>
            <pubDate>Mon, 13 Apr 2026 13:08:35 GMT</pubDate>
            <description><![CDATA[ We’re introducing Durable Object Facets, allowing Dynamic Workers to instantiate Durable Objects with their own isolated SQLite databases. This enables developers to build platforms that run persistent, stateful code generated on-the-fly.
 ]]></description>
            <content:encoded><![CDATA[ <p>A few weeks ago, we announced <a href="https://blog.cloudflare.com/dynamic-workers/"><u>Dynamic Workers</u></a>, a new feature of the Workers platform which lets you load Worker code on-the-fly into a secure sandbox. The Dynamic Worker Loader API essentially provides direct access to the basic compute isolation primitive that Workers has been based on all along: isolates, not containers. Isolates are much lighter-weight than containers, and as such, can load 100x faster using 1/10 the memory. They are so efficient, they can be treated as "disposable": start one up to run a few lines of code, then throw it away. Like a secure version of eval(). </p><p>Dynamic Workers have many uses. In the original announcement, we focused on how to use them to run AI-agent-generated code as an alternative to tool calls. In this use case, an AI agent performs actions at the request of a user by writing a few lines of code and executing them. The code is single-use, intended to perform one task one time, and is thrown away immediately after it executes.</p><p>But what if you want an AI to generate more persistent code? What if you want your AI to build a small application with a custom UI the user can interact with? What if you want that application to have long-lived state? But of course, you still want it to run in a secure sandbox.</p><p>One way to do this would be to use Dynamic Workers, and simply provide the Worker with an <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/"><u>RPC</u></a> API that gives it access to storage. Using <a href="https://developers.cloudflare.com/dynamic-workers/usage/bindings/"><u>bindings</u></a>, you could give the Dynamic Worker an API that points back to your remote SQL database (perhaps backed by <a href="https://developers.cloudflare.com/d1/"><u>Cloudflare D1</u></a>, or a Postgres database you access through <a href="https://developers.cloudflare.com/hyperdrive/"><u>Hyperdrive</u></a> — it's up to you).</p><p>But Workers also has a unique and extremely fast type of storage that may be a perfect fit for this use case: <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a>. A Durable Object is a special kind of Worker that has a unique name, with one instance globally per name. That instance has a SQLite database attached, which lives <i>on local disk</i> on the machine where the Durable Object runs. This makes storage access ridiculously fast: there is effectively <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/"><u>zero latency</u></a>.</p><p>Perhaps, then, what you really want is for your AI to write code for a Durable Object, and then you want to run that code in a Dynamic Worker.</p>
    <div>
      <h2><b>But how?</b></h2>
      <a href="#but-how">
        
      </a>
    </div>
    <p>This presents a weird problem. Normally, to use Durable Objects you have to:</p><ol><li><p>Write a class extending <code>DurableObject</code>.</p></li><li><p>Export it from your Worker's main module.</p></li><li><p><a href="https://developers.cloudflare.com/durable-objects/get-started/#5-configure-durable-object-class-with-sqlite-storage-backend"><u>Specify in your Wrangler config</u></a> that storage should be provision for this class. This creates a Durable Object namespace that points at your class for handling incoming requests.</p></li><li><p><a href="https://developers.cloudflare.com/durable-objects/get-started/#4-configure-durable-object-bindings"><u>Declare a Durable Object namespace binding</u></a> pointing at your namespace (or use <a href="https://developers.cloudflare.com/workers/runtime-apis/context/#exports"><u>ctx.exports</u></a>), and use it to make requests to your Durable Object.</p></li></ol><p>This doesn't extend naturally to Dynamic Workers. First, there is the obvious problem: The code is dynamic. You run it without invoking the Cloudflare API at all. But Durable Object storage has to be provisioned through the API, and the namespace has to point at an implementing class. It can't point at your Dynamic Worker.</p><p>But there is a deeper problem: Even if you could somehow configure a Durable Object namespace to point directly at a Dynamic Worker, would you want to? Do you want your agent (or user) to be able to create a whole namespace full of Durable Objects? To use unlimited storage spread around the world?</p><p>You probably don't. You probably want some control. You may want to limit, or at least track, how many objects they create. Maybe you want to limit them to just one object (probably good enough for vibe-coded personal apps). You may want to add logging and other observability. Metrics. Billing. Etc.</p><p>To do all this, what you really want is for requests to these Durable Objects to go to <i>your</i> code <i>first</i>, where you can then do all the "logistics", and <i>then</i> forward the request into the agent's code. You want to write a <i>supervisor</i> that runs as part of every Durable Object.</p>
    <div>
      <h2><b>Solution: Durable Object Facets</b></h2>
      <a href="#solution-durable-object-facets">
        
      </a>
    </div>
    <p>Today we are releasing, in open beta, a feature that solves this problem.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/mUUk7svflWvIp5Ff3npbG/cd2ec9a7111681657c37e3560fd9af58/BLOG-3211_2.png" />
          </figure><p><a href="https://developers.cloudflare.com/dynamic-workers/usage/durable-object-facets/"><u>Durable Object Facets</u></a> allow you to load and instantiate a Durable Object class dynamically, while providing it with a SQLite database to use for storage. With Facets:</p><ul><li><p>First you create a normal Durable Object namespace, pointing to a class <i>you</i> write.</p></li><li><p>In that class, you load the agent's code as a Dynamic Worker, and call into it.</p></li><li><p>The Dynamic Worker's code can implement a Durable Object class directly. That is, it literally exports a class declared as <code>extends DurableObject</code>.</p></li><li><p>You are instantiating that class as a "facet" of your own Durable Object.</p></li><li><p>The facet gets its own SQLite database, which it can use via the normal Durable Object storage APIs. This database is separate from the supervisor's database, but the two are stored together as part of the same overall Durable Object.</p></li></ul>
    <div>
      <h2><b>How it works</b></h2>
      <a href="#how-it-works">
        
      </a>
    </div>
    <p>Here is a simple, complete implementation of an app platform that dynamically loads and runs a Durable Object class:</p>
            <pre><code>import { DurableObject } from "cloudflare:workers";

// For the purpose of this example, we'll use this static
// application code, but in the real world this might be generated
// by AI (or even, perhaps, a human user).
const AGENT_CODE = `
  import { DurableObject } from "cloudflare:workers";

  // Simple app that remembers how many times it has been invoked
  // and returns it.
  export class App extends DurableObject {
    fetch(request) {
      // We use storage.kv here for simplicity, but storage.sql is
      // also available. Both are backed by SQLite.
      let counter = this.ctx.storage.kv.get("counter") || 0;
      ++counter;
      this.ctx.storage.kv.put("counter", counter);

      return new Response("You've made " + counter + " requests.\\n");
    }
  }
`;

// AppRunner is a Durable Object you write that is responsible for
// dynamically loading applications and delivering requests to them.
// Each instance of AppRunner contains a different app.
export class AppRunner extends DurableObject {
  async fetch(request) {
    // We've received an HTTP request, which we want to forward into
    // the app.

    // The app itself runs as a child facet named "app". One Durable
    // Object can have any number of facets (subject to storage limits)
    // with different names, but in this case we have only one. Call
    // this.ctx.facets.get() to get a stub pointing to it.
    let facet = this.ctx.facets.get("app", async () =&gt; {
      // If this callback is called, it means the facet hasn't
      // started yet (or has hibernated). In this callback, we can
      // tell the system what code we want it to load.

      // Load the Dynamic Worker.
      let worker = this.#loadDynamicWorker();

      // Get the exported class we're interested in.
      let appClass = worker.getDurableObjectClass("App");

      return { class: appClass };
    });

    // Forward request to the facet.
    // (Alternatively, you could call RPC methods here.)
    return await facet.fetch(request);
  }

  // RPC method that a client can call to set the dynamic code
  // for this app.
  setCode(code) {
    // Store the code in the AppRunner's SQLite storage.
    // Each unique code must have a unique ID to pass to the
    // Dynamic Worker Loader API, so we generate one randomly.
    this.ctx.storage.kv.put("codeId", crypto.randomUUID());
    this.ctx.storage.kv.put("code", code);
  }

  #loadDynamicWorker() {
    // Use the Dynamic Worker Loader API like normal. Use get()
    // rather than load() since we may load the same Worker many
    // times.
    let codeId = this.ctx.storage.kv.get("codeId");
    return this.env.LOADER.get(codeId, async () =&gt; {
      // This Worker hasn't been loaded yet. Load its code from
      // our own storage.
      let code = this.ctx.storage.kv.get("code");

      return {
        compatibilityDate: "2026-04-01",
        mainModule: "worker.js",
        modules: { "worker.js": code },
        globalOutbound: null,  // block network access
      }
    });
  }
}

// This is a simple Workers HTTP handler that uses AppRunner.
export default {
  async fetch(req, env, ctx) {
    // Get the instance of AppRunner named "my-app".
    // (Each name has exactly one Durable Object instance in the
    // world.)
    let obj = ctx.exports.AppRunner.getByName("my-app");

    // Initialize it with code. (In a real use case, you'd only
    // want to call this once, not on every request.)
    await obj.setCode(AGENT_CODE);

    // Forward the request to it.
    return await obj.fetch(req);
  }
}
</code></pre>
            <p>In this example:</p><ul><li><p><code>AppRunner</code> is a "normal" Durable Object written by the platform developer (you).</p></li><li><p>Each instance of <code>AppRunner</code> manages one application. It stores the app code and loads it on demand.</p></li><li><p>The application itself implements and exports a Durable Object class, which the platform expects is named <code>App</code>.</p></li><li><p><code>AppRunner</code> loads the application code using Dynamic Workers, and then executes the code as a Durable Object Facet.</p></li><li><p>Each instance of <code>AppRunner</code> is one Durable Object composed of <i>two</i> SQLite databases: one belonging to the parent (<code>AppRunner</code> itself) and one belonging to the facet (<code>App</code>). These databases are isolated: the application cannot read <code>AppRunner</code>'s database, only its own.</p></li></ul><p>To run the example, copy the code above into a file <code>worker.j</code>s, pair it with the following <code>wrangler.jsonc</code>, and run it locally with <code>npx wrangler dev</code>.</p>
            <pre><code>// wrangler.jsonc for the above sample worker.
{
  "compatibility_date": "2026-04-01",
  "main": "worker.js",
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "AppRunner"
      ]
    }
  ],
  "worker_loaders": [
    {
      "binding": "LOADER",
    },
  ],
}
</code></pre>
            
    <div>
      <h2><b>Start building</b></h2>
      <a href="#start-building">
        
      </a>
    </div>
    <p>Facets are a feature of Dynamic Workers, available in beta immediately to users on the Workers Paid plan.</p><p>Check out the documentation to learn more about <a href="https://developers.cloudflare.com/dynamic-workers/"><u>Dynamic Workers</u></a> and <a href="https://developers.cloudflare.com/dynamic-workers/usage/durable-object-facets/"><u>Facets</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Agents Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Durable Objects]]></category>
            <category><![CDATA[Storage]]></category>
            <guid isPermaLink="false">2OYAJUdGLODlCXKKdCZMeG</guid>
            <dc:creator>Kenton Varda</dc:creator>
        </item>
        <item>
            <title><![CDATA[Investigating multi-vector attacks in Log Explorer]]></title>
            <link>https://blog.cloudflare.com/investigating-multi-vector-attacks-in-log-explorer/</link>
            <pubDate>Tue, 10 Mar 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[ Log Explorer customers can now identify and investigate multi-vector attacks. Log Explorer supports 14 additional Cloudflare datasets, enabling users to have a 360-degree view of their network. ]]></description>
            <content:encoded><![CDATA[ <p>In the world of cybersecurity, a single data point is rarely the whole story. Modern attackers don’t just knock on the front door; they probe your APIs, flood your network with "noise" to distract your team, and attempt to slide through applications and servers using stolen credentials.</p><p>To stop these multi-vector attacks, you need the full picture. By using Cloudflare Log Explorer to conduct security forensics, you get 360-degree visibility through the integration of 14 new datasets, covering the full surface of Cloudflare’s Application Services and Cloudflare One product portfolios. By correlating telemetry from application-layer HTTP requests, network-layer DDoS and Firewall logs, and Zero Trust Access events, security analysts can significantly reduce Mean Time to Detect (MTTD) and effectively unmask sophisticated, multi-layered attacks.</p><p>Read on to learn more about how Log Explorer gives security teams the ultimate landscape for rapid, deep-dive forensics.</p>
    <div>
      <h2>The flight recorder for your entire stack</h2>
      <a href="#the-flight-recorder-for-your-entire-stack">
        
      </a>
    </div>
    <p>The contemporary digital landscape requires deep, correlated telemetry to defend against adversaries using multiple attack vectors. Raw logs serve as the "flight recorder" for an application, capturing every single interaction, attack attempt, and performance bottleneck. And because Cloudflare sits at the edge, between your users and your servers, all of these events are logged before the requests even reach your infrastructure. </p><p>Cloudflare Log Explorer centralizes these logs into a unified interface for rapid investigation.</p>
    <div>
      <h3>Log Types Supported</h3>
      <a href="#log-types-supported">
        
      </a>
    </div>
    
    <div>
      <h4>Zone-Scoped Logs</h4>
      <a href="#zone-scoped-logs">
        
      </a>
    </div>
    <p><i>Focus: Website traffic, security events, and edge performance.</i></p><table><tr><td><p><b>HTTP Requests</b></p></td><td><p>As the most comprehensive dataset, it serves as the "primary record" of all application-layer traffic, enabling the reconstruction of session activity, exploit attempts, and bot patterns.</p></td></tr><tr><td><p><b>Firewall Events</b></p></td><td><p>Provides critical evidence of blocked or challenged threats, allowing analysts to identify the specific WAF rules, IP reputations, or custom filters that intercepted an attack.</p></td></tr><tr><td><p><b>DNS Logs</b></p></td><td><p>Identify cache poisoning attempts, domain hijacking, and infrastructure-level reconnaissance by tracking every query resolved at the authoritative edge.</p></td></tr><tr><td><p><b>NEL (Network Error Logging) Reports</b></p></td><td><p>Distinguish between a coordinated Layer 7 DDoS attack and legitimate network connectivity issues by tracking client-side browser errors.</p></td></tr><tr><td><p><b>Spectrum Events</b></p></td><td><p>For non-web applications, these logs provide visibility into L4 traffic (TCP/UDP), helping to identify anomalies or brute-force attacks against protocols like SSH, RDP, or custom gaming traffic.</p></td></tr><tr><td><p><b>Page Shield</b></p></td><td><p>Track and audit unauthorized changes to your site's client-side environment such as JavaScript, outbound connections.</p></td></tr><tr><td><p><b>Zaraz Events</b></p></td><td><p>Examine how third-party tools and trackers are interacting with user data, which is vital for auditing privacy compliance and detecting unauthorized script behaviors.</p></td></tr></table>
    <div>
      <h4>Account-Scoped Logs</h4>
      <a href="#account-scoped-logs">
        
      </a>
    </div>
    <p><i>Focus: Internal security, Zero Trust, administrative changes, and network activity.</i></p><table><tr><td><p><b>Access Requests</b></p></td><td><p>Tracks identity-based authentication events to determine which users accessed specific internal applications and whether those attempts were authorized.</p></td></tr><tr><td><p><b>Audit Logs</b></p></td><td><p>Provides a trail of configuration changes within the Cloudflare dashboard to identify unauthorized administrative actions or modifications.</p></td></tr><tr><td><p><b>CASB Findings</b></p></td><td><p>Identifies security misconfigurations and data risks within SaaS applications (like Google Drive or Microsoft 365) to prevent unauthorized data exposure.</p></td></tr><tr><td><p><b>Magic Transit / IPSec Logs</b></p></td><td><p>Helps network engineers perform network-level (L3) monitoring such as reviewing tunnel health and view BGP routing changes.</p></td></tr><tr><td><p><b>Browser Isolation Logs</b></p></td><td><p>Tracks user actions <i>inside</i> an isolated browser session (e.g., copy-paste, print, or file uploads) to prevent data leaks on untrusted sites </p></td></tr><tr><td><p><b>Device Posture Results </b></p></td><td><p>Details the security health and compliance status of devices connecting to your network, helping to identify compromised or non-compliant endpoints.</p></td></tr><tr><td><p><b>DEX Application Tests </b></p></td><td><p>Monitors application performance from the user's perspective, which can help distinguish between a security-related outage and a standard performance degradation.</p></td></tr><tr><td><p><b>DEX Device State Events</b></p></td><td><p>Provides telemetry on the physical state of user devices, useful for correlating hardware or OS-level anomalies with potential security incidents.</p></td></tr><tr><td><p><b>DNS Firewall Logs</b></p></td><td><p>Tracks DNS queries filtered through the DNS Firewall to identify communication with known malicious domains or command-and-control (C2) servers.</p></td></tr><tr><td><p><b>Email Security Alerts</b></p></td><td><p>Logs malicious email activity and phishing attempts detected at the gateway to trace the origin of email-based entry vectors.</p></td></tr><tr><td><p><b>Gateway DNS</b></p></td><td><p>Monitors every DNS query made by users on your network to identify shadow IT, malware callbacks, or domain-generation algorithms (DGAs).</p></td></tr><tr><td><p><b>Gateway HTTP</b></p></td><td><p>Provides full visibility into encrypted and unencrypted web traffic to detect hidden payloads, malicious file downloads, or unauthorized SaaS usage.</p></td></tr><tr><td><p><b>Gateway Network</b></p></td><td><p>Tracks L3/L4 network traffic (non-HTTP) to identify unauthorized port usage, protocol anomalies, or lateral movement within the network.</p></td></tr><tr><td><p><b>IPSec Logs</b></p></td><td><p>Monitors the status and traffic of encrypted site-to-site tunnels to ensure the integrity and availability of secure network connections.</p></td></tr><tr><td><p><b>Magic IDS Detections</b></p></td><td><p>Surfaces matches against intrusion detection signatures to alert investigators to known exploit patterns or malware behavior traversing the network.</p></td></tr><tr><td><p><b>Network Analytics Logs</b></p></td><td><p>Provides high-level visibility into packet-level data to identify volumetric DDoS attacks or unusual traffic spikes targeting specific infrastructure.</p></td></tr><tr><td><p><b>Sinkhole HTTP Logs</b></p></td><td><p>Captures traffic directed to "sinkholed" IP addresses to confirm which internal devices are attempting to communicate with known botnet infrastructure.</p></td></tr><tr><td><p><b>WARP Config Changes</b></p></td><td><p>Tracks modifications to the WARP client settings on end-user devices to ensure that security agents haven't been tampered with or disabled.</p></td></tr><tr><td><p><b>WARP Toggle Changes</b></p></td><td><p>Specifically logs when users enable or disable their secure connectivity, helping to identify periods where a device may have been unprotected.</p></td></tr><tr><td><p><b>Zero Trust Network Session Logs</b></p></td><td><p>Logs the duration and status of authenticated user sessions to map out the complete lifecycle of a user's access within the protected perimeter.</p></td></tr></table>
    <div>
      <h2>Log Explorer can identify malicious activity at every stage</h2>
      <a href="#log-explorer-can-identify-malicious-activity-at-every-stage">
        
      </a>
    </div>
    <p>Get granular application layer visibility with <b>HTTP Requests</b>, <b>Firewall Events</b>, and <b>DNS logs</b> to see exactly how traffic is hitting your public-facing properties.<b> </b>Track internal movement with <b>Access Requests</b>, <b>Gateway logs</b>, and <b>Audit logs</b>. If a credential is compromised, you’ll see where they went. Use <b>Magic IDS</b> and <b>Network Analytics logs</b> to spot volumetric attacks and "East-West" lateral movement within your private network.</p>
    <div>
      <h3>Identify the reconnaissance</h3>
      <a href="#identify-the-reconnaissance">
        
      </a>
    </div>
    <p>Attackers use scanners and other tools to look for entry points, hidden directories, or software vulnerabilities. To identify this, using Log Explorer, you can query <code>http_requests</code> for any <code>EdgeResponseStatus</code> codes of 401, 403, or 404 coming from a single IP, or requests to sensitive paths (e.g. <code>/.env</code>, <code>/.git</code>, <code>/wp-admin</code>). </p><p>Additionally, <code>magic_ids_detections</code> logs can also be used to identify scanning at the network layer. These logs provide packet-level visibility into threats targeting your network. Unlike standard HTTP logs, these logs focus on <b>signature-based detections</b> at the network and transport layers (IP, TCP, UDP). Query to discover cases where a single <code>SourceIP</code> is triggering multiple unique detections across a wide range of <code>DestinationPort</code> values in a short timeframe. Magic IDS signatures can specifically flag activities like Nmap scans or SYN stealth scans.</p>
    <div>
      <h3>Check for diversions</h3>
      <a href="#check-for-diversions">
        
      </a>
    </div>
    <p>While the attacker is conducting reconnaissance, they may attempt to disguise this with a simultaneous network flood. Pivot to <code>network_analytics_logs</code> to see if a volumetric attack is being used as a smokescreen.</p>
    <div>
      <h3>Identify the approach </h3>
      <a href="#identify-the-approach">
        
      </a>
    </div>
    <p>Once attackers identify a potential vulnerability, they begin to craft their weapon. The attacker sends malicious payloads (e.g. SQL injection or large/corrupt file uploads) to confirm the vulnerability. Review <code>http_requests</code> and/or <code>fw_events</code> to identify any Cloudflare detection tools that have triggered. Cloudflare logs security signals in these datasets to easily identify requests with malicious payloads using fields such as <code>WAFAttackScore</code>, <code>WAFSQLiAttackScore</code>, <code>FraudAttack</code>, <code>ContentScanJobResults</code>, and several more. Review <a href="https://developers.cloudflare.com/logs/logpush/logpush-job/datasets/zone/http_requests/"><u>our documentation</u></a> to get a full understanding of these fields. The <code>fw_events</code> logs can be used to determine whether these requests made it past Cloudflare’s defenses by examining the <code>action</code>, <code>source</code>, and <code>ruleID</code> fields. Cloudflare’s managed rules by default blocks many of these payloads by default. Review Application Security Overview to know if your application is protected.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1zpFguYrnbOPwyASGQCqZK/63f398acce2226e453a5eea1cc749241/image3.png" />
          </figure><p><sup><i>Showing the Managed rules Insight that displays on Security Overview if the current zone does not have Managed Rules enabled</i></sup></p>
    <div>
      <h3>Audit the identity</h3>
      <a href="#audit-the-identity">
        
      </a>
    </div>
    <p>Did that suspicious IP manage to log in? Use the <code>ClientIP</code> to search <code>access_requests</code>. If you see a "<code>Decision: Allow</code>" for a sensitive internal app, you know you have a compromised account.</p>
    <div>
      <h3>Stop the leak (data exfiltration)</h3>
      <a href="#stop-the-leak-data-exfiltration">
        
      </a>
    </div>
    <p>Attackers sometimes use DNS tunneling to bypass firewalls by encoding sensitive data (like passwords or SSH keys) into DNS queries. Instead of a normal request like <code>google.com</code>, the logs will show long, encoded strings. Look for an unusually high volume of queries for unique, long, and high-entropy subdomains by examining the fields: <code>QueryName</code>: Look for strings like <a href="http://h3ldo293js92.example.com"><code><u>h3ldo293js92.example.com</u></code></a>, <code>QueryType</code>: Often uses <code>TXT</code>, <code>CNAME</code>, or <code>NULL</code> records to carry the payload, and <code>ClientIP</code>: Identify if a single internal host is generating thousands of these unique requests.</p><p>Additionally, attackers may attempt to leak sensitive data by hiding it within non-standard protocols or by using common protocols (like DNS or ICMP) in unusual ways to bypass standard firewalls. Discover this by querying the <code>magic_ids_detections</code> logs to look for signatures that flag protocol anomalies, such as "ICMP tunneling" or "DNS tunneling" detections in the <code>SignatureMessage</code>.</p><p>Whether you are investigating a zero-day vulnerability or tracking a sophisticated botnet, the data you need is now at your fingertips.</p>
    <div>
      <h2>Correlate across datasets</h2>
      <a href="#correlate-across-datasets">
        
      </a>
    </div>
    <p>Investigate malicious activity across multiple datasets by pivoting between multiple concurrent searches. With Log Explorer, you can now work with multiple queries simultaneously with the new Tabs feature. Switch between tabs to query different datasets or Pivot and adjust queries using filtering via your query results.</p><div>
  
</div>
<p></p><p>When you correlate data across multiple Cloudflare log sources, you can detect sophisticated multi-stage attacks that appear benign when viewed in isolation. This cross-dataset analysis allows you to see the full attack chain from reconnaissance to exfiltration.</p>
    <div>
      <h3>Session hijacking (token theft)</h3>
      <a href="#session-hijacking-token-theft">
        
      </a>
    </div>
    <p><b>Scenario:</b> A user authenticates via Cloudflare Access, but their subsequent HTTP_request traffic looks like a bot.</p><p><b>Step 1:</b> Identify high-risk sessions in <code>http_requests</code>.</p>
            <pre><code>SELECT RayID, ClientIP, ClientRequestUserAgent, BotScore
FROM http_requests
WHERE date = '2026-02-22' 
  AND BotScore &lt; 20 
LIMIT 100</code></pre>
            <p><b>Step 2:</b> Copy the <code>RayID</code> and search <code>access_requests</code> to see which user account is associated with that suspicious bot activity.</p>
            <pre><code>
SELECT Email, IPAddress, Allowed
FROM access_requests
WHERE date = '2026-02-22' 
  AND RayID = 'INSERT_RAY_ID_HERE'</code></pre>
            
    <div>
      <h3>Post-phishing C2 beaconing</h3>
      <a href="#post-phishing-c2-beaconing">
        
      </a>
    </div>
    <p><b>Scenario:</b> An employee clicked a link in a phishing email which resulted in compromising their workstation. This workstation sends a DNS query for a known malicious domain, then immediately triggers an IDS alert.</p><p><b>Step 1:</b> Find phishing attacks by examining email_security_alerts for violations. </p>
            <pre><code>SELECT Timestamp, Threatcategories, To, Alertreason
FROM email_security_alerts
WHERE date = '2026-02-22' 
  AND Threatcategories LIKE 'phishing'</code></pre>
            <p><b>Step 2:</b> Use Access logs to correlate the user’s email (To) to their IP Address.</p>
            <pre><code>SELECT Email, IPAddress
FROM access_requests
WHERE date = '2026-02-22' </code></pre>
            <p><b>Step 3:</b> Find internal IPs querying a specific malicious domain in <code>gateway_dns</code> logs.</p>
            <pre><code>
SELECT SrcIP, QueryName, DstIP, 
FROM gateway_dns
WHERE date = '2026-02-22' 
  AND SrcIP = 'INSERT_IP_FROM_PREVIOUS_QUERY'
  AND QueryName LIKE '%malicious_domain_name%'</code></pre>
            
    <div>
      <h3>Lateral movement (Access → network probing)</h3>
      <a href="#lateral-movement-access-network-probing">
        
      </a>
    </div>
    <p><b>Scenario:</b> A user logs in via Zero Trust and then tries to scan the internal network.</p><p><b>Step 1:</b> Find successful logins from unexpected locations in <code>access_requests</code>.</p>
            <pre><code>SELECT IPAddress, Email, Country
FROM access_requests
WHERE date = '2026-02-22' 
  AND Allowed = true 
  AND Country != 'US' -- Replace with your HQ country</code></pre>
            <p><b>Step 2:</b> Check if that <code>IPAddress</code> is triggering network-level signatures in <code>magic_ids_detections</code>.</p>
            <pre><code>SELECT SignatureMessage, DestinationIP, Protocol
FROM magic_ids_detections
WHERE date = '2026-02-22' 
  AND SourceIP = 'INSERT_IP_ADDRESS_HERE'</code></pre>
            
    <div>
      <h3>Opening doors for more data </h3>
      <a href="#opening-doors-for-more-data">
        
      </a>
    </div>
    <p>From the beginning, Log Explorer was designed with extensibility in mind. Every dataset schema is defined using JSON Schema, a widely-adopted standard for describing the structure and types of JSON data. This design decision has enabled us to easily expand beyond HTTP Requests and Firewall Events to the full breadth of Cloudflare's telemetry. The same schema-driven approach that powered our initial datasets scaled naturally to accommodate Zero Trust logs, network analytics, email security alerts, and everything in between.</p><p>More importantly, this standardization opens the door to ingesting data beyond Cloudflare's native telemetry. Because our ingestion pipeline is schema-driven rather than hard-coded, we're positioned to accept any structured data that can be expressed in JSON format. For security teams managing hybrid environments, this means Log Explorer could eventually serve as a single pane of glass, correlating Cloudflare's edge telemetry with logs from third-party sources, all queryable through the same SQL interface. While today's release focuses on completing coverage of Cloudflare's product portfolio, the architectural groundwork is laid for a future where customers can bring their own data sources with custom schemas.</p>
    <div>
      <h3>Faster data, faster response: architectural upgrades</h3>
      <a href="#faster-data-faster-response-architectural-upgrades">
        
      </a>
    </div>
    <p>To investigate a multi-vector attack effectively, timing is everything. A delay of even a few minutes in the log availability can be the difference between proactive defense and reactive damage control.</p><p>That is why we have optimized our ingestion for better speed and resilience. By increasing concurrency in one part of our ingestion path, we have eliminated bottlenecks that could cause “noisy neighbor” issues, ensuring that one client’s data surge doesn’t slow down another’s visibility. This architectural work has reduced our P99 ingestion latency by approximately 55%, and our P50 by 25%, cutting the time it takes for an event at the edge to become available for your SQL queries.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/41M2eWP0BwrQFSZW4GzZbV/7a6139354abb561aba17e77d83beb17a/image4.png" />
          </figure><p><sup><i>Grafana chart displaying the drop in ingest latency after architectural upgrades</i></sup></p>
    <div>
      <h2>Follow along for more updates</h2>
      <a href="#follow-along-for-more-updates">
        
      </a>
    </div>
    <p>We're just getting started. We're actively working on even more powerful features to further enhance your experience with Log Explorer, including the ability to run these detection queries on a custom defined schedule. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2JIOu9PXDwVAVcmbgq456q/1eace4b5d38bb705e82442a4ee8045dc/Scheduled_Queries_List.png" />
          </figure><p><sup><i>Design mockup of upcoming Log Explorer Scheduled Queries feature</i></sup></p><p><a href="https://blog.cloudflare.com/"><u>Subscribe to the blog</u></a> and keep an eye out for more Log Explorer updates soon in our <a href="https://developers.cloudflare.com/changelog/product/log-explorer/"><u>Change Log</u></a>. </p>
    <div>
      <h2>Get access to Log Explorer</h2>
      <a href="#get-access-to-log-explorer">
        
      </a>
    </div>
    <p>To get access to Log Explorer, you can purchase self-serve directly from the dash or for contract customers, reach out for a <a href="https://www.cloudflare.com/application-services/products/log-explorer/"><u>consultation</u></a> or contact your account manager. Additionally, you can read more in our <a href="https://developers.cloudflare.com/logs/log-explorer/"><u>Developer Documentation</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Analytics]]></category>
            <category><![CDATA[Logs]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[R2]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[SIEM]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Connectivity Cloud]]></category>
            <guid isPermaLink="false">1hirraqs3droftHovXp1G6</guid>
            <dc:creator>Jen Sells</dc:creator>
            <dc:creator>Claudio Jolowicz</dc:creator>
            <dc:creator>Nico Gutierrez</dc:creator>
        </item>
        <item>
            <title><![CDATA[Improve global upload performance with R2 Local Uploads]]></title>
            <link>https://blog.cloudflare.com/r2-local-uploads/</link>
            <pubDate>Tue, 03 Feb 2026 14:00:00 GMT</pubDate>
            <description><![CDATA[ Local Uploads on R2 reduces request duration for uploads by up to 75%. It writes object data to a nearby location and asynchronously copies it to your bucket, all while data is available immediately.  ]]></description>
            <content:encoded><![CDATA[ <p>Today, we are launching<b> Local Uploads</b> for R2 in <b>open beta</b>. With <a href="https://developers.cloudflare.com/r2/buckets/local-uploads/"><u>Local Uploads</u></a> enabled, object data is automatically written to a storage location close to the client first, then asynchronously copied to where the bucket lives. The data is immediately accessible and stays <a href="https://developers.cloudflare.com/r2/reference/consistency/"><u>strongly consistent</u></a>. Uploads get faster, and data feels global.</p><p>For many applications, performance needs to be global. Users uploading media content from different regions, for example, or devices sending logs and telemetry from all around the world. But your data has to live somewhere, and that means uploads from far away have to travel the full distance to reach your bucket. </p><p><a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>R2</u></a> is <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/"><u>object storage</u></a> built on Cloudflare's global network. Out of the box, it automatically caches object data globally for fast reads anywhere — all while retaining strong consistency and zero <a href="https://www.cloudflare.com/learning/cloud/what-are-data-egress-fees/"><u>egress fees</u></a>. This happens behind the scenes whether you're using the <a href="https://www.cloudflare.com/developer-platform/solutions/s3-compatible-object-storage/">S3</a> API, Workers Bindings, or plain HTTP. And now with Local Uploads, both reads and writes can be fast from anywhere in the world.</p><p>Try it yourself <a href="https://local-uploads.r2-demo.workers.dev/"><u>in this demo</u></a> to see the benefits of Local Uploads.</p><p>Ready to try it? Enable Local Uploads in the <a href="https://dash.cloudflare.com/?to=/:account/r2/overview"><u>Cloudflare Dashboard</u></a> under your bucket's settings, or with a single Wrangler command on an existing bucket.</p>
            <pre><code>npx wrangler r2 bucket local-uploads enable [BUCKET]</code></pre>
            
    <div>
      <h2>75% lower total request duration for global uploads</h2>
      <a href="#75-lower-total-request-duration-for-global-uploads">
        
      </a>
    </div>
    <p><a href="https://developers.cloudflare.com/r2/buckets/local-uploads"><u>Local Uploads</u></a> makes upload requests (i.e. PutObject, UploadPart) faster. In both our private beta tests with customers and our synthetic benchmarks, we saw up to 75% reduction in Time to Last Byte (TTLB) when upload requests are made in a different region than the bucket. In these results, TTLB is measured from when R2 receives the upload request to when R2 returns a 200 response.</p><p>In our synthetic tests, we measured the impact of Local Uploads by using a synthetic workload to simulate a cross-region upload workflow. We deployed a test client in Western North America and configured an R2 bucket with a <a href="https://developers.cloudflare.com/r2/reference/data-location/"><u>location hint</u></a> for Asia-Pacific. The client performed around 20 PutObject requests per second over 30 minutes to upload objects of 5 MB size. </p><p>The following graph compares the p50 (or median) TTLB metrics for these requests, showing the difference in upload request duration — first without Local Uploads (TTLB around 2s), and then with Local Uploads enabled (TTLB around 500ms): </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4uvSdPwflyjHohwLQvOKsu/4b82637a5ac29ceee0fc37e04ab0107f/image1.png" />
          </figure>
    <div>
      <h2>How it works: The distance problem</h2>
      <a href="#how-it-works-the-distance-problem">
        
      </a>
    </div>
    <p>To understand how Local Uploads can improve upload requests, let’s first take a look at <a href="https://developers.cloudflare.com/r2/how-r2-works/"><u>how R2 works</u></a>. R2's architecture is composed of multiple components including:</p><ul><li><p><b>R2 Gateway Worker: </b>The entry point for all API requests that handles authentication and routing logic. It is deployed across Cloudflare's global network via <a href="https://developers.cloudflare.com/workers/"><u>Cloudflare Workers</u></a>.</p></li><li><p><b>Durable Object Metadata Service: </b>A distributed layer built on <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a> used to store and manage object metadata (e.g. object key, checksum).</p></li><li><p><b>Distributed Storage Infrastructure: </b>The underlying infrastructure that persistently stores encrypted object data.</p></li></ul><p>Without Local Uploads, here’s what happens when you upload objects to your bucket: The request is first received by the R2 Gateway, close to the user, where it is authenticated. Then, as the client streams bytes of the object data, the data is encrypted and written into the storage infrastructure in the region where the bucket is placed. When this is completed, the Gateway reaches out to the Metadata Service to publish the object metadata, and it returns a success response back to the client after it is committed.</p><p>If the client and the bucket are in separate regions, more variability can be introduced in the process of uploading bytes of the object data, due to the longer distance that the request must travel. This could result in slower or less reliable uploads. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6toAZ6JSHPv2jgntdyCOvr/704f6837d2705f18a0e5b8554994cb7a/image9.png" />
          </figure><p><sup>A client uploading from Eastern North America to a bucket in Eastern Europe without Local Uploads enabled. </sup></p><p>Now, when you make an upload request to a bucket with Local Uploads enabled, there are two cases that are handled: </p><ol><li><p>The client and the bucket region are in the <b>same</b> region</p></li><li><p>The client and the bucket region are in <b>different</b> regions</p></li></ol><p>In the first case, R2 follows the regular flow, where object data is written to the storage infrastructure for your bucket. In the second case, R2 writes to the storage infrastructure located in the client region while still publishing to the object metadata to the region of the bucket.</p><p>Importantly, the object is immediately accessible after the initial write completes. It remains accessible throughout the entire replication process — there's <b>no</b> <b>waiting period</b> for background replication to finish before the object can be read.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/33oUAdlGF8cWOeQhha6Ocy/68537e503f1ec8d1dd080db363f97dc3/image3.png" />
          </figure><p><sup>A client uploading from Eastern North America to a bucket in Eastern Europe with Local Uploads enabled. </sup></p><p>Note that this is for non-jurisdiction restricted buckets, and Local Uploads are not available for buckets with jurisdiction restriction (e.g. EU, FedRAMP) enabled.</p>
    <div>
      <h2>When to use Local Uploads</h2>
      <a href="#when-to-use-local-uploads">
        
      </a>
    </div>
    <p>Local uploads are built for workloads that receive a lot of upload requests originating from different geographic regions than where your bucket is located. This feature is ideal when:</p><ul><li><p>Your users are globally distributed</p></li><li><p>Upload performance and reliability is critical to your application</p></li><li><p>You want to optimize write performance without changing your bucket's primary location</p></li></ul><p>To understand the geographic distribution of where your read and write requests are initiated, you can visit the <a href="https://dash.cloudflare.com/?to=/:account/r2/overview"><u>Cloudflare Dashboard</u></a>, and go to your R2 bucket’s Metrics page and view the Request Distribution by Region graph. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6SJ9UYY3RADryXmnT0J3Vq/9b26c948e925a705387a64c24a1dd7e3/image7.png" />
          </figure>
    <div>
      <h2>How we built Local Uploads</h2>
      <a href="#how-we-built-local-uploads">
        
      </a>
    </div>
    <p>With Local Uploads, object data is written close to the client and then copied to the bucket's region in the background. We call this copy job a replication task.</p><p>Given these replication tasks, we needed an asynchronous processing component for them, which tends to be a great use case for <a href="https://developers.cloudflare.com/queues/"><u>Cloudflare Queues</u></a>. Queues allow us to control the rate at which we process replication tasks, and it provides built-in failure handling capabilities like <a href="https://developers.cloudflare.com/queues/configuration/batching-retries/"><u>retries</u></a> and <a href="https://developers.cloudflare.com/queues/configuration/dead-letter-queues/"><u>dead letter queues</u></a>. In this case, R2 shards replication tasks across multiple queues per storage region.</p>
    <div>
      <h3>Publishing metadata and scheduling replication</h3>
      <a href="#publishing-metadata-and-scheduling-replication">
        
      </a>
    </div>
    <p>When publishing the metadata of an object with Local Uploads enabled, we perform three operations atomically:</p><ol><li><p>Store the object metadata</p></li><li><p>Create a pending replica key that tracks which replications still need to happen</p></li><li><p>Create a replication task marker keyed by timestamp, which controls when the task should be sent to the queue</p></li></ol><p>The pending replica key contains the full replication plan: the number of replication tasks, which source location to read from, which destination location to write to, the replication mode and priority, and whether the source should be deleted after successful replication.</p><p>This gives us flexibility in how we move an object's data. For example, moving data across long geographical distances is expensive. We could try to move all the replicas as fast as possible by processing them in parallel, but this would incur greater cost and pressure the network infrastructure. Instead, we minimize the number of cross-regional data movements by first creating one replica in the target bucket region, and then use this local copy to create additional replicas within the bucket region.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2rCuA2zXR4ltZJsiNDBHd7/ae388f13ea27922158b27f429080c69c/image6.png" />
          </figure><p>A background process periodically scans the replication task markers and sends them to one of the queues associated with the destination storage region. The markers guarantee at-least-once delivery to the queue — if enqueueing fails or the process crashes, the marker persists and the task will be retried on the next scan. This also allows us to process replications at different times and enqueue only valid tasks. Once a replication task reaches a queue, it is ready to be processed.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5G4STZSp67TnKhehzCqFMv/445a0c74ba7f4bc5dd3de04eb7aa1257/image4.png" />
          </figure>
    <div>
      <h3>Asynchronous replication: Pull model</h3>
      <a href="#asynchronous-replication-pull-model">
        
      </a>
    </div>
    <p>For the queue consumer, we chose a pull model where a centralized polling service consumes tasks from the regional queues and dispatches them to the Gateway Worker for execution.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2p6SHkqO1tT7wxdhPJCFCr/86f219af85e332813ede2eb95a3810d8/image2.png" />
          </figure><p>Here's how it works:</p><ol><li><p>Polling service pulls from a regional queue: The consumer service polls the regional queue for replication tasks. It then batches the tasks to create uniform batch sizes based on the amount of data to be moved.</p></li><li><p>Polling service dispatches to Gateway Worker: The consumer service sends the replication job to the Gateway Worker.</p></li><li><p>Gateway Worker executes replication: The worker reads object data from the source location, writes it to the destination, and updates metadata in the Durable Object, optionally marking the source location to be garbage collected.</p></li><li><p>Gateway Worker reports result: On completion, the worker returns the result to the poller, which acknowledges the task to the queue as completed or failed.</p></li></ol><p>By using this pull model approach, we ensure that the replication process remains stable and efficient. The service can dynamically adjust its pace based on real-time system health, guaranteeing that data is safely replicated across regions.</p>
    <div>
      <h2>Try it out</h2>
      <a href="#try-it-out">
        
      </a>
    </div>
    <p>Local Uploads is available now in open beta. There is <b>no additional cost</b> to enable Local Uploads. Upload requests made with this feature enabled incur the standard <a href="https://developers.cloudflare.com/r2/pricing/"><u>Class A operation costs</u></a>, same as upload requests made without Local Uploads.</p><p>To get started, visit the <a href="https://dash.cloudflare.com/?to=/:account/r2/overview"><u>Cloudflare Dashboard</u></a> under your bucket's settings and look for the Local Uploads card to enable, or simply run the following command using Wrangler to enable Local Uploads on a bucket.</p>
            <pre><code>npx wrangler r2 bucket local-uploads enable [BUCKET]</code></pre>
            <p>Enabling Local Uploads on a bucket is seamless: existing uploads will complete as expected and there’s no interruption to traffic.</p><p>For more information, refer to the <a href="https://developers.cloudflare.com/r2/buckets/local-uploads/"><u>Local Uploads documentation</u></a>. If you have questions or want to share feedback, join the discussion on our <a href="https://discord.gg/cloudflaredev"><u>Developer Discord</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[R2]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Storage]]></category>
            <guid isPermaLink="false">453lZMuYluqGqfRKADhf9K</guid>
            <dc:creator>Frank Chen</dc:creator>
            <dc:creator>Rahul Suresh</dc:creator>
            <dc:creator>Anni Wang</dc:creator>
        </item>
        <item>
            <title><![CDATA[Quicksilver v2: evolution of a globally distributed key-value store (Part 2)]]></title>
            <link>https://blog.cloudflare.com/quicksilver-v2-evolution-of-a-globally-distributed-key-value-store-part-2-of-2/</link>
            <pubDate>Thu, 17 Jul 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ This is part two of a story about how we overcame the challenges of making a complex system more scalable. ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>What is Quicksilver?</h2>
      <a href="#what-is-quicksilver">
        
      </a>
    </div>
    <p>Cloudflare has servers in <a href="https://www.cloudflare.com/network"><u>330 cities spread across 125+ countries</u></a>. All of these servers run Quicksilver, which is a key-value database that contains important configuration information for many of our services, and is queried for all requests that hit the Cloudflare network.</p><p>Because it is used while handling requests, Quicksilver is designed to be very fast; it currently responds to 90% of requests in less than 1 ms and 99.9% of requests in less than 7 ms. Most requests are only for a few keys, but some are for hundreds or even more keys.</p><p>Quicksilver currently contains over five billion key-value pairs with a combined size of 1.6 TB, and it serves over three billion keys per second, worldwide. Keeping Quicksilver fast provides some unique challenges, given that our dataset is always growing, and new use cases are added regularly.</p><p>Quicksilver used to store all key-values on all servers everywhere, but there is obviously a limit to how much disk space can be used on every single server. For instance, the more disk space used by Quicksilver, the less disk space is left for content caching. Also, with each added server that contains a particular key-value, the cost of storing that key-value increases.</p><p>This is why disk space usage has been the main battle that the Quicksilver team has been waging over the past several years. A lot was done over the years, but we now think that we have finally created an architecture that will allow us to get ahead of the disk space limitations and finally make Quicksilver scale better.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2mGYCFdZIWdYl1TuDzvlAf/45d1b975adce0ec4eb5cc2842c8bd185/image1.png" />
          </figure><p><sup>The size of the Quicksilver database has grown by 50% to about 1.6 TB in the past year</sup></p>
    <div>
      <h2>What we talked about previously</h2>
      <a href="#what-we-talked-about-previously">
        
      </a>
    </div>
    <p><a href="https://blog.cloudflare.com/quicksilver-v2-evolution-of-a-globally-distributed-key-value-store-part-1/"><u>Part one of the story</u></a> explained how Quicksilver V1 stored all key-value pairs on each server all around the world. It was a very simple and fast design, it worked very well, and it was a great way to get started. But over time, it turned out to not scale well from a disk space perspective.</p><p>The problem was that disk space was running out so fast that there was not enough time to design and implement a fully scalable version of Quicksilver. Therefore, Quicksilver V1.5 was created first. It halved the disk space used on each server compared to V1.</p><p>For this, a new <i>proxy </i>mode was introduced for Quicksilver. In this mode, Quicksilver does not contain the full dataset anymore, but only contains a cache. All cache misses are looked up on another server that runs Quicksilver with a full dataset. Each server runs about ten separate <i>instances</i> of Quicksilver, and all have different databases with different sets of key-values. We call Quicksilver instances with the full data set <i>replicas</i>.</p><p>For Quicksilver V1.5, half of those instances on a particular server would run Quicksilver in proxy mode, and therefore would not have the full dataset anymore. The other half would run in replica mode. This worked well for a time, but it was not the final solution.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2GHG0tl5diZ7w09qNkblyd/6fa0ba250477cb9027496669640a0acb/image4.png" />
          </figure><p>Building this intermediate solution had the added benefit of allowing the team to gain experience running an even more distributed version of Quicksilver.</p>
    <div>
      <h2>The problem</h2>
      <a href="#the-problem">
        
      </a>
    </div>
    <p>There were a few reasons why Quicksilver V1.5 was not fully scalable.</p><p>First, the size of the separate instances were not very stable. The key-space is owned by the teams that use Quicksilver, not by the Quicksilver team, and the way those teams use Quicksilver changes frequently. Furthermore, while most instances grow in size over time, some instances have actually gotten smaller, such as when the use of Quicksilver is optimised by teams. The result of this is that the split of instances that was well-balanced at the start, quickly became unbalanced.</p><p>Second, the analyses that were done to estimate how much of the key space would need to be in cache on each server assumed that taking all keys that were accessed in a three-day period would represent a good enough cache. This assumption turned out to be wildly off. This analysis estimated that we needed about 20% of the key space in cache, which turned out to not be entirely accurate. Whereas most instances did have a good cache hit rate, with 20% or less of the key space in cache, some instances turned out to need a much higher percentage.</p><p>The main issue, however, was that reducing the disk space used by Quicksilver on our network by as much as 40% does not actually make it more scalable. The number of key-values that are stored in Quicksilver keeps growing. It only took about two years before disk space was running low again.</p>
    <div>
      <h2>The solution</h2>
      <a href="#the-solution">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4G3Y1t9OeF7BlsASIwlpI7/8df9e21affde869fbdd41f203cbcd256/image6.png" />
          </figure><p><sup>Except for a handful of special storage servers, Quicksilver does not contain the full dataset anymore, but only cache. Any cache misses will be looked up in replicas on our storage servers, which do have the full dataset.</sup></p><p>The solution to the scalability problem was brought on by a new insight. As it turns out, numerous key-values were actually almost never used. We call these <i>cold keys</i>. There are different reasons for these cold keys: some of them were old and not well cleaned up, some were used only in certain regions or in certain data centers, and some were not used for a very long time or maybe not at all (a domain name that is never looked up for example or a script that was uploaded but never used).</p><p>At first, the team had been considering solving our scalability problem by splitting up the entire dataset into shards and distributing those across the servers in the different data centers. But sharding the full dataset adds a lot of complexity, corner cases, and unknowns. Sharding also does not optimize for data locality. For example, if the key-space is split into 4 shards and each server gets one shard, that server can only serve 25% of the requested keys from its local database. The cold keys would also still be contained in those shards and would take up disk space unnecessarily.</p><p>Another data structure that is much better at data locality and explicitly avoids storing keys that are never used is a cache. So it was decided that only a handful of servers with large disks would maintain the full data set, and all other servers would only have a cache. This was an obvious evolution from Quicksilver V1.5. Caching was already being done on a smaller scale, so all the components were already available. The caching proxies and the inter-data center discovery mechanisms were already in place. They had been used since 2021 and were therefore thoroughly battle tested. However, one more component needed to be added.</p><p>There was a concern that having all instances on all servers connect to a handful of storage nodes with replicas would overload them with too many connections. So a Quicksilver <i>relay</i> was added. For each instance, a few servers would be elected within each data center on which Quicksilver would run in relay mode. The relays would maintain the connections to the replicas on the storage nodes. All proxies inside a data center would discover those relays and all cache misses would be relayed through them to the replicas.</p><p>This new architecture worked very well. The cache hit rates still needed some improvement, however.</p>
    <div>
      <h3>Prefetching the future</h3>
      <a href="#prefetching-the-future">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/48FbryG5MCUAmQNY34jtOK/5e57c62fafa81fa323cf4ef77ccded55/Prefetching_the_future.png" />
          </figure><p><sup>Every resolved cache miss is prefetched by all servers in the data center</sup></p><p>We had a hypothesis that prefetching all keys that were cache misses on the other servers inside the same data center would improve the cache hit rate. So an analysis was done, and it indeed showed that every key that was a cache miss on one server in a data center had a very high probability of also being a cache miss on another server in the same data center sometime in the near future. Therefore, a mechanism was built that distributed all resolved cache misses on relays to all other servers.</p><p>All cache misses in a data center are resolved by requesting them from a relay, which subsequently forwards the requests to one of the replicas on the storage nodes. Therefore, the prefetching mechanism was implemented by making relays publish a stream of all resolved cache misses, to which all Quicksilver proxies in the same data center subscribe. The resulting key-values were then added to the proxy local caches.</p><p>This strategy is called <i>reactive</i> prefetching, because it fills caches only with the key-values that directly resulted from cache misses inside the same data center. Those prefetches are a <i>reaction</i> to the cache misses. Another way of prefetching is called <i>predictive</i> prefetching, in which an algorithm tries to predict which keys that have not yet been requested will be requested in the near future. A few approaches for making these predictions were tried, but they did not result in any improvement, and so this idea was abandoned.</p><p>With the prefetching enabled, cache hit rates went up to about 99.9% for the worst performing instance. This was the goal that we were trying to reach. But while rolling this out to more of our network, it turned out that there was one team that needed an even higher cache hit rate, because the tail latencies they were seeing with this new architecture were too high.</p><p>This team was using a Quicksilver instance called <i>dnsv2</i>. This is a very latency sensitive instance, because it is the one from which DNS queries are served. Some of the DNS queries under the hood need multiple queries to Quicksilver, so any added latency to Quicksilver multiplies for them. This is why it was decided that one more improvement to the Quicksilver cache was needed.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7fKjcIASCIsaW52NDTF6OX/25cc40046536b28fca5b44249234cc48/image12.png" />
          </figure><p><sup>The level 1 cache hit-rate is 99.9% or higher, on average.</sup></p>
    <div>
      <h3>Back to the sharding</h3>
      <a href="#back-to-the-sharding">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/10ur3qVEVDt4carNCqldch/0777d16f53fd501354f7196caac4d9fe/back-to-sharding.png" />
          </figure><p><sup>Before going to a replica in another data center, a cache miss is first looked up in a data center-wide sharded cache</sup></p><p>The instance on which higher cache hit rates were required was also the instance on which the cache performed the worst. The cache works with a retention time, defined as the number of days a key-value is kept in cache after it was last accessed, after which it is evicted from the cache. An analysis of the cache showed that this instance needed a much longer retention time. But, a higher retention time also causes the cache to take up more disk space — space that was not available.</p><p>However, while running Quicksilver V1.5, we had already noticed the pattern that caches generally performed much better in smaller data centers as compared to larger ones. This sparked the hypothesis that led to the final improvement.</p><p>It turns out that smaller data centers, with fewer servers, generally needed less disk space for their cache. Vice versa, the more servers there are in a data center, the larger the Quicksilver cache needs to be. This is easily explained by the fact that larger data centers generally serve larger populations, and therefore have a larger diversity of requests. More servers also means more total disk space available inside the data center. To be able to make use of this pattern the concept of sharding was reintroduced.</p><p>Our key space was split up into multiple shards. Each server in a data center was assigned one of the shards. Instead of those shards containing the full dataset for their part of the key space, they contain a cache for it. Those cache shards are populated by all cache misses inside the data center. This all forms a data center-wide cache that is distributed using sharding.</p><p>The data locality issue that sharding the full dataset has, as described above, is solved by keeping the local per-server caches as well. The sharded cache is in addition to the local caches. All servers in a data center contain both their local cache and a cache for one physical shard of the sharded cache. Therefore, each requested key is first looked up in the server’s local cache, after that the data center-wide sharded cache is queried, and finally if both caches miss the requested key, it is looked up on one of the storage nodes.</p><p>The key space is split up into separate shards by first dividing hashes of the keys by range into 1024 logical shards. Those logical shards are then divided up into physical shards, again by range. Each server gets one physical shard assigned by repeating the same process on the server hostname.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4fKK9EPdwP69l4VtIErNGW/5481fb93b8e89fff6f5bdc6dd2dfbb44/image7.png" />
          </figure><p><sup>Each server contains one physical shard. A physical shard contains a range of logical shards. A local shard contains a range of the ordered set that result from hashing all keys.</sup></p><p>This approach has the advantage that the sharding factor can be scaled up by factors of two without the need for copying caches to other servers. When the sharding factor is increased in this way, the servers will automatically get a new physical shard assigned that contains a subset of the key space that the previous physical shard on that server contained. After this has happened, their cache will contain supersets of the needed cache. The key-values that are not needed anymore will be evicted over time.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2IBc0EMQEDeenGX2ShbrrM/d661ef58d7114caa3c77b9eb5b18b736/image10.png" />
          </figure><p><sup>When the number of physical shards are doubled the servers will automatically get new physical shards that are subsets of their previous physical shards, therefore still have the relevant key-values in cache.</sup></p><p>This approach means that the sharded caches can easily be scaled up when needed as the number of keys that are in Quicksilver grows, and without any need for relocating data. Also, shards are well-balanced due to the fact that they contain uniform random subsets of a very large key-space.</p><p>Adding new key-values to the physical cache shards piggybacks on the prefetching mechanism, which already distributes all resolved cache misses to all servers in a data center. The keys that are part of the key space for a physical shard on a particular server are just kept longer in cache than the keys that are not part of that physical shard.</p><p>Another reason why a sharded cache is simpler than sharding the full key-space is that it is possible to cut some corners with a cache. For instance, looking up older versions of key-values (as used for  multiversion concurrency control) is not supported on cache shards. As explained in an <a href="https://blog.cloudflare.com/quicksilver-v2-evolution-of-a-globally-distributed-key-value-store-part-1/"><u>earlier blog post</u></a>, this is needed for consistency when looking up key-values on different servers, when that server has a newer version of the database. It is not needed in the cache shards, because lookups can always fall back to the storage nodes when the right version is not available.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7qKie4ZFES4yJw1Sa2K26X/3d326d4e8e7553dcf96738ceae7229fe/image9.png" />
          </figure><p><sup>Proxies have a recent keys window that contains all recently written key-values. A cache shard only has its cached key-values. Storage replicas contain all key-values and on top of that they contain multiple versions for recently written key-values. When the proxy, that has database version 1000, has a cache miss for </sup><sup><i>key1</i></sup><sup> it can be seen that the version of that key on the cache shard was written at database version 1002 and therefore is too new. This means that it is not consistent with the proxy’s database version. This is why the relay will fetch that key from a replica instead, which can return the earlier consistent version. In contrast, </sup><sup><i>key2</i></sup><sup> on the cache shard can be used, because it was written at index 994, well below the database version of the proxy.</sup></p><p>There is only one very specific corner case in which a key-value on a cache shard cannot be used. This happens when the key-value in the cache shard was written at a more recent database version than the version of the proxy database at that time. This would mean that the key-value probably has a different value than it had at the correct version. Because, in general, the cache shard and the proxy database versions are very close to each other, and this only happens for key-values that were written in between those two database versions, this happens very rarely. As such, deferring the lookup to storage nodes has no noticeable effect on the cache hit rate.</p>
    <div>
      <h3>Tiered Storage</h3>
      <a href="#tiered-storage">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2bLnTL9WWzQfMqjyXv5o6Q/1c1bf1cb348aa736fb320c77b52e10dd/image3.png" />
          </figure><p>To summarize, Quicksilver V2 has three levels of storage.</p><ol><li><p>Level 1: The local cache on each server that contains the key-values that have most recently been accessed.</p></li><li><p>Level 2: The data center wide sharded cache that contains key-values that haven’t been accessed in a while, but do have been accessed.</p></li><li><p>Level 3: The replicas on the storage nodes that contain the full dataset, which live on a handful of storage nodes and are only queried for the <i>cold</i> keys.</p></li></ol>
    <div>
      <h2>The results</h2>
      <a href="#the-results">
        
      </a>
    </div>
    <p>The percentage of keys that can be resolved within a data center improved significantly by adding the second caching layer. The worst performing instance has a cache hit rate higher than 99.99%. All other instances have a cache hit rate that is higher than 99.999%.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6V5BgOVPsTrYQf4WuUKdmg/67756312764cdab789eff1ff33caf0f3/image5.png" />
          </figure><p><sup>The combined level 1 and level 2 cache hit-rate is 99.99% or higher for the worst caching instance.</sup></p>
    <div>
      <h2>Final notes</h2>
      <a href="#final-notes">
        
      </a>
    </div>
    <p>It took the team quite a few years to go from the old Quicksilver V1, where all data was stored on each server to the tiered caching Quicksilver V2, where all but a handful of servers only have cache. We faced many challenges, including migrating hundreds of thousands of live databases without interruptions, while serving billions of requests per second. A lot of code changes were rolled out, with the result that Quicksilver now has a significantly different architecture. All of this was done transparently to our customers. It was all done iteratively, always learning from the previous step before taking the next one. And always making sure that, if at all possible, all changes are easy to revert. These are important strategies for migrating complex systems safely.</p><p>If you like these kinds of stories, keep an eye out for more development stories on our blog. And if you are enthusiastic about solving these kinds of problems, <a href="https://www.cloudflare.com/en-gb/careers/jobs/"><u>we are hiring for multiple types of roles across the organization</u></a></p>
    <div>
      <h2>Thank you</h2>
      <a href="#thank-you">
        
      </a>
    </div>
    <p>And finally, a big thanks to the rest of the Quicksilver team, because we all do this together: Aleksandr Matveev, Aleksei Surikov, Alex Dzyoba, Alexandra (Modi) Stana-Palade, Francois Stiennon, Geoffrey Plouviez, Ilya Polyakovskiy, Manzur Mukhitdinov, Volodymyr Dorokhov.</p> ]]></content:encoded>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Quicksilver]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Key Value]]></category>
            <guid isPermaLink="false">5oxVTbLoVGabfWNbMtNbs</guid>
            <dc:creator>Marten van de Sanden</dc:creator>
            <dc:creator>Anton Dort-Golts</dc:creator>
        </item>
        <item>
            <title><![CDATA[Quicksilver v2: evolution of a globally distributed key-value store (Part 1)]]></title>
            <link>https://blog.cloudflare.com/quicksilver-v2-evolution-of-a-globally-distributed-key-value-store-part-1/</link>
            <pubDate>Thu, 10 Jul 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ This blog post is the first of a series, in which we share our journey in redesigning Quicksilver — Cloudflare’s distributed key-value store that serves over 3 billion keys per second globally.  ]]></description>
            <content:encoded><![CDATA[ <p>Quicksilver is a key-value store developed internally by Cloudflare to enable fast global replication and low-latency access on a planet scale. It was <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale/"><u>initially designed</u></a> to be a global distribution system for configurations, but over time it gained popularity and became the foundational storage system for many products in Cloudflare.</p><p>A previous <a href="https://blog.cloudflare.com/moving-quicksilver-into-production/"><u>post</u></a> described how we moved Quicksilver to production and started replicating on all machines across our global network. That is what we called Quicksilver v1: each server has a full copy of the data and updates it through asynchronous replication. The design served us well for some time. However, as our business grew with an ever-expanding data center footprint and a growing dataset, it became more and more expensive to store everything everywhere.</p><p>We realized that storing the full dataset on every server is inefficient. Due to the uniform design, data accessed in one region or data center is replicated globally, even if it's never accessed elsewhere. This leads to wasted disk space. We decided to introduce a more efficient system with two new server roles: <b><i>replica</i></b>, which stores the full dataset and <b><i>proxy</i></b>, which acts as a persistent cache, evicting unused key-value pairs to free up some disk space. We call this design <b>Quicksilver v1.5</b> – an interim step towards a more sophisticated and scalable system.</p><p>To understand how those two roles helped us reduce disk space usage, we first need to share some background on our setup and introduce some terminology. Cloudflare is architected in a way where we have a few hyperscale core data centers that form our <a href="https://www.cloudflare.com/learning/network-layer/what-is-the-control-plane/"><u>control plane</u></a>, and many smaller data centers distributed across the globe where resources are more constrained. Quicksilver has dozens of servers in the core data centers with terabytes of storage called <b><i>root nodes</i></b>. In the smaller data centers, though, things are different. A typical data center has two types of nodes: <b><i>intermediate nodes </i></b><i>and </i><b><i>leaf nodes.</i></b> Intermediate servers replicate data either from the other intermediate nodes or directly from the root nodes. Leaf nodes serve end user traffic, and receive updates from intermediate servers, effectively being leaves of a replication tree. Disk capacity varies significantly between node types. While root nodes aren't facing an imminent disk space bottleneck, it's a definite concern for leaf nodes.</p><p>Every server – whether it’s a root, intermediate, or leaf – hosts 10 Quicksilver <b><i>instances</i></b>. These are independent databases, each used by specific Cloudflare services or products such as the <a href="https://www.cloudflare.com/application-services/products/dns/"><u>DNS</u></a>, <a href="https://www.cloudflare.com/application-services/products/cdn/"><u>CDN</u></a>  or <a href="https://www.cloudflare.com/application-services/products/waf/"><u>WAF</u></a>. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/336Tl3Q00d5MVe29nzkQcY/3781ee725f2da6a847677235c1c3c960/image5.png" />
          </figure><p><sup>Figure 1. Global Quicksilver</sup></p><p>Let’s consider the role distribution. Instead of hosting ten full datasets on every machine within a data center, what if we deploy only a few replicas in each?  The remaining servers would be proxies, maintaining a persistent cache of hot keys and querying replicas for any cache misses.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3RzCZ7Yr1Nx3wd28DL5uco/5e4bd0cd0ce649080e77814ab6f048c0/image1.png" />
          </figure><p><sup>Figure 2. Role allocation for different Quicksilver instances</sup></p><p>Data centers across our network are very different in size, ranging from hundreds of servers to a single rack with just a few servers. To ensure every data center has at least one replica, the simplest initial step is an even split: on each server, place five replicas of some instances and five proxies for others. The change immediately frees up disk space, as the cached hot dataset on a proxy should be smaller than a full replica. While it doesn’t remove the bottleneck entirely, it could, in theory, lead to an up to 50% reduction in disk space usage. More importantly, it lays the foundation for a new distributed design of Quicksilver, where queries can be served by multiple machines in a data center, paving the way for further horizontal scaling. Additionally, an iterative approach helps to battle-proof the code changes earlier.</p>
    <div>
      <h2>Can it even work?</h2>
      <a href="#can-it-even-work">
        
      </a>
    </div>
    <p>Before committing to building Quicksilver v1.5, we wanted to be sure that the proxy/replica design would actually work for our workload. If proxies needed to cache the entire dataset for good performance, then it would be a dead end, offering no potential disk space benefits. To assess this, we built a data pipeline which pushes accessed keys from all across our network to <a href="https://clickhouse.com"><u>ClickHouse</u></a>. This allowed us to estimate typical sizes of working sets. Our analysis revealed that:</p><ul><li><p>in large data centers approximately, 20% of the keyspace was in use</p></li><li><p>in small data centers this number dropped to just about 1%</p></li></ul><p>These findings gave us confidence that the caching approach should work, though it wouldn’t be without its challenges.</p>
    <div>
      <h2>Persistent caching</h2>
      <a href="#persistent-caching">
        
      </a>
    </div>
    <p>When talking about caches, the first thing that comes to mind is an in-memory cache. However, this cannot work for Quicksilver for two main reasons: memory usage and the “cold cache” problem.</p><p>Indeed, with billions of stored keys, even a fraction of them would lead to an unmanageable increase in memory usage. System restarts should not affect performance, which means that cache data must be preserved somewhere anyway. So we decided to make the cache persistent and store it in the same way as full datasets: in our embedded <a href="https://rocksdb.org/"><u>RocksDB</u></a>. Thus, cached keys normally sit on disk and can be retrieved on-demand with low memory footprint.</p><p>When a key cannot be found in the proxy’s cache, we request it from a replica using our internal <i>distributed key-value protocol</i>, and put it into a local cache after processing.</p><p>Evictions are based on RocksDB <a href="https://github.com/facebook/rocksdb/wiki/Compaction-Filter"><u>compaction filters</u></a>. Compaction filters allow defining custom logic executed in background RocksDB threads responsible for compacting files on disk. Each key-value pair is processed with a filter on a regular basis, evicting least recently used data from the disk when available disk space drops below a certain threshold called a <i>soft limit</i>. To track keys accessed on disk, we have an LRU-like in-memory data structure, which is passed to the compaction filter to set last access date in key metadata and inform potential evictions.</p><p>However, with some specific workloads there is still a chance that evictions will not keep up with disk space growth, and for this scenario we have a <i>hard limit</i>: when available disk space drops below a critical threshold, we temporarily stop adding new keys to the cache. This hurts performance, but it acts as a safeguard, ensuring our proxies remain stable and don't overflow under a massive surge of requests.</p>
    <div>
      <h2>Consistency and asynchronous replication</h2>
      <a href="#consistency-and-asynchronous-replication">
        
      </a>
    </div>
    <p>Quicksilver has, from the start, provided <a href="https://jepsen.io/consistency/models/sequential"><u>sequential consistency</u></a> to clients: if key A was written before B, it’s not possible to read B and not A. We are committed to maintaining this guarantee in the new design. We have experienced <a href="https://www.hyrumslaw.com/"><u>Hyrum's Law</u></a> first hand, with Quicksilver being so widely adopted across the company that every property we introduced in earlier versions is now relied upon by other teams. This means that changing behaviour would inevitably break existing functionality and introduce bugs.</p><p>However, there is one thing standing in our way: asynchronous replication. Quicksilver replication is asynchronous mainly because machines in different parts of the world replicate at different speeds, and we don’t want a single server to slow down the entire tree. But it turns out in a proxy-replica design, independent replication progress can result in non-<a href="https://jepsen.io/consistency/models/monotonic-reads"><u>monotonic</u></a> reads!</p><p>Consider the following scenario: a client sequentially writes keys A, B, C, .. K one after another to the Quicksilver root node. These keys are asynchronously replicated through data centers across our network with varying latency. Imagine we have a proxy on index 5, which has observed keys from A to E, and two replicas: </p><ul><li><p>replica_1 is at index 2 (slightly behind the proxy), having only received A and B</p></li><li><p>replica_2 at index 9, which is slightly ahead due to a faster replication path and has received all keys from A to I
</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3miaQrw1UCUypXhra7mada/e713ebf0299e4b6f0a15bb25bbfa451a/image6.jpg" />
          </figure></li></ul><p><sup>Figure 3. Asynchronous replication in QSv1.5</sup></p><p>Now, a client performs two successive requests on a proxy, each time reading the keys E, F, G, H and I. For simplicity, we assume these keys are not cacheable (for example, due to low disk space). The proxy’s first remote request is routed to replica_2, which already has all keys and responds back with values. To prevent hot spots in a data center, we load balance requests from proxies, and the next one lands on replica_1, which hasn’t received any of the requested keys yet, and responds with a “not found” error.</p><p>So, which result is correct?</p><p>The correct behavior here is that of Quicksilver v1, which we aim to preserve. If the server on replication index 5 were a replica instead of a proxy, it would have seen updates for keys A through E inclusive, resulting in E being the only key in both replies, while all other keys cannot be found yet. Which means <i>responses from both replica_1 and replica_2 are wrong!</i></p><p>Therefore, to maintain previous guarantees and API backwards compatibility, Quicksilver v1.5 must address two crucial consistency problems: cases where the replica is ahead of the proxy, and conversely, where it lags behind. For now let’s focus on the case where a proxy lags behind a replica.</p>
    <div>
      <h2>Multiversion concurrency control</h2>
      <a href="#multiversion-concurrency-control">
        
      </a>
    </div>
    <p>In our example, replica_2 responds to a request from a proxy “from the past”. We cannot use any locks for synchronizing two servers, as it would introduce undesirable delays to the replication tree, defeating the purpose of asynchronous replication. The only option is for replicas to maintain a history of recent updates. This naturally leads us to implementing <b>multiversion concurrency control </b>(<a href="https://en.wikipedia.org/wiki/Multiversion_concurrency_control"><u>MVCC</u></a>), a popular database mechanism for tracking changes in a non-blocking fashion, where for any key we can keep multiple versions of its values for different points in time.</p><p>With MVCC, we no longer overwrite the latest value of a key in the default <a href="https://github.com/facebook/rocksdb/wiki/column-families"><u>column family</u></a> for every update. Instead, we introduced a new MVCC column family in RocksDB, where all updates are stored with a corresponding replication index. Lookup for a key at some index in the past goes as follows:</p><ol><li><p>First we search in the default column family. If a key is found and the write timestamp is not greater than the index of a requesting proxy, we can use it straight away.</p></li><li><p>Otherwise, we begin scanning the MVCC column family, where keys have unique suffixes based on latest timestamps for which they are still valid.</p></li></ol><p>In the example above, replica_2 has MVCC enabled and has keys A@1 .. K@11 in its default column family. The MVCC is initially empty, because no keys have been overwritten yet. When it receives a request for, say, key H with target index 5, it first makes a lookup in a default column family and finds the given key, but its timestamp is 8, which means this version should not be visible to the proxy yet. It then scans the MVCC, finds no matching previous versions and responds with “not found” to the proxy. Should key H be updated twice at indexes 4 and 8, we would have placed the initial version into MVCC before overwriting it in the default column family, and the proxy would receive the first version in response.</p><p>If a key E is requested at index 5, replica_2 can find it quickly in the default column family and return it back to the proxy. There is no need to read from MVCC, as the timestamp of the latest version (5) satisfies the request.</p><p>Another corner case to consider is deletions. When a key is deleted and then re-written, we need to explicitly mark the period of removal in MVCC. For that we’ve implemented <a href="https://en.wikipedia.org/wiki/Tombstone_(data_store)"><u>tombstones</u></a> – a special value format for absent keys.</p><p>Finally, we need to make sure that key history is not growing uncontrollably, using up all of the disk space available. Luckily we don’t actually need to record history for a long period of time, it just needs to cover the maximum replication index difference between any two machines. And in practice, a two-hour interval turned out to be way more than enough, while adding only about 500 MB of extra disk space usage. All records in the MVCC column family older than two hours are garbage collected, and for that again we use custom RocksDB compaction filters.</p>
    <div>
      <h2>Sliding window</h2>
      <a href="#sliding-window">
        
      </a>
    </div>
    <p>Now we know how to deal with proxies lagging behind replicas. But what about the opposite case, when a proxy is ahead of replicas?</p><p>The simplest solution is for replicas to not serve requests with a target index higher than its own. After all, it cannot know about keys from the future, whether they will be added, updated, or removed. In fact, our first implementation just returned an error when the proxy was ahead, as we expected it to happen quite infrequently. But after rolling out gradually to a few data centers, our metrics made it clear that the approach was not going to work.</p><p>This led us to analyze which keys are affected by this kind of replication asymmetry. It’s definitely not keys added or updated a long time ago, because replicas would already have the changes replicated. The only problematic keys are those updated very recently, which the proxy already knows about, but the replica does not.</p><p>With this insight, we realized that the issue should be solved on the proxies rather than on the replica side. By preserving <i>all</i> recent updates locally, the proxy can avoid querying the replica. This became known as the <b>sliding window</b> approach.</p><p>The sliding window retains all recent updates written in a short, rolling timeframe. Unlike cached keys, items in the window cannot be evicted until they move outside of the window. Internally, the sliding window is defined by lower and upper boundary pointers. These are kept in memory, and can easily be restored after a reload from the current database index and the pre-configured window size.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3kj7NdphFy8BQoJmsRH8fH/37ca06ac1c1800e4e97073815e754e51/image4.jpg" />
          </figure><p><sup>Figure 4. The sliding window shifts when replication updates arrive</sup></p><p>When a new update event arrives from the replication layer we add it to the sliding window by moving both the upper and lower boundary one position higher. Thereby, we maintain the fixed size of the window. Keys written before the lower bound can be evicted by the compaction filter, which is aware of current sliding window boundaries.</p>
    <div>
      <h2>Negative lookups</h2>
      <a href="#negative-lookups">
        
      </a>
    </div>
    <p>Another problem arising with our distributed replica-proxy design is negative lookups – requests for keys which don't exist in the database. Interestingly, in our workloads we see about ten times more negative lookups than positive ones!</p><p>But why is it a problem? Unfortunately, each negative lookup will be a cache miss on a proxy, requiring a request to a replica. Given the volume of requests and proportion of such lookups, it would be a disaster for performance, with overloaded replicas, overused data center networks, and massive latency degradation. We needed a fast and efficient approach to identifying non-existing keys directly at the proxy level.</p><p>In v1, negative lookups are the quickest type of requests. We rely on a special probabilistic data structure – <a href="https://en.wikipedia.org/wiki/Bloom_filter"><u>Bloom filters</u></a> – used in RocksDB to determine if the requested key might belong to a certain data file containing a range of sorted keys (called Sorted Sequence Table or SST) or definitely not. 99% of the time, negative lookups are served using only this in-memory data structure, avoiding the need for disk I/O.</p><p>One approach we considered for proxies was to cache negative lookups. Two problems immediately arise:</p><ul><li><p>How big is the keyspace of negative lookups? In theory, it’s infinite, but the real size was unclear. We can store it in our cache only if it is small enough.</p></li><li><p>Cached negative lookups would no longer be served by the fast Bloom filters. We have row and block caches in RocksDB, but the hit rate is nowhere near the filters for SSTs, which means negative lookups would end up going to disk more often.</p></li></ul><p>These turned out to be dealbreakers: not only was the negative keyspace vast, greatly exceeding the actual keyspace (by a thousand times for some instances!), but clients also need lookups to be <i>really</i> fast, ideally served from memory.</p><p>In pursuit of probabilistic data structures which could give us a dynamic compact representation of a full keyspace on proxies, we spent some time exploring <a href="https://en.wikipedia.org/wiki/Cuckoo_filter"><u>Cuckoo filters</u></a>. Unfortunately, with 5 billion keys it takes about 18 GB to have a false positive rate similar to Bloom filters (which only require 6 GB). And this is not only about wasted disk space — to be fast we have to keep it all in memory too!</p><p>Clearly some other solution was needed.</p><p>Finally, we decided to implement <b>key and value separation</b>, storing all keys on proxies, but persisting values only for cached keys. Evicting a key from the cache actually results in the removal of its value.</p><p>But wait, don’t the keys, even stripped of values, take a lot of space? Well, yes and no.</p><p>The total size of pure keys in Quicksilver is approximately 11 times smaller than the full dataset. Of course, it’s larger than any representation by probabilistic data structure, but there are some very desirable properties to such a solution. Firstly, we continue to enjoy fast Bloom filter lookups in RocksDB. Another benefit is that it unlocks some cool optimizations for range queries in a distributed context.</p><p>We may revisit it one day, but so far it has worked great for us.</p>
    <div>
      <h2>Discovery mechanism</h2>
      <a href="#discovery-mechanism">
        
      </a>
    </div>
    <p>Having solved all of the above challenges, one bit remained to be sorted out to make distributed query execution work: how can proxies discover replicas?</p><p>Within the local data center it is fairly easy. Each one runs its own <a href="https://www.consul.io/"><u>consul</u></a> cluster, where machines are registered as services. Consul is well integrated with our internal DNS resolvers, and with a single DNS request, we can get the names of all replicas running in a data center, which proxies can directly connect to.</p><p>However, data centers vary in size, servers are constantly added and removed, and having only local discovery would not be enough for the system to work reliably. Proxies also need to find replicas in other nearby data centers.</p><p>We had previously encountered a similar problem with our replication layer. Initially, the replication topology was statically defined in a configuration and distributed to all servers, such that they know from which sources they should replicate. While simple, this approach was quite fragile and tedious to operate. It led to a rigid replication tree with suboptimal overall performance, unable to adapt to network changes.</p><p>Our solution to this problem was the <b>Network Oracle</b> – a special overlay network based on a <a href="https://en.wikipedia.org/wiki/Gossip_protocol"><u>gossip</u></a> protocol and consisting of intermediate nodes in our data centers. Each member of this overlay constantly exchanges status and metainformation with other nodes, which helps us see active members in near-real time. Each member runs network probes measuring round-trip time to its peers, making it easy to find closest (in terms of <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/"><u>RTT</u></a>) active intermediate nodes to form a low-latency replication tree. Introducing the Network Oracle was a major improvement: we no longer needed to reconfigure the topology, watch intermediate nodes or entire data centers go down, or investigate frequent replication issues. Replication is now a completely self-organized and self-healing dynamic system.</p><p>Naturally, we decided to reuse the Network Oracle for our discovery mechanism. It consists of two subproblems: data center discovery and specific service lookup. We use the Network Oracle to find the closest data centers. Adding all machines running Quicksilver to the same overlay would be inefficient because of significant increase of network traffic and message delivery times. Instead, we use intermediate nodes as sources of <b>network proximity</b> information for the leaf nodes. Knowing which data centers are nearby, we can directly send DNS queries there to resolve specific services – Quicksilver replicas in this case.</p><p>Proxies maintain a pool of connections to active replicas and distribute requests among them to smooth out the load and avoid hotspots in a data center. Proxies also have a health-tracking mechanism, monitoring the state of connections and errors coming from replicas, and temporarily deprioritizing or isolating potentially faulty ones.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/70olkvpfiCY3aDNn1owv1j/a188c0b9253c0e23f0341463a31b5f8e/image3.png" />
          </figure><p><sup>Figure 5. Internal replica request errors</sup></p><p>To demonstrate its efficiency, we graphed errors coming from replica requests, which showed that such errors almost disappeared after introducing the new discovery system.</p>
    <div>
      <h2>Results</h2>
      <a href="#results">
        
      </a>
    </div>
    <p>Our objective with Quicksilver v1.5 was simple: gain some disk space without losing request latency, because clients rely heavily on us being fast. While the replica-proxy design delivered significant space savings, what about latencies?</p><p>Proxy</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/jyrzamGb5K05r7EOSJbrq/05a30c951ad2c9c91edd72f898bd09f1/image7.png" />
          </figure><p>Replica</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4ry4erxQ6vf7WF6wSfPpit/0de9d46bbdb4a69f2d210ec6c8baadcd/image2.png" />
          </figure><p><sup>Figure 6. Proxy-replica latency comparison</sup></p><p>Above, we have the 99.9% percentile of request latency on both a replica and proxy during a 24-hour window. One can hardly find a difference between the two. Surprisingly, proxies can even be slightly faster than replicas sometimes, likely because of smaller datasets on disk!</p><p>Quicksilver v1.5 is released but our journey to a highly scalable and efficient solution is not over. In the next post we’ll share what challenges we faced with the following iteration. Stay tuned!</p>
    <div>
      <h2>Thank you</h2>
      <a href="#thank-you">
        
      </a>
    </div>
    <p>This project was a big team effort, so we’d like to thank everyone on the Quicksilver team – it would not have come true without you all.</p><ul><li><p>Aleksandr Matveev</p></li><li><p>Aleksei Surikov</p></li><li><p>Alex Dzyoba</p></li><li><p>Alexandra (Modi) Stana-Palade</p></li><li><p>Francois Stiennon</p></li><li><p>Geoffrey Plouviez</p></li><li><p>Ilya Polyakovskiy</p></li><li><p>Manzur Mukhitdinov</p></li><li><p>Volodymyr Dorokhov</p></li></ul><p></p> ]]></content:encoded>
            <category><![CDATA[Quicksilver]]></category>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[RocksDB]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Replication]]></category>
            <category><![CDATA[Distributed]]></category>
            <guid isPermaLink="false">4gVdi0wvV700DVpfnnBo7j</guid>
            <dc:creator>Anton Dort-Golts</dc:creator>
            <dc:creator>Marten van de Sanden</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[Building Vectorize, a distributed vector database, on Cloudflare’s Developer Platform]]></title>
            <link>https://blog.cloudflare.com/building-vectorize-a-distributed-vector-database-on-cloudflare-developer-platform/</link>
            <pubDate>Tue, 22 Oct 2024 13:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare's Vectorize is now generally available, offering faster responses, lower pricing, a free tier, and supporting up to 5 million vectors. ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://developers.cloudflare.com/vectorize/"><u>Vectorize</u></a> is a globally distributed vector database that enables you to build full-stack, AI-powered applications with Cloudflare Workers. Vectorize makes querying embeddings — representations of values or objects like text, images, audio that are designed to be consumed by machine learning models and semantic search algorithms — faster, easier and more affordable.</p><p>In this post, we dive deep into how we built Vectorize on <a href="https://developers.cloudflare.com/"><u>Cloudflare’s Developer Platform</u></a>, leveraging Cloudflare’s <a href="https://www.cloudflare.com/network/"><u>global network</u></a>, <a href="https://developers.cloudflare.com/cache"><u>Cache</u></a>, <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a>, <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>, <a href="https://developers.cloudflare.com/queues/"><u>Queues</u></a>, <a href="https://developers.cloudflare.com/durable-objects"><u>Durable Objects</u></a>, and <a href="https://blog.cloudflare.com/container-platform-preview/"><u>container platform</u></a>.</p>
    <div>
      <h2>What is a vector database?</h2>
      <a href="#what-is-a-vector-database">
        
      </a>
    </div>
    <p>A <a href="https://www.cloudflare.com/learning/ai/what-is-vector-database/"><u>vector database</u></a> is a queryable store of vectors. A vector is a large array of numbers called vector dimensions.</p><p>A vector database has a <a href="https://en.wikipedia.org/wiki/Similarity_search"><u>similarity search</u></a> query: given an input vector, it returns the vectors that are closest according to a specified metric, potentially filtered on their metadata.</p><p><a href="https://blog.cloudflare.com/vectorize-vector-database-open-beta/#why-do-i-need-a-vector-database"><u>Vector databases are used</u></a> to power semantic search, document classification, and recommendation and anomaly detection, as well as contextualizing answers generated by LLMs (<a href="https://www.cloudflare.com/learning/ai/retrieval-augmented-generation-rag/"><u>Retrieval Augmented Generation, RAG</u></a>).</p>
    <div>
      <h3>Why do vectors require special database support?</h3>
      <a href="#why-do-vectors-require-special-database-support">
        
      </a>
    </div>
    <p>Conventional data structures like <a href="https://en.wikipedia.org/wiki/B-tree"><u>B-trees</u></a>, or <a href="https://en.wikipedia.org/wiki/Binary_search_tree"><u>binary search trees</u></a> expect the data they index to be cheap to compare and to follow a one-dimensional linear ordering. They leverage this property of the data to organize it in a way that makes search efficient. Strings, numbers, and booleans are examples of data featuring this property.</p><p>Because vectors are high-dimensional, ordering them in a one-dimensional linear fashion is ineffective for similarity search, as the resulting ordering doesn’t capture the proximity of vectors in the high-dimensional space. This phenomenon is often referred to as the <a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality"><u>curse of dimensionality</u></a>.</p><p>In addition to this, comparing two vectors using distance metrics useful for similarity search is a computationally expensive operation, requiring vector-specific techniques for databases to overcome.</p>
    <div>
      <h2>Query processing architecture</h2>
      <a href="#query-processing-architecture">
        
      </a>
    </div>
    <p>Vectorize builds upon Cloudflare’s global network to bring fast vector search close to its users, and relies on many components to do so.</p><p>These are the Vectorize components involved in processing vector queries.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3iEISPtYCjwmggsjjQ4i14/dddac58c03a875ca258456b25f75df38/blog-2590-vectorize-01-query-read.png" />
          </figure><p>Vectorize runs in every <a href="https://www.cloudflare.com/network/"><u>Cloudflare data center</u></a>, on the infrastructure powering Cloudflare Workers. It serves traffic coming from <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/"><u>Worker bindings</u></a> as well as from the <a href="https://developers.cloudflare.com/api/operations/vectorize-list-vectorize-indexes"><u>Cloudflare REST API</u></a> through our API Gateway.</p><p>Each query is processed on a server in the data center in which it enters, picked in a fashion that spreads the load across all servers of that data center.</p><p>The Vectorize DB Service (a Rust binary) running on that server processes the query by reading the data for that index on <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>R2</u></a>, Cloudflare’s object storage. It does so by reading through <a href="https://developers.cloudflare.com/cache"><u>Cloudflare’s Cache</u></a> to speed up I/O operations.</p>
    <div>
      <h2>Searching vectors, and indexing them to speed things up</h2>
      <a href="#searching-vectors-and-indexing-them-to-speed-things-up">
        
      </a>
    </div>
    <p>Being a vector database, Vectorize features a similarity search query: given an input vector, it returns the K vectors that are closest according to a specified metric.</p><p>Conceptually, this similarity search consists of 3 steps:</p><ol><li><p>Evaluate the proximity of the query vector with every vector present in the index.</p></li><li><p>Sort the vectors based on their proximity “score”.</p></li><li><p>Return the top matches.</p></li></ol><p>While this method is accurate and effective, it is computationally expensive and does not scale well to indexes containing millions of vectors (see <b>Why do vectors require special database support?</b> above).</p><p>To do better, we need to prune the search space, that is, avoid scanning the entire index for every query.</p><p>For this to work, we need to find a way to discard vectors we know are irrelevant for a query, while focusing our efforts on those that might be relevant.</p>
    <div>
      <h3>Indexing vectors with IVF</h3>
      <a href="#indexing-vectors-with-ivf">
        
      </a>
    </div>
    <p>Vectorize prunes the search space for a query using an indexing technique called <a href="https://blog.dailydoseofds.com/p/approximate-nearest-neighbor-search"><u>IVF, Inverted File Index</u></a>.</p><p>IVF clusters the index vectors according to their relative proximity. For each cluster, it then identifies its centroid, the center of gravity of that cluster, a high-dimensional point minimizing the distance with every vector in the cluster.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1q48ixmjKRsTkBdTR6SbSR/1c0ba3c188a78150e74ef3baf849ae7e/blog-2590-vectorize-02-ivf-index.png" />
          </figure><p>Once the list of centroids is determined, each centroid is given a number. We then structure the data on storage by placing each vector in a file named like the centroid it is closest to.</p><p>When processing a query, we then can then focus on relevant vectors by looking only in the centroid files closest to that query vector, effectively pruning the search space.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3BjkRvCyT4fEgVqIHGkwQg/d692e167dc34fe5b09e94a5ebb29fe28/image8.png" />
          </figure>
    <div>
      <h3>Compressing vectors with PQ</h3>
      <a href="#compressing-vectors-with-pq">
        
      </a>
    </div>
    <p>Vectorize supports vectors of up to 1536 dimensions. At 4 bytes per dimension (32 bits float), this means up to 6 KB per vector. That’s 6 GB of uncompressed vector data per million vectors that we need to fetch from storage and put in memory.</p><p>To process multi-million vector indexes while limiting the CPU, memory, and I/O required to do so, Vectorize uses a <a href="https://en.wikipedia.org/wiki/Dimensionality_reduction"><u>dimensionality reduction</u></a> technique called <a href="https://en.wikipedia.org/wiki/Vector_quantization"><u>PQ (Product Quantization)</u></a>. PQ compresses the vectors data in a way that retains most of their specificity while greatly reducing their size — a bit like down sampling a picture to reduce the file size, while still being able to tell precisely what’s in the picture — enabling Vectorize to efficiently perform similarity search on these lighter vectors.</p><p>In addition to storing the compressed vectors, their original data is retained on storage as well, and can be requested through the API; the compressed vector data is used only to speed up the search.</p>
    <div>
      <h3>Approximate nearest neighbor search and result accuracy refining</h3>
      <a href="#approximate-nearest-neighbor-search-and-result-accuracy-refining">
        
      </a>
    </div>
    <p>By pruning the search space and compressing the vector data, we’ve managed to increase the efficiency of our query operation, but it is now possible to produce a set of matches that is different from the set of true closest matches. We have traded result accuracy for speed by performing an <a href="https://en.wikipedia.org/wiki/Nearest_neighbor_search#Approximation_methods"><u>approximate nearest neighbor search</u></a>, reaching an accuracy of ~80%.</p><p>To boost the <a href="https://blog.cloudflare.com/workers-ai-bigger-better-faster/#how-fast-is-vectorize"><u>result accuracy up to over 95%</u></a>, Vectorize then <a href="https://developers.cloudflare.com/vectorize/best-practices/query-vectors/#control-over-scoring-precision-and-query-accuracy"><u>performs a result refinement pass</u></a> on the top approximate matches using uncompressed vector data, and returns the best refined matches.</p>
    <div>
      <h2>Eventual consistency and snapshot versioning</h2>
      <a href="#eventual-consistency-and-snapshot-versioning">
        
      </a>
    </div>
    <p>Whenever you query your Vectorize index, you are guaranteed to receive results which are read from a consistent, immutable snapshot — even as you write to your index concurrently. Writes are applied in strict order of their arrival in our system, and they are funneled into an asynchronous process. We update the index files by reading the old version, making changes, and writing this updated version as a new object in R2. Each index file has its own version number, and can be updated independently of the others. Between two versions of the index we may update hundreds or even thousands of IVF and metadata index files, but even as we update the files, your queries will consistently use the current version until it is time to switch.</p><p>Each IVF and metadata index file has its own version. The list of all versioned files which make up the snapshotted version of the index is contained within a <i>manifest file</i>. Each version of the index has its own manifest. When we write a new manifest file based on the previous version, we only need to update references to the index files which were modified; if there are files which weren't modified, we simply keep the references to the previous version.</p><p>We use a <i>root manifest</i> as the authority of the current version of the index. This is the pivot point for changes. The root manifest is a copy of a manifest file from a particular version, which is written to a deterministic location (the root of the R2 bucket for the index). When our async write process has finished processing vectors, and has written all new index files to R2, we <i>commit</i> by overwriting the current root manifest with a copy of the new manifest. PUT operations in R2 are atomic, so this effectively makes our updates atomic. Once the manifest is updated, Vectorize DB Service instances running on our network will pick it up, and use it to serve reads.</p><p>Because we keep past versions of index and manifest files, we effectively maintain versioned snapshots of your index. This means we have a straightforward path towards building a point-in-time recovery feature (similar to <a href="https://developers.cloudflare.com/d1/reference/time-travel/"><u>D1's Time Travel feature</u></a>).</p><p>You may have noticed that because our write process is asynchronous, this means Vectorize is <i>eventually consistent</i> — that is, there is a delay between the successful completion of a request writing on the index, and finally seeing those updates reflected in queries.  This isn't always ideal for all data storage use cases. For example, imagine two users using an online ticket reservation application for airline tickets, where both users buy the same seat — one user will successfully reserve the ticket, and the other will eventually get an error saying the seat was taken, and they need to choose again. Because a vector index is not typically used as a primary database for these transactional use cases, we decided eventual consistency was a worthy trade off in order to ensure Vectorize queries would be fast, high-throughput, and cheap even as the size of indexes grew into the millions.</p>
    <div>
      <h2>Coordinating distributed writes: it's just another block in the WAL</h2>
      <a href="#coordinating-distributed-writes-its-just-another-block-in-the-wal">
        
      </a>
    </div>
    <p>In the section above, we touched on our eventually consistent, asynchronous write process. Now we'll dive deeper into our implementation. </p>
    <div>
      <h3>The WAL</h3>
      <a href="#the-wal">
        
      </a>
    </div>
    <p>A <a href="https://en.wikipedia.org/wiki/Write-ahead_logging"><u>write ahead log</u></a> (WAL) is a common technique for making atomic and durable writes in a database system. Vectorize’s WAL is implemented with <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/"><u>SQLite in Durable Objects</u></a>.</p><p>In Vectorize, the payload for each update is given an ID, written to R2, and the ID for that payload is handed to the WAL Durable Object which persists it as a "block." Because it's just a pointer to the data, the blocks are lightweight records of each mutation.</p><p>Durable Objects (DO) have many benefits — strong transactional guarantees, a novel combination of compute and storage, and a high degree of horizontal scale — but individual DOs are small allotments of memory and compute. However, the process of updating the index for even a single mutation is resource intensive — a single write may include thousands of vectors, which may mean reading and writing thousands of data files stored in R2, and storing a lot of data in memory. This is more than what a single DO can handle.</p><p>So we designed the WAL to leverage DO's strengths and made it a coordinator. It controls the steps of updating the index by delegating the heavy lifting to beefier instances of compute resources (which we call "Executors"), but uses its transactional properties to ensure the steps are done with strong consistency. It safeguards the process from rogue or stalled executors, and ensures the WAL processing continues to move forward. DOs are easy to scale, so we create a new DO instance for each Vectorize index.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7l5hw2yVkyGKpU8xTMyX8f/743941bd273731bdb3dbf8110afc2526/unnamed.png" />
          </figure>
    <div>
      <h3>WAL Executor</h3>
      <a href="#wal-executor">
        
      </a>
    </div>
    <p>The executors run from a single pool of compute resources, shared by all WALs. We use a simple producer-consumer pattern using <a href="https://developers.cloudflare.com/queues/"><u>Cloudflare Queues</u></a>. The WAL enqueues a request, and executors poll the queue. When they get a request, they call an API on the WAL requesting to be <i>assigned </i>to the request.</p><p>The WAL ensures that one and only one executor is ever assigned to that write. As the executor writes, the index files and the updated manifest are written in R2, but they are not yet visible. The final step is for the executor to call another API on the WAL to <i>commit</i> the change — and this is key — it passes along the updated manifest. The WAL is responsible for overwriting the root manifest with the updated manifest. The root manifest is the pivot point for atomic updates: once written, the change is made visible to Vectorize’s database service, and the updated data will appear in queries.</p><p>From the start, we designed this process to account for non-deterministic errors. We focused on enumerating failure modes first, and only moving forward with possible design options after asserting they handled the possibilities for failure. For example, if an executor stalls, the WAL finds a new executor. If the first executor comes back, the coordinator will reject its attempt to commit the update. Even if that first executor is working on an old version which has already been written, and writes new index files and a new manifest to R2, they will not overwrite the files written from the committed version.</p>
    <div>
      <h3>Batching updates</h3>
      <a href="#batching-updates">
        
      </a>
    </div>
    <p>Now that we have discussed the general flow, we can circle back to one of our favorite features of the WAL. On the executor, the most time-intensive part of the write process is reading and writing many files from R2. Even with making our reads and writes concurrent to maximize throughput, the cost of updating even thousands of vectors within a single file is dwarfed by the total latency of the network I/O. Therefore it is more efficient to maximize the number of vectors processed in a single execution.</p><p>So that is what we do: we batch discrete updates. When the WAL is ready to request work from an executor, it will get a chunk of "blocks" off the WAL, starting with the next un-written block, and maintaining the sequence of blocks. It will write a new "batch" record into the SQLite table, which ties together that sequence of blocks, the version of the index, and the ID of the executor assigned to the batch.</p><p>Users can batch multiple vectors to update in a single insert or upsert call. Because the size of each update can vary, the WAL adaptively calculates the optimal size of its batch to increase throughput. The WAL will fit as many upserted vectors as possible into a single batch by counting the number of updates represented by each block. It will batch up to 200,000 vectors at once (a value we arrived at after our own testing) with a limit of 1,000 blocks. With this throughput, we have been able to quickly load millions of vectors into an index (with upserts of 5,000 vectors at a time). Also, the WAL does not pause itself to collect more writes to batch — instead, it begins processing a write as soon as it arrives. Because the WAL only processes one batch at a time, this creates a natural pause in its workflow to batch up writes which arrive in the meantime.</p>
    <div>
      <h3>Retraining the index</h3>
      <a href="#retraining-the-index">
        
      </a>
    </div>
    <p>The WAL also coordinates our process for retraining the index. We occasionally re-train indexes to ensure the mapping of IVF centroids best reflects the current vectors in the index. This maintains the high accuracy of the vector search.</p><p>Retraining produces a completely new index. All index files are updated; vectors have been reshuffled across the index space. For this reason, all indexes have a second version stamp — which we call the <i>generation</i> — so that we can differentiate between retrained indexes. </p><p>The WAL tracks the state of the index, and controls when the training is started. We have a second pool of processes called "trainers." The WAL enqueues a request on a queue, then a trainer picks up the request and it begins training.</p><p>Training can take a few minutes to complete, but we do not pause writes on the current generation. The WAL will continue to handle writes as normal. But the training runs from a fixed snapshot of the index, and will become out-of-date as the live index gets updated in parallel. Once the trainer has completed, it signals the WAL, which will then start a multi-step process to switch to the new generation. It enters a mode where it will continue to record writes in the WAL, but will stop making those writes visible on the current index. Then it will begin catching up the retrained index with all of the updates that came in since it started. Once it has caught up to all data present in the index when the trainer signaled the WAL, it will switch over to the newly retrained index. This prevents the new index from appearing to "jump back in time." All subsequent writes will be applied to that new index.</p><p>This is all modeled seamlessly with the batch record. Because it associates the index version with a range of WAL blocks, multiple batches can span the same sequence of blocks as long as they belong to different generations. We can say this another way: a single WAL block can be associated with many batches, as long as these batches are in different generations. Conceptually, the batches act as a second WAL layered over the WAL blocks.</p>
    <div>
      <h2>Indexing and filtering metadata</h2>
      <a href="#indexing-and-filtering-metadata">
        
      </a>
    </div>
    <p>Vectorize supports metadata filters on vector similarity queries. This allows a query to focus the vector similarity search on a subset of the index data, yielding matches that would otherwise not have been part of the top results.</p><p>For instance, this enables us to query for the best matching vectors for <code>color: “blue” </code>and <code>category: ”robe”</code>.</p><p>Conceptually, what needs to happen to process this example query is:</p><ul><li><p>Identify the set of vectors matching <code>color: “blue”</code> by scanning all metadata.</p></li><li><p>Identify the set of vectors matching <code>category: “robe”</code> by scanning all metadata.</p></li><li><p>Intersect both sets (boolean AND in the filter) to identify vectors matching both the color and category filter.</p></li><li><p>Score all vectors in the intersected set, and return the top matches.</p></li></ul><p>While this method works, it doesn’t scale well. For an index with millions of vectors, processing the query that way would be very resource intensive. What’s worse, it prevents us from using our IVF index to identify relevant vector data, forcing us to compute a proximity score on potentially millions of vectors if the filtered set of vectors is large.</p><p>To do better, we need to prune the metadata search space by indexing it like we did for the vector data, and find a way to efficiently join the vector sets produced by the metadata index with our IVF vector index.</p>
    <div>
      <h3>Indexing metadata with Chunked Sorted List Indexes</h3>
      <a href="#indexing-metadata-with-chunked-sorted-list-indexes">
        
      </a>
    </div>
    <p>Vectorize maintains one metadata index per filterable property. Each filterable metadata property is indexed using a Chunked Sorted List Index.</p><p>A Chunked Sorted List Index is a sorted list of all distinct values present in the data for a filterable property, with each value mapped to the set of vector IDs having that value. This enables Vectorize to <a href="https://en.wikipedia.org/wiki/Binary_search"><u>binary search</u></a> a value in the metadata index in <a href="https://www.geeksforgeeks.org/what-is-logarithmic-time-complexity/"><u>O(log n)</u></a> complexity, in other words about as fast as search can be on a large dataset.</p><p>Because it can become very large on big indexes, the sorted list is chunked in pieces matching a target weight in KB to keep index state fetches efficient.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7wRfqvPKtRx9RX5clyapxt/9630152212779ac008efc9685538f48e/blog-2590-vectorize-03-chunked-sorted-list.png" />
          </figure><p>A lightweight chunk descriptor list is maintained in the index manifest, keeping track of the list chunks and their lower/upper values. This chunk descriptor list can be binary searched to identify which chunk would contain the searched metadata value.</p><p>Once the candidate chunk is identified, Vectorize fetches that chunk from index data and binary searches it to take the set of vector IDs matching a metadata value if found, or an empty set if not found.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6leIbnEGLN2qRitRXxoqog/01d5cfaad6e9e537b27bbc15933c1db0/blog-2590-vectorize-04-chunked-sorted-list-2.png" />
          </figure><p>We identify the matching vector set this way for every predicate in the metadata filter of the query, then intersect the sets in memory to determine the final set of vectors matched by the filters.</p><p>This is just half of the query being processed. We now need to identify the vectors most similar to the query vector, within those matching the metadata filters.</p>
    <div>
      <h3>Joining the metadata and vector indexes</h3>
      <a href="#joining-the-metadata-and-vector-indexes">
        
      </a>
    </div>
    <p>A vector similarity query always comes with an input vector. We can rank all centroids of our IVF vector index based on their proximity with that query vector.</p><p>The vector set matched by the metadata filters contains for each vector its ID and IVF centroid number.</p><p>From this, Vectorize derives the number of vectors matching the query filters per IVF centroid, and determines which and how many top-ranked IVF centroids need to be scanned according to the number of matches the query asks for.</p><p>Vectorize then performs the IVF-indexed vector search (see the section <b>Searching Vectors, and indexing them to speed things up</b> above<b>)</b> by considering only the vectors in the filtered metadata vector set while doing so.</p><p>Because we’re effectively pruning the vector search space using metadata filters, filtered queries can often be faster than their unfiltered equivalent.</p>
    <div>
      <h2>Query performance</h2>
      <a href="#query-performance">
        
      </a>
    </div>
    <p>The performance of a system is measured in terms of latency and throughput.</p><p>Latency is a measure relative to individual queries, evaluating the time it takes for a query to be processed, usually expressed in milliseconds. It is what an end user perceives as the “speed” of the service, so a lower latency is desirable.</p><p>Throughput is a measure relative to an index, evaluating the number of queries it can process concurrently over a period of time, usually expressed in requests per second or RPS. It is what enables an application to scale to thousands of simultaneous users, so a higher throughput is desirable.</p><p>Vectorize is designed for great index throughput and optimized for low query latency to deliver great performance for demanding applications. <a href="https://blog.cloudflare.com/workers-ai-bigger-better-faster/#how-fast-is-vectorize"><u>Check out our benchmarks</u></a>.</p>
    <div>
      <h3>Query latency optimization</h3>
      <a href="#query-latency-optimization">
        
      </a>
    </div>
    <p>As a distributed database keeping its data state on blob storage, Vectorize’s latency is primarily driven by the fetch of index data, and relies heavily on <a href="https://developers.cloudflare.com/cache"><u>Cloudflare’s network of caches</u></a> as well as individual server RAM cache to keep latency low.</p><p>Because Vectorize data is snapshot versioned, (see <b>Eventual consistency and snapshot versioning</b> above), each version of the index data is immutable and thus highly cacheable, increasing the latency benefits Vectorize gets from relying on Cloudflare’s cache infrastructure.</p><p>To keep the index data lean, Vectorize uses techniques to reduce its weight. In addition to Product Quantization (see <b>Compressing vectors with PQ</b> above), index files use a space-efficient binary format optimized for runtime performance that Vectorize is able to use without parsing, once fetched.</p><p>Index data is fragmented in a way that minimizes the amount of data required to process a query. Auxiliary indexes into that data are maintained to limit the amount of fragments to fetch, reducing overfetch by jumping straight to the relevant piece of data on mass storage.</p><p>Vectorize boosts all vector proximity computations by leveraging <a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data"><u>SIMD CPU instructions</u></a>, and by organizing the vector search in 2 passes, effectively balancing the latency/result accuracy ratio (see <b>Approximate nearest neighbor search and result accuracy refining</b> above).</p><p>When used via a Worker binding, each query is processed close to the server serving the worker request, and thus close to the end user, minimizing the network-induced latency between the end user, the Worker application, and Vectorize.</p>
    <div>
      <h3>Query throughput</h3>
      <a href="#query-throughput">
        
      </a>
    </div>
    <p>Vectorize runs in every Cloudflare data center, on thousands of servers across the world.</p><p>Thanks to the snapshot versioning of every index’s data, every server is simultaneously able to serve the index concurrently, without contention on state.</p><p>This means that a Vectorize index elastically scales horizontally with its distributed traffic, providing very high throughput for the most demanding Worker applications.</p>
    <div>
      <h2>Increased index size</h2>
      <a href="#increased-index-size">
        
      </a>
    </div>
    <p>We are excited that our upgraded version of Vectorize can support a maximum of 5 million vectors, which is a 25x improvement over the limit in beta (200,000 vectors). All the improvements we discussed in this blog post contribute to this increase in vector storage. <a href="https://blog.cloudflare.com/workers-ai-bigger-better-faster/#how-fast-is-vectorize"><u>Improved query performance</u></a> and throughput comes with this increase in storage as well.</p><p>However, 5 million may be constraining for some use cases. We have already heard this feedback. The limit falls out of the constraints of building a brand new globally distributed stateful service, and our desire to iterate fast and make Vectorize generally available so builders can confidently leverage it in their production apps.</p><p>We believe builders will be able to leverage Vectorize as their primary vector store, either with a single index or by sharding across multiple indexes. But if this limit is too constraining for you, please let us know. Tell us your use case, and let's see if we can work together to make Vectorize work for you.</p>
    <div>
      <h2>Try it now!</h2>
      <a href="#try-it-now">
        
      </a>
    </div>
    <p>Every developer on a free plan can give Vectorize a try. You can <a href="https://developers.cloudflare.com/vectorize/"><u>visit our developer documentation to get started</u></a>.</p><p>If you’re looking for inspiration on what to build, <a href="http://developers.cloudflare.com/vectorize/get-started/embeddings/"><u>see the semantic search tutorial</u></a> that combines <a href="https://developers.cloudflare.com/workers-ai/"><u>Workers AI</u></a> and Vectorize for document search, running entirely on Cloudflare. Or an example of <a href="https://developers.cloudflare.com/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/"><u>how to combine OpenAI and Vectorize</u></a> to give an LLM more context and dramatically improve the accuracy of its answers.</p><p>And if you have questions about how to use Vectorize for our product &amp; engineering teams, or just want to bounce an idea off of other developers building on Workers AI, join the #vectorize and #workers-ai channels on our <a href="https://discord.cloudflare.com/"><u>Developer Discord</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Engineering]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Edge Database]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[Storage]]></category>
            <guid isPermaLink="false">2UYBSJmJKD66lXTstsRhTg</guid>
            <dc:creator>Jérôme Schneider</dc:creator>
            <dc:creator>Alex Graham</dc:creator>
        </item>
        <item>
            <title><![CDATA[Sippy helps you avoid egress fees while incrementally migrating data from S3 to R2]]></title>
            <link>https://blog.cloudflare.com/sippy-incremental-migration-s3-r2/</link>
            <pubDate>Tue, 26 Sep 2023 13:00:44 GMT</pubDate>
            <description><![CDATA[ Use Sippy to incrementally migrate data from S3 to R2 as it’s requested and avoid migration-specific egress fees ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4whKfl7szHhdKNWCiCETR3/d65f22ad8c25598a41426bd1de59188e/image1-16.png" />
            
            </figure><p>Earlier in 2023, we announced <a href="/r2-super-slurper-ga/">Super Slurper</a>, a data migration tool that makes it easy to copy large amounts of data to <a href="https://developers.cloudflare.com/r2/">R2</a> from other <a href="https://www.cloudflare.com/developer-platform/products/r2/">cloud object storage</a> providers. Since the announcement, developers have used Super Slurper to run thousands of successful migrations to R2!</p><p>While Super Slurper is perfect for cases where you want to move all of your data to R2 at once, there are scenarios where you may want to migrate your data incrementally over time. Maybe you want to avoid the one time upfront <a href="https://www.cloudflare.com/learning/cloud/what-is-aws-data-transfer-pricing/">AWS data transfer bill</a>? Or perhaps you have legacy data that may never be accessed, and you only want to migrate what’s required?</p><p>Today, we’re announcing the open beta of <a href="https://developers.cloudflare.com/r2/data-migration/sippy/">Sippy</a>, an incremental migration service that copies data from S3 (other cloud providers coming soon!) to R2 as it’s requested, without paying unnecessary cloud egress fees typically associated with moving large amounts of data. On top of addressing <a href="https://www.cloudflare.com/learning/cloud/what-is-vendor-lock-in/">vendor lock-in</a>, Sippy makes stressful, time-consuming migrations a thing of the past. All you need to do is replace the S3 endpoint in your application or attach your domain to your new R2 bucket and data will start getting copied over.</p>
    <div>
      <h2>How does it work?</h2>
      <a href="#how-does-it-work">
        
      </a>
    </div>
    <p>Sippy is an incremental migration service built directly into your R2 bucket. Migration-specific <a href="https://www.cloudflare.com/learning/cloud/what-are-data-egress-fees/">egress fees</a> are reduced by leveraging requests within the flow of your application where you’d already be paying egress fees to simultaneously copy objects to R2. Here is how it works:</p><p>When an object is requested from <a href="https://developers.cloudflare.com/r2/api/workers/">Workers</a>, <a href="https://developers.cloudflare.com/r2/api/s3/">S3 API</a>, or <a href="https://developers.cloudflare.com/r2/buckets/public-buckets/">public bucket</a>, it is served from your R2 bucket if it is found.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2QSl0q22LEdoOUp6BzkWhE/12f69030f3c5a4d53edf0a7d62af8b43/image2-16.png" />
            
            </figure><p>If the object is not found in R2, it will simultaneously be returned from your S3 bucket and copied to R2.</p><p>Note: Some large objects may take multiple requests to copy.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7ok5dS498ocJgCBRR1V5HU/683129c203b3343af7a4aa1c29e15b8a/image3-22.png" />
            
            </figure><p>That means after objects are copied, subsequent requests will be served from R2, and you’ll begin saving on egress fees immediately.</p>
    <div>
      <h2>Start incrementally migrating data from S3 to R2</h2>
      <a href="#start-incrementally-migrating-data-from-s3-to-r2">
        
      </a>
    </div>
    
    <div>
      <h3>Create an R2 bucket</h3>
      <a href="#create-an-r2-bucket">
        
      </a>
    </div>
    <p>To get started with incremental migration, you’ll first need to create an R2 bucket if you don’t already have one. To create a new R2 bucket from the Cloudflare dashboard:</p><ol><li><p>Log in to the <a href="https://dash.cloudflare.com/">Cloudflare dashboard</a> and select <b>R2</b>.</p></li><li><p>Select <b>Create bucket</b>.</p></li><li><p>Give your bucket a name and select <b>Create bucket</b>.</p></li></ol><p>​​To learn more about other ways to create R2 buckets refer to the documentation on <a href="https://developers.cloudflare.com/r2/buckets/create-buckets/">creating buckets</a>.</p>
    <div>
      <h3>Enable Sippy on your R2 bucket</h3>
      <a href="#enable-sippy-on-your-r2-bucket">
        
      </a>
    </div>
    <p>Next, you’ll enable Sippy for the R2 bucket you created. During the beta, you can do this by using the API. Here’s an example of how to enable Sippy for an R2 bucket with cURL:</p>
            <pre><code>curl -X PUT https://api.cloudflare.com/client/v4/accounts/{account_id}/r2/buckets/{bucket_name}/sippy \
--header "Authorization: Bearer &lt;API_TOKEN&gt;" \
--data '{"provider": "AWS", "bucket": "&lt;AWS_BUCKET_NAME&gt;", "zone": "&lt;AWS_REGION&gt;","key_id": "&lt;AWS_ACCESS_KEY_ID&gt;", "access_key":"&lt;AWS_SECRET_ACCESS_KEY&gt;", "r2_key_id": "&lt;R2_ACCESS_KEY_ID&gt;", "r2_access_key": "&lt;R2_SECRET_ACCESS_KEY&gt;"}'</code></pre>
            <p>For more information on getting started, please refer to the <a href="https://developers.cloudflare.com/r2/data-migration/sippy/">documentation</a>. Once enabled, requests to your bucket will now start copying data over from S3 if it’s not already present in your R2 bucket.</p>
    <div>
      <h3>Finish your migration with Super Slurper</h3>
      <a href="#finish-your-migration-with-super-slurper">
        
      </a>
    </div>
    <p>You can run your incremental migration for as long as you want, but eventually you may want to complete the migration to R2. To do this, you can pair Sippy with <a href="https://developers.cloudflare.com/r2/data-migration/super-slurper/">Super Slurper</a> to easily migrate your remaining data that hasn’t been accessed to R2.</p>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We’re excited about open beta, but it’s only the starting point. Next, we plan on making incremental migration configurable from the Cloudflare dashboard, complete with analytics that show you the progress of your migration and how much you are saving by not paying egress fees for objects that have been copied over so far.</p><p>If you are looking to start incrementally migrating your data to R2 and have any questions or feedback on what we should build next, we encourage you to join our <a href="https://discord.com/invite/cloudflaredev">Discord community</a> to share!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5OxNi1UZsYR9ITa7KbaQ11/753b4c01ddbed65d637c55b0b91a47de/Kfb7dwYUUzfrLKPH_ukrJRTvRlfl4E8Uy00vwEQPCTiW0IQ--fxpikjv1p0afm4A5J3JfVjQiOVjN3RMNeMcu3vhnz97pEmENCkNIuwdW_m-aW7ABfZnmUpJB_jh.png" />
            
            </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Connectivity Cloud]]></category>
            <category><![CDATA[R2]]></category>
            <guid isPermaLink="false">4Oc5iRahgLMh31qeAxVZPt</guid>
            <dc:creator>Phillip Jones</dc:creator>
            <dc:creator>Vlad Krasnov</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing Object Lifecycle Management for Cloudflare R2]]></title>
            <link>https://blog.cloudflare.com/introducing-object-lifecycle-management-for-cloudflare-r2/</link>
            <pubDate>Wed, 10 May 2023 13:00:39 GMT</pubDate>
            <description><![CDATA[ We’re excited to announce that Object Lifecycle Management for R2 is generally available, allowing you to effectively manage object expiration, all from the R2 dashboard or via our API ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2gYUTS0mGft4UNdyl6azxW/13b7fd11df446836b17c2060d637f36f/R21.png" />
            
            </figure><p>Last year, <a href="/r2-ga/">R2 made its debut</a>, providing developers with <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a> while eliminating the burden of <a href="https://www.cloudflare.com/learning/cloud/what-are-data-egress-fees/">egress fees</a>. (For many, egress costs account for <a href="https://twitter.com/CloudflareDev/status/1639281667973033988?s=20">over half of their object storage bills</a>!) Since R2’s launch, tens of thousands of developers have chosen it to store data for many different types of applications.</p><p>But for some applications, data stored in <a href="https://www.cloudflare.com/developer-platform/products/r2/">R2</a> doesn’t need to be retained forever. Over time, as this data grows, it can <a href="https://r2-calculator.cloudflare.com/">unnecessarily lead to higher storage costs</a>. Today, we’re excited to announce that Object Lifecycle Management for R2 is generally available, allowing you to effectively manage object expiration, all from the R2 dashboard or via our API.</p>
    <div>
      <h3>Object Lifecycle Management</h3>
      <a href="#object-lifecycle-management">
        
      </a>
    </div>
    <p>Object lifecycles give you the ability to define rules (up to 1,000) that determine how long objects uploaded to your bucket are kept. For example, by implementing an object lifecycle rule that deletes objects after 30 days, you could automatically delete outdated logs or temporary files. You can also define rules to abort unfinished multipart uploads that are sitting around and contributing to storage costs.</p>
    <div>
      <h3>Getting started with object lifecycles in R2</h3>
      <a href="#getting-started-with-object-lifecycles-in-r2">
        
      </a>
    </div>
    
    <div>
      <h4>Cloudflare dashboard</h4>
      <a href="#cloudflare-dashboard">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2NokpyjDvm1FLghbgfaXLs/48ce0dd4d55e12e942c1d9c3f7603f3a/R22.png" />
            
            </figure><ol><li><p>From the Cloudflare dashboard, select <b>R2</b>.</p></li><li><p>Select your R2 bucket.</p></li><li><p>Navigate to the <b>Settings</b> tab and find the <b>Object lifecycle rules</b> section.</p></li><li><p>Select <b>Add rule</b> to define the name, relevant prefix, and lifecycle actions: delete uploaded objects or abort incomplete multipart uploads.</p></li><li><p>Select <b>Add rule</b> to complete the process.</p></li></ol>
    <div>
      <h4>S3 Compatible API</h4>
      <a href="#s3-compatible-api">
        
      </a>
    </div>
    <p>With R2’s <a href="https://www.cloudflare.com/developer-platform/solutions/s3-compatible-object-storage/">S3-compatible</a> API, it’s easy to apply any existing object lifecycle rules to your R2 buckets.</p><p>Here’s an example of how to configure your R2 bucket’s lifecycle policy using the AWS SDK for JavaScript. To try this out, you’ll need to generate an <a href="https://developers.cloudflare.com/r2/api/s3/tokens/">Access Key</a>.</p>
            <pre><code>import S3 from "aws-sdk/clients/s3.js";

const client = new S3({
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: ACCESS_KEY_ID, //  fill in your own
    secretAccessKey: SECRET_ACCESS_KEY, // fill in your own
  },
  region: "auto",
});

await client
  .putBucketLifecycleConfiguration({
    LifecycleConfiguration: {
      Bucket: "testBucket",
      Rules: [
        // Example: deleting objects by age
        // Delete logs older than 90 days
        {
          ID: "Delete logs after 90 days",
          Filter: {
            Prefix: "logs/",
          },
          Expiration: {
            Days: 90,
          },
        },
        // Example: abort all incomplete multipart uploads after a week
        {
          ID: "Abort incomplete multipart uploads",
          AbortIncompleteMultipartUpload: {
            DaysAfterInitiation: 7,
          },
        },
      ],
    },
  })
  .promise();</code></pre>
            <p>For more information on how object lifecycle policies work and how to configure them in the dashboard or API, see the documentation <a href="https://developers.cloudflare.com/r2/buckets/object-lifecycles/">here</a>.</p><p>Speaking of documentation, if you’d like to provide feedback for R2’s documentation, fill out our <a href="https://docs.google.com/forms/d/e/1FAIpQLScaVrdZh2PoZFvJGFPyMthuGVvKpQvoPfZ-BxIJ4Q5zsQebDA/viewform">documentation survey</a>!</p>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Creating object lifecycle rules to delete ephemeral objects is a great way to reduce storage costs, but what if you need to keep objects around to access in the future? We’re working on new, lower cost ways to store objects in R2 that aren’t frequently accessed, like long tail user-generated content, archive data, and more. If you’re interested in providing feedback and gaining early access, let us know by joining the waitlist <a href="https://forms.gle/JLLzQtqSuLAQNRPX7">here</a>.</p>
    <div>
      <h3>Join the conversation: share your feedback and experiences</h3>
      <a href="#join-the-conversation-share-your-feedback-and-experiences">
        
      </a>
    </div>
    <p>If you have any questions or feedback relating to R2, we encourage you to join our <a href="https://discord.gg/cloudflaredev">Discord community</a> to share! Stay tuned for more exciting R2 updates in the future.</p> ]]></content:encoded>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[R2]]></category>
            <guid isPermaLink="false">2toJKgkVGnaE3gFJUCqFbf</guid>
            <dc:creator>Harshal Brahmbhatt</dc:creator>
            <dc:creator>Phillip Jones</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we built an open-source SEO tool using Workers, D1, and Queues]]></title>
            <link>https://blog.cloudflare.com/how-we-built-an-open-source-seo-tool-using-workers-d1-and-queues/</link>
            <pubDate>Thu, 02 Mar 2023 15:03:54 GMT</pubDate>
            <description><![CDATA[ In this blog post, I’m excited to show off some of the new tools in Cloudflare’s developer arsenal, D1 and Queues, to prototype and ship an internal tool for our SEO experts at Cloudflare. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Building applications on Cloudflare Workers has always been fun. Workers applications have low latency response times by default, and easy developer ergonomics thanks to Wrangler. It's no surprise that for years now, developers have been going from idea to production with Workers in just a few minutes.</p><p>Internally, we're no different. When a member of our team has a project idea, we often reach for Workers first, and not just for the MVP stage, but in production, too. Workers have been a secret ingredient to Cloudflare’s innovation for some time now, allowing us to build products like Access, Stream and Workers KV. Even better, when we have new ideas <i>and</i> we can use new Cloudflare products to build them, it's a great way to give feedback on those products.</p><p>We've discussed this in the past on the Cloudflare blog - in May last year, <a href="/new-dev-docs/">I wrote how we rebuilt Cloudflare's developer documentation</a> using many of the tools that had recently been released in the Workers ecosystem: Cloudflare Pages for hosting, and Bulk Redirects for the redirect rules. In November, <a href="/building-a-better-developer-experience-through-api-documentation/">we released a new version of our API documentation</a>, which again used Pages for hosting, and Pages functions for intelligent caching and transformation of our API schema.</p><p>In this blog post, I’m excited to show off some of the new tools in Cloudflare’s developer arsenal, <a href="https://www.cloudflare.com/developer-platform/products/d1/">D1</a> and <a href="/introducing-cloudflare-queues/">Queues</a>, to prototype and ship an internal tool for our SEO experts at Cloudflare. We've made this project, which we're calling Prospector, open-source too - check it out in our <code>[cloudflare/templates](https://github.com/cloudflare/workers-sdk/tree/main/templates/worker-prospector)</code> repo on GitHub. Whether you're a developer looking to understand how to use multiple parts of Cloudflare's developer stack together, or an SEO specialist who may want to deploy the tool in production, we've made it incredibly easy to get up and running.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6AbvyVpEkBfnOITizlfkhT/73156db0a0fe274ead622a677b6eb959/image1.png" />
            
            </figure>
    <div>
      <h2>What we're building</h2>
      <a href="#what-were-building">
        
      </a>
    </div>
    <p>Prospector is a tool that allows Cloudflare's SEO experts to monitor our blog and marketing site for specific keywords. When a keyword is matched on a page, Prospector will notify an email address. This allows our SEO experts to stay informed of any changes to our website, and take action accordingly.</p><p><a href="/sending-email-from-workers-with-mailchannels/">Using MailChannels' integration with Workers</a>, we can quickly and easily send emails from our application using a single API call. This allows us to focus on the core functionality of the application, and not worry about the details of sending emails.</p><p>Prospector uses Cloudflare Workers as the user-facing API for the application. It uses D1 to store and retrieve data in real-time, and Queues to handle the fetching of all URLs and the notification process. We've also included an intuitive user interface for the application, which is built with HTML, CSS, and JavaScript.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/339I5LA7CAciIdEOyfKkl3/fc05a5f4b5d41ef794d638df5e41d1fb/image3-1.png" />
            
            </figure>
    <div>
      <h2>Why we built it</h2>
      <a href="#why-we-built-it">
        
      </a>
    </div>
    <p>It is widely known in SEO that both internal and external links help Google and other search engines understand what a website is about, which impacts keyword rankings. Not only do these links guide readers to additional helpful information, they also allow <a href="https://www.cloudflare.com/learning/bots/what-is-a-web-crawler/">web crawlers</a> for search engines to discover and index content on the site.</p><p>Acquiring external links is often a time-consuming process and at the discretion of third parties, whereas website owners typically have much more control over internal links. As a result, internal linking is one of the most useful levers available in SEO.</p><p>In an ideal world, every piece of content would be fully formed upon publication, replete with helpful internal links throughout the piece. However, this is often not the case. Many times, content is edited after the fact or additional pieces of relevant content come along after initial publication. These situations result in missed opportunities for internal linking.</p><p>Like other large organizations, Cloudflare has published thousands of blogs and web pages over the years. We share new content every time a product/technology is introduced and improved. Ultimately, that also means it's become more challenging to identify opportunities for internal linking in a timely, automated fashion. We needed a tool that would allow us to identify internal linking opportunities as they appear, and speed up the time it takes to identify new internal linking opportunities.</p><p>Although we tested several tools that might solve this problem, we found that they were limited in several ways. First, some tools only scanned the first 2,000 characters of a web page. Any opportunities found beyond that limit would not be detected. Next, some tools did not allow us to limit searches to certain areas of the site and resulted in many false positives. Finally, other potential solutions required manual operation, leaving the process at the mercy of human memory.</p><p>To solve our problem (and ultimately, improve our SEO), we needed an automated tool that could discover and notify us of new instances of targeted phrases on a specified range of pages.</p>
    <div>
      <h2>How it works</h2>
      <a href="#how-it-works">
        
      </a>
    </div>
    
    <div>
      <h3>Data model</h3>
      <a href="#data-model">
        
      </a>
    </div>
    <p>First, let's explore the data model for Prospector. We have two main tables: <code>notifiers</code> and <code>urls</code>. The <code>notifiers</code> table stores the email address and keyword that we want to monitor. The <code>urls</code> table stores the URL and sitemap that we want to scrape. The <code>notifiers</code> table has a one-to-many relationship with the <code>urls</code> table, meaning that each notifier can have many URLs associated with it.</p><p>In addition, we have a <code>sitemaps</code> table that stores the sitemap URLs that we've scraped. Many larger websites don't just have a single sitemap: the Cloudflare blog, for instance, has a primary sitemap that contains four sub-sitemaps. When the application is deployed, a primary sitemap is provided as configuration, and Prospector will parse it to find all of the sub-sitemaps.</p><p>Finally, <code>notifier_matches</code> is a table that stores the matches between a notifier and a URL. This allows us to keep track of which URLs have already been matched, and which ones still need to be processed. When a match has been found, the <code>notifier_matches</code> table is updated to reflect that, and "matches" for a keyword are no longer processed. This saves our SEO experts from a crowded inbox, and allows them to focus and act on new matches.</p><p><b>Connecting the pieces with Cloudflare Queues</b>Cloudflare Queues acts as the work queue for Prospector. When a new notifier is added, a new job is created for it and added to the queue. Behind the scenes, Queues will distribute the work across multiple Workers, allowing us to scale the application as needed. When a job is processed, Prospector will scrape the URL and check for matches. If a match is found, Prospector will send an email to the notifier's email address.</p><p>Using the Cron Triggers functionality in Workers, we can schedule the scraping process to run at a regular interval - by default, once a day. This allows us to keep our data up-to-date, and ensures that we're always notified of any changes to our website. It also allows the end-user to configure when they receive emails in case they want to receive them more or less frequently, or at the beginning of their workday.</p><p>The Module Workers syntax for Workers makes accessing the application bindings - the constants available in the application for querying D1, Queues, and other services - incredibly easy. <code>src/index.ts</code>, the entrypoint for the application, looks like this:</p>
            <pre><code>import { DBUrl, Env } from './types'

import {
  handleQueuedUrl,
  scheduled,
} from './functions';

import h from './api'

export default {
  async fetch(
	request: Request,
	env: Env,
	ctx: ExecutionContext
  ): Promise&lt;Response&gt; {
	return h.fetch(request, env, ctx)
  },

  async queue(
	batch: MessageBatch&lt;Error&gt;,
	env: Env
  ): Promise&lt;void&gt; {
	for (const message of batch.messages) {
  	const url: DBUrl = JSON.parse(message.body)
  	await handleQueuedUrl(url, env.DB)
	}
  },

  async scheduled(
	env: Env,
  ): Promise&lt;void&gt; {
	await scheduled({
  	authToken: env.AUTH_TOKEN,
  	db: env.DB,
  	queue: env.QUEUE,
  	sitemapUrl: env.SITEMAP_URL,
	})
  }
};</code></pre>
            <p>With this syntax, we can see where the various events incoming to the application - the <code>fetch</code> event, the <code>queue</code> event, and the <code>scheduled</code> event - are handled. The <code>fetch</code> event is the main entrypoint for the application, and is where we handle all of the API routes. The <code>queue</code> event is where we handle the work that's been added to the queue, and the <code>scheduled</code> event is where we handle the scheduled scraping process.</p><p>Central to the application, of course, is Workers - acting as the API gateway and coordinator. We've elected to use the popular open-source framework <a href="https://honojs.dev/">Hono</a>, an Express-style API for Workers, in Prospector. With Hono, we can quickly map out a REST API in just a few lines of code. Here's an example of a few API routes and how they're defined with Hono:</p>
            <pre><code>const app = new Hono()

app.get("/", (context) =&gt; {
  return context.html(index)
})

app.post("/notifiers", async context =&gt; {
  try {
	const { keyword, email } = await context.req.parseBody()
	await context.env.DB.prepare(
  	"insert into notifiers (keyword, email) values (?, ?)"
	).bind(keyword, email).run()
	return context.redirect('/')
  } catch (err) {
	context.status(500)
	return context.text("Something went wrong")
  }
})

app.get('/sitemaps', async (context) =&gt; {
  const query = await context.env.DB.prepare(
	"select * from sitemaps"
  ).all();
  const sitemaps: Array&lt;DBSitemap&gt; = query.results
  return context.json(sitemaps)
})</code></pre>
            <p>Crucial to the development of Prospector are the improved TypeScript bindings for Workers. <a href="/improving-workers-types/">As announced in November of last year</a>, TypeScript bindings for Workers are now automatically generated based on <a href="/workerd-open-source-workers-runtime/">our open source runtime, <code>workerd</code></a>. This means that whenever we use the types provided from the <a href="https://github.com/cloudflare/workers-types"><code>@cloudflare/workers-types</code> package</a> in our application, we can be sure that the types are always up-to-date.</p><p>With these bindings, we can define the types for our environment variables, and use them in our application. Here's an example of the <code>Env</code> type, which defines the environment variables that we use in the application:</p>
            <pre><code>export interface Env {
  AUTH_TOKEN: string
  DB: D1Database
  QUEUE: Queue
  SITEMAP_URL: string
}</code></pre>
            <p>Notice the types of the <code>DB</code> and <code>QUEUE</code> bindings - <code>D1Database</code> and <code>Queue</code>, respectively. These types are automatically generated, complete with type signatures for each method inside of the D1 and Queue APIs. This means that we can be sure that we're using the correct methods, and that we're passing the correct arguments to them, directly from our text editor - without having to refer to the documentation.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6uqW1v9MEpdsEMieihxKFk/e865da1397167301f051616d61f83a1a/image4.png" />
            
            </figure>
    <div>
      <h2>How to use it</h2>
      <a href="#how-to-use-it">
        
      </a>
    </div>
    <p>One of my favorite things about Workers is that deploying applications is quick and easy. Using `wrangler.toml` and some simple build scripts, we can deploy a fully-functional application in just a few minutes. Prospector is no different. With just a few commands, we can create the necessary D1 database and Queues instance, and deploy the application to our account.</p><p>First, you'll need to clone the repository from our cloudflare/templates repository:</p><p><code>git clone $URL</code></p><p>If you haven't installed wrangler yet, you can do so by running:</p><p><code>npm install @cloudflare/wrangler -g</code></p><p>With Wrangler installed, you can login to your account by running:</p><p><code>wrangler login</code></p><p>After you've done that, you'll need to create a new D1 database, as well as a Queues instance. You can do this by running the following commands:</p><p><code>wrangler d1 create $DATABASE_NAMEwrangler queues create $QUEUE_NAME</code></p><p>Configure your <code>wrangler.toml</code> with the appropriate bindings (see [the README](URL) for an example):</p>
            <pre><code>[[ d1_databases ]]
binding = "DB"
database_name = "keyword-tracker-db"
database_id = "ab4828aa-723b-4a77-a3f2-a2e6a21c4f87"
preview_database_id = "8a77a074-8631-48ca-ba41-a00d0206de32"
	
[[queues.producers]]
  queue = "queue"
  binding = "QUEUE"

[[queues.consumers]]
  queue = "queue"
  max_batch_size = 10
  max_batch_timeout = 30
  max_retries = 10
  dead_letter_queue = "queue-dlq"</code></pre>
            <p>Next, you can run the <code>bin/migrate</code> script to create the tables in your database:</p><p><code>bin/migrate</code></p><p>This will create all the needed tables in your database, both in development (locally) and in production. Note that you'll even see the creation of a honest-to-goodness <code>.sqlite3</code> file in your project directory - this is the local development database, which you can connect to directly using the same SQLite CLI that you're used to:</p><p><code>$ sqlite3 .wrangler/state/d1/DB.sqlite3sqlite&gt; .tables notifier_matches  notifiers      sitemaps       urls</code></p><p>Finally, you can deploy the application to your account:</p><p><code>npm run deploy</code></p><p>With a deployed application, you can visit your Workers URL to see the user interface. From there, you can add new notifiers and URLs, and see the results of your scraping process. When a new keyword match is found, you’ll receive an email with the details of the match instantly:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2P59U5wQRoysgE8nWLbwLD/b1e7240ddd90dd36676163b201998cb3/image2-1.png" />
            
            </figure>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>For some time, there have been a great deal of applications that were hard to build on Workers without relational data or background task tooling. Now, with D1 and Queues, we can build applications that seamlessly integrate between real-time user interfaces, geographically distributed data, background processing, and more, all using the same developer ergonomics and low latency that Workers is known for.</p><p>D1 has been crucial for building this application. On larger sites, the number of URLs that need to be scraped can be quite large. If we were to use Workers KV, our key-value store, for storing this data, we would quickly struggle with how to model, retrieve, and update the data needed for this use-case. With D1, we can build relational data models and quickly query <i>just</i> the data we need for each queued processing task.</p><p>Using these tools, developers can build internal tools and applications for their companies that are more powerful and more scalable than ever before. With the integration of Cloudflare's Zero Trust suite, developers can make these applications secure by default, and deploy them to Cloudflare's global network. This allows developers to build applications that are fast, secure, and reliable, all without having to worry about the underlying infrastructure.</p><p>Prospector is a great example of how easy it is to build applications on Cloudflare Workers. With the recent addition of D1 and Queues, we've been able to build fully-functional applications that require real-time data and background processing in just a few hours. We're excited to share the open-source code for Prospector, and we'd love to hear your feedback on the project.</p><p>If you have any questions, feel free to reach out to us on Twitter at <a href="https://twitter.com/cloudflaredev">@cloudflaredev</a>, or join us in the Cloudflare Workers Discord community, which recently hit 20k members and is a great place to ask questions and get help from other developers.</p> ]]></content:encoded>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[D1]]></category>
            <category><![CDATA[Queues]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">3Ye7OiZdwDby0AGqA7LQAh</guid>
            <dc:creator>Kristian Freeman</dc:creator>
            <dc:creator>Neal Kindschi</dc:creator>
        </item>
        <item>
            <title><![CDATA[Making static sites dynamic with Cloudflare D1]]></title>
            <link>https://blog.cloudflare.com/making-static-sites-dynamic-with-cloudflare-d1/</link>
            <pubDate>Wed, 16 Nov 2022 14:00:00 GMT</pubDate>
            <description><![CDATA[ In this blog post, I'll show you how to use D1 to add comments to a static blog site. To do this, we'll construct a new D1 database and build a simple JSON API that allows the creation and retrieval of comments. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4EtdDhbUmqO6pJ0onYlD80/6c88d9a3b9189dac7ead2fb45c2450f0/image1-40.png" />
            
            </figure>
    <div>
      <h3>Introduction</h3>
      <a href="#introduction">
        
      </a>
    </div>
    <p>There are many ways to store data in your applications. For example, in Cloudflare Workers applications, we have Workers KV for key-value storage and Durable Objects for real-time, coordinated storage without compromising on consistency. Outside the Cloudflare ecosystem, you can also plug in other tools like NoSQL and graph databases.</p><p>But sometimes, you want SQL. Indexes allow us to retrieve data quickly. Joins enable us to describe complex relationships between different tables. SQL declaratively describes how our application's data is validated, created, and performantly queried.</p><p><a href="/d1-open-alpha">D1 was released today in open alpha</a>, and to celebrate, I want to share my experience building apps with D1: specifically, how to get started, and why I’m excited about D1 joining the long list of tools you can use to build apps on Cloudflare.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1T6IVlTeQ59oZQ2M6auyey/ea995b570a3dfb93c2c1d466d75f4524/image3-24.png" />
            
            </figure><p><a href="https://www.cloudflare.com/developer-platform/products/d1/">D1</a> is remarkable because it's an instant value-add to applications without needing new tools or stepping out of the Cloudflare ecosystem. Using wrangler, we can do local development on our Workers applications, and with the addition of D1 in wrangler, we can now develop proper stateful applications locally as well. Then, when it's time to deploy the application, wrangler allows us to both access and execute commands to your D1 database, as well as your API itself.</p>
    <div>
      <h3>What we’re building</h3>
      <a href="#what-were-building">
        
      </a>
    </div>
    <p>In this blog post, I'll show you how to use D1 to add comments to a static blog site. To do this, we'll construct a new D1 database and build a simple JSON API that allows the creation and retrieval of comments.</p><p>As I mentioned, separating D1 from the app itself - an API and database that remains separate from the static site - allows us to abstract the static and dynamic pieces of our website from each other. It also makes it easier to deploy our application: we will deploy the frontend to Cloudflare Pages, and the D1-powered API to Cloudflare Workers.</p>
    <div>
      <h3>Building a new application</h3>
      <a href="#building-a-new-application">
        
      </a>
    </div>
    <p>First, we'll add a basic <a href="https://www.cloudflare.com/learning/security/api/what-is-an-api/">API</a> in Workers. Create a new directory and in it a new wrangler project inside it:</p>
            <pre><code>$ mkdir d1-example &amp;&amp; d1-example
$ wrangler init</code></pre>
            <p>In this example, we’ll use Hono, an Express.js-style framework, to rapidly build our API. To use Hono in this project, install it using NPM:</p>
            <pre><code>$ npm install hono</code></pre>
            <p>Then, in <code>src/index.ts</code>, we’ll initialize a new Hono app, and define a few endpoints - GET /API/posts/:slug/comments, and POST /get/api/:slug/comments.</p>
            <pre><code>import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono()

app.get('/api/posts/:slug/comments', async c =&gt; {
  // do something
})

app.post('/api/posts/:slug/comments', async c =&gt; {
  // do something
})

export default app</code></pre>
            <p>Now we'll create a D1 database. In Wrangler 2, there is support for the <code>wrangler d1</code> subcommand, which allows you to create and query your D1 databases directly from the command line. So, for example, we can create a new database with a single command:</p>
            <pre><code>$ wrangler d1 create d1-example</code></pre>
            <p>With our created database, we can take the database name ID and associate it with a <b>binding</b> inside of wrangler.toml, wrangler's configuration file. Bindings allow us to access Cloudflare resources, like D1 databases, KV namespaces, and R2 buckets, using a simple variable name in our code. Below, we’ll create the binding <code>DB</code> and use it to represent our new database:</p>
            <pre><code>[[ d1_databases ]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "d1-example"
database_id = "4e1c28a9-90e4-41da-8b4b-6cf36e5abb29"</code></pre>
            <p>Note that this directive, the <code>[[d1_databases]]</code> field, currently requires a beta version of wrangler. You can install this for your project using the command <code>npm install -D wrangler/beta</code>.</p><p>With the database configured in our wrangler.toml, we can start interacting with it from the command line and inside our Workers function.</p><p>First, you can issue direct SQL commands using <code>wrangler d1 execute</code>:</p>
            <pre><code>$ wrangler d1 execute d1-example --command "SELECT name FROM sqlite_schema WHERE type ='table'"
Executing on d1-example:
┌─────────────────┐
│ name │
├─────────────────┤
│ sqlite_sequence │
└─────────────────┘</code></pre>
            <p>You can also pass a SQL file - perfect for initial data seeding in a single command. Create <code>src/schema.sql</code>, which will create a new <code>comments</code> table for our project:</p>
            <pre><code>drop table if exists comments;
create table comments (
  id integer primary key autoincrement,
  author text not null,
  body text not null,
  post_slug text not null
);
create index idx_comments_post_id on comments (post_slug);

-- Optionally, uncomment the below query to create data

-- insert into comments (author, body, post_slug)
-- values ("Kristian", "Great post!", "hello-world");</code></pre>
            <p>With the file created, execute the schema file against the D1 database by passing it with the flag <code>--file</code>:</p>
            <pre><code>$ wrangler d1 execute d1-example --file src/schema.sql</code></pre>
            <p>We've created a SQL database with just a few commands and seeded it with initial data. Now we can add a route to our Workers function to retrieve data from that database. Based on our wrangler.toml config, the D1 database is now accessible via the <code>DB</code> binding. In our code, we can use the binding to prepare SQL statements and execute them, for instance, to retrieve comments:</p>
            <pre><code>app.get('/api/posts/:slug/comments', async c =&gt; {
  const { slug } = c.req.param()
  const { results } = await c.env.DB.prepare(`
    select * from comments where post_slug = ?
  `).bind(slug).all()
  return c.json(results)
})</code></pre>
            <p>In this function, we accept a <code>slug</code> URL query parameter and set up a new SQL statement where we select all comments with a matching <code>post_slug</code> value to our query parameter. We can then return it as a simple JSON response.</p><p>So far, we've built read-only access to our data. But "inserting" values to SQL is, of course, possible as well. So let's define another function that allows POST-ing to an endpoint to create a new comment:</p>
            <pre><code>app.post('/API/posts/:slug/comments', async c =&gt; {
  const { slug } = c.req.param()
  const { author, body } = await c.req.json&lt;Comment&gt;()

  if (!author) return c.text("Missing author value for new comment")
  if (!body) return c.text("Missing body value for new comment")

  const { success } = await c.env.DB.prepare(`
    insert into comments (author, body, post_slug) values (?, ?, ?)
  `).bind(author, body, slug).run()

  if (success) {
    c.status(201)
    return c.text("Created")
  } else {
    c.status(500)
    return c.text("Something went wrong")
  }
})</code></pre>
            <p>In this example, we built a comments API for powering a blog. To see the source for this D1-powered comments API, you can visit <a href="https://github.com/cloudflare/templates/tree/main/worker-d1-api">cloudflare/templates/worker-d1-api</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3Bbc7exdfzvFnV47Btu7Gn/362b947983416c62e0b9670417e1babb/image2-31.png" />
            
            </figure>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>One of the things most exciting about D1 is the opportunity to augment existing applications or websites with dynamic, relational data. As a former Ruby on Rails developer, one of the things I miss most about that framework in the world of JavaScript and serverless development tools is the ability to rapidly spin up full data-driven applications without needing to be an expert in managing database infrastructure. With D1 and its easy onramp to SQL-based data, we can build true data-driven applications without compromising on performance or developer experience.</p><p>This shift corresponds nicely with the advent of static sites in the last few years, using tools like Hugo or Gatsby. A blog built with a static site generator like Hugo is incredibly performant - it will build in seconds with small asset sizes.</p><p>But by trading a tool like WordPress for a static site generator, you lose the opportunity to add dynamic information to your site. Many developers have patched over this problem by adding more complexity to their build processes: fetching and retrieving data and generating pages using that data as part of the build.</p><p>This addition of complexity in the build process attempts to fix the lack of dynamism in applications, but it still isn't genuinely dynamic. Instead of being able to retrieve and display new data as it's created, the application rebuilds and redeploys whenever data changes so that it appears to be a live, dynamic representation of data. Your application can remain static, and the dynamic data will live geographically close to the users of your site, accessible via a queryable and expressive API.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[D1]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">42d4M0F5dhHm6ImRHKTL7Z</guid>
            <dc:creator>Kristian Freeman</dc:creator>
        </item>
        <item>
            <title><![CDATA[Easy Postgres integration on Cloudflare Workers with Neon.tech]]></title>
            <link>https://blog.cloudflare.com/neon-postgres-database-from-workers/</link>
            <pubDate>Tue, 15 Nov 2022 13:44:00 GMT</pubDate>
            <description><![CDATA[ Neon.tech is a database platform that allows you to branch your (Postgres compatible) database exactly like your code repository. And with the release of their Cloudflare Workers integration you can get started in minutes ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5xCkAJAUyFmN2b0MizGCP0/e191956551ac3604f322f51bee54dc02/image1-28.png" />
            
            </figure><p>It’s no wonder that Postgres is one of the world’s favorite databases. It’s easy to learn, a pleasure to use, and can scale all the way up from your first database in an early-stage startup to the system of record for giant organizations. Postgres has been an integral part of Cloudflare’s journey, so we know this fact well. But when it comes to connecting to Postgres from environments like Cloudflare Workers, there are unfortunately a bunch of challenges, as we mentioned in our <a href="/relational-database-connectors/">Relational Database Connector post</a>.</p><p>Neon.tech not only solves these problems; it also has other cool features such as <a href="https://neon.tech/docs/conceptual-guides/branching/">branching databases</a> — being able to branch your database in exactly the same way you branch your code: instant, cheap and completely isolated.</p>
    <div>
      <h3>How to use it</h3>
      <a href="#how-to-use-it">
        
      </a>
    </div>
    <p>It’s easy to get started. Neon’s client library <code>@neondatabase/serverless</code> is a drop-in replacement for <a href="https://node-postgres.com/">node-postgres</a>, the npm <code>pg</code> package with which you may already be familiar. After going through the <a href="https://neon.tech/docs/get-started-with-neon/signing-up/">getting started</a> process to set up your Neon database, you can easily create a Worker to ask Postgres for the current time like so:</p><ol><li><p><b>Create a new Worker</b> — Run <code>npx wrangler init neon-cf-demo</code> and accept all the defaults. Enter the new folder with <code>cd neon-cf-demo</code>.</p></li><li><p><b>Install the Neon package</b> — Run <code>npm install @neondatabase/serverless</code>.</p></li><li><p><b>Provide connection details</b> — For deployment, run <code>npx wrangler secret put DATABASE_URL</code> and paste in your connection string when prompted (you’ll find this in your Neon dashboard: something like <code>postgres://user:password@project-name-1234.cloud.neon.tech/main</code>). For development, create a new file <code>.dev.vars</code> with the contents <code>DATABASE_URL=</code> plus the same connection string.</p></li><li><p><b>Write the code</b> — Lastly, replace <code>src/index.ts</code> with the following code:</p></li></ol>
            <pre><code>import { Client } from '@neondatabase/serverless';
interface Env { DATABASE_URL: string; }

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const client = new Client(env.DATABASE_URL);
    await client.connect();
    const { rows: [{ now }] } = await client.query('select now();');
    ctx.waitUntil(client.end());  // this doesn’t hold up the response
    return new Response(now);
  }
}</code></pre>
            <p>To try this locally, type <code>npm start</code>. To deploy it around the globe, type <code>npx wrangler publish</code>.</p><p>You can also <a href="https://github.com/neondatabase/serverless-cfworker-demo">check out the source</a> for a slightly more complete demo app. This shows <a href="https://neon-cf-pg-test.pages.dev/">your nearest UNESCO World Heritage sites</a> using IP geolocation in Cloudflare Workers and nearest-neighbor sorting in PostGIS.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1cdG0zVBCrFyiBilGo94o8/1d33ccaca61c6efec303aaa763ddb1e8/image3-18.png" />
            
            </figure><p>How does this work? In this case, we take the coordinates supplied to our Worker in <code>request.cf.longitude</code> and <code>request.cf.latitude</code>. We then feed these coordinates to a SQL query that uses the <a href="https://postgis.net/docs/geometry_distance_knn.html">PostGIS distance operator <code>&lt;-&gt;</code></a> to order our results:</p>
            <pre><code>const { longitude, latitude } = request.cf
const { rows } = await client.query(`
  select 
    id_no, name_en, category,
    st_makepoint($1, $2) &lt;-&gt; location as distance
  from whc_sites_2021
  order by distance limit 10`,
  [longitude, latitude]
);</code></pre>
            <p>Since we created a <a href="http://postgis.net/workshops/postgis-intro/indexing.html">spatial index</a> on the location column, the query is blazing fast. The result (<code>rows</code>) looks like this:</p>
            <pre><code>[{
  "id_no": 308,
  "name_en": "Yosemite National Park",
  "category": "Natural",
  "distance": 252970.14782223428
},
{
  "id_no": 134,
  "name_en": "Redwood National and State Parks",
  "category": "Natural",
  "distance": 416334.3926827573
},
/* … */
]</code></pre>
            <p>For even lower latencies, we could cache these results at a slightly coarser geographical resolution — rounding, say, to one sixtieth of a degree (one <a href="https://en.wikipedia.org/wiki/Minute_and_second_of_arc">arc minute</a>) of longitude and latitude, which is a little under a mile.</p><p><a href="https://console.neon.tech/?invite=serverless">Sign up to Neon</a> using the invite code <i>serverless</i> and try the @neondatabase/serverless driver with Cloudflare Workers.</p>
    <div>
      <h3>Why we did it</h3>
      <a href="#why-we-did-it">
        
      </a>
    </div>
    <p>Cloudflare Workers has enormous potential to improve back-end development and deployment. It’s cost-effective, admin-free, and radically scalable.</p><p>The use of V8 isolates means Workers are now fast and lightweight enough for nearly any use case. But it has a key drawback: Cloudflare Workers don’t yet support raw TCP communication, which has made database connections a challenge.</p><p>Even when Workers eventually support raw TCP communication, we will not have fully solved our problem, because database connections are expensive to set up and also have quite a bit of memory overhead.</p><p>This is what the solution looks like:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7CWb6OEZuNu8mg1YJsQTzY/3c14f184ab034be30f106f921f2485c7/image2-23.png" />
            
            </figure><p>It consists of three parts:</p><ol><li><p><b>Connection pooling built into the platform</b> — Given Neon’s serverless compute model, splitting storage and compute operations, it is not recommended to rely on a one-to-one mapping between external clients and Postgres connections. Instead, you can turn on connection pooling simply by flicking a switch (it’s in the <i>Settings</i> area of your Neon dashboard).</p></li><li><p><b>WebSocket proxy</b> — We deploy our <a href="https://github.com/neondatabase/wsproxy">own WebSocket-to-TCP proxy</a>, written in Go. The proxy simply accepts WebSocket connections from Cloudflare Worker clients, relays message payloads to a requested (Neon-only) host over plain TCP, and relays back the responses.</p></li><li><p><b>Client library</b> — Our driver library is based on node-postgres but provides the necessary <a href="https://github.com/neondatabase/serverless">shims for Node.js features</a> that aren’t present in Cloudflare Workers. Crucially, we replace Node’s <code>net.Socket</code> and <code>tls.connect</code> with code that redirects network reads and writes via the WebSocket connection. To support end-to-end TLS encryption between Workers and the database, we compile <a href="https://www.wolfssl.com/">WolfSSL</a> to WebAssembly with <a href="https://emscripten.org/">emscripten</a>. Then we use <a href="https://esbuild.github.io/">esbuild</a> to bundle it all together into an easy-to-use npm package.</p></li></ol><p>The <code>@neondatabase/serverless</code> package is currently in public beta. We have plans to improve, extend, and explain it further in the near future <a href="https://neon.tech/blog/">on the Neon blog</a>. In line with our commitment to open source, you can configure our serverless driver and/or run our WebSocket proxy to provide access to Postgres databases hosted anywhere — just see the respective repos for details.</p><p>So <a href="https://console.neon.tech/?invite=serverless">try Neon</a> using invite code <code><i>serverless</i></code>, <a href="https://workers.cloudflare.com/">sign up and connect to it with Cloudflare Workers</a>, and you’ll have a fully flexible back-end service running in next to no time.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Guest Post]]></category>
            <guid isPermaLink="false">7yvumN0c8zRWHVBbQAkbLc</guid>
            <dc:creator>Erwin van der Koogh</dc:creator>
            <dc:creator>George MacKerron (Guest Author)</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Workers scale too well and broke our infrastructure, so we are rebuilding it on Workers]]></title>
            <link>https://blog.cloudflare.com/devcycle-customer-story/</link>
            <pubDate>Mon, 14 Nov 2022 14:00:00 GMT</pubDate>
            <description><![CDATA[ DevCycle re-architected their feature management tool using Workers for improved scalability ]]></description>
            <content:encoded><![CDATA[ <p></p><p>While scaling our new Feature Flagging product <a href="https://devcycle.com/">DevCycle</a>, we’ve encountered an interesting challenge: our Cloudflare Workers-based infrastructure can handle way more instantaneous load than our traditional AWS infrastructure. This led us to rethink how we design our infrastructure to always use Cloudflare Workers for everything.</p>
    <div>
      <h3>The origin of DevCycle</h3>
      <a href="#the-origin-of-devcycle">
        
      </a>
    </div>
    <p>For almost 10 years, Taplytics has been a leading provider of no-code A/B testing and feature flagging solutions for product and marketing teams across a wide range of use cases for some of the largest consumer-facing companies in the world. So when we applied ourselves to build a new engineering-focused feature management product, DevCycle, we built upon our experience using Workers which have served over 140 billion requests for Taplytics customers.</p><p>The inspiration behind DevCycle is to build a focused feature management tool for engineering teams, empowering them to build their software more efficiently and deploy it faster. Helping engineering teams reach their goals, whether it be continuous deployment, lower change failure rate, or a faster recovery time. DevCycle is the culmination of our vision of how teams should use Feature Management to build high-quality software faster. We've used DevCycle to <i>build</i> DevCycle, enabling us to implement continuous deployment successfully.</p>
    <div>
      <h3>DevCycle architecture</h3>
      <a href="#devcycle-architecture">
        
      </a>
    </div>
    <p>One of the first things we asked ourselves when ideating DevCycle was how we could get out of the business of managing 1000’s of vCPUs worth of AWS instances and move our core business logic closer to our end-user devices. Based on our experience with Cloudflare Workers at Taplytics we knew we wanted it to be a core part of our future infrastructure for DevCycle.</p><p>By using the global computing power of Workers and moving as much logic to the SDKs as possible with our local bucketing server-side SDKs, we were able to massively reduce or eliminate the latency of fetching feature flag configurations for our users. In addition, we used a shared WASM library across our Workers and local bucketing SDKs to dramatically reduce the amount of code we need to maintain per SDK, and increase the consistency of our platform. This architecture has also fundamentally changed our business's cost structure to easily serve any customer of any scale.</p><p>The core architecture of DevCycle revolves around publishing and consuming JSON configuration files per project environment. The publishing side is managed in our AWS services, while Cloudflare manages the consumption of these config files at scale. This split in responsibilities allows for all high-scale requests to be managed by Cloudflare, while keeping our AWS services simple and low-scale.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5u1ZlzkOHcB4ImfB5t9Nw7/95a30c622c0b646be1472e5619f8fc1d/image1-22.png" />
            
            </figure>
    <div>
      <h3>“Workers are breaking our events pipeline”</h3>
      <a href="#workers-are-breaking-our-events-pipeline">
        
      </a>
    </div>
    <p>One of the primary challenges as a feature management platform is that we don’t have direct control over the load from our customers’ applications using our SDKs; our systems need the ability to scale instantly to match their load. For example, we have a couple of large customers whose mobile traffic is primarily driven by push notifications, which causes massive instantaneous spikes in traffic to our APIs in the range of 10x increases in load. As you can imagine, any traditional auto-scaled API service and the load balancer cannot manage that type of increase in load. Thus, our choices are to dramatically increase the minimum size of our cluster and load balancer to handle these unknown load spikes, accept that some requests will be rate-limited, or move to an architecture that can handle this load.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6wv1AhdzZCGrR1nX0x0BBm/3788b48880257b4346e1ec58fda2e9a6/image4-10.png" />
            
            </figure><p>Given that all our SDK API requests are already served with Workers, they have no problem scaling instantly to 10x+ their base load. Sadly we can’t say the same about the traditional parts of our infrastructure.</p><p>For each feature flag configuration request to a Worker, a corresponding events request is sent to our AWS events infrastructure. The events are received by our events API container in Kubernetes, where they are then published to Kafka and eventually ingested by Snowflake. While Cloudflare Workers have no problem handling instantaneous spikes in feature flag requests, the events system can't keep up. Our cluster and events API containers need to be scaled up faster to prevent the existing instances from being overwhelmed. Even the load balancer has issues accepting the sudden increase. Cloudflare Workers just work too well in comparison to EC2 instances + EKS.</p><p>To solve this issue we are moving towards a new events Cloudflare Worker which will be able to handle the instantaneous events load from these requests and make use of the Kinesis Data Firehose to write events to our existing S3 bucket which is ingested by Snowflake. In the future, we look forward to testing out Cloudflare Queues writing to R2 once a Snowflake connector has been created. This architecture should allow us to ingest events at almost any scale and withstand instantaneous traffic spikes with a predictable and efficient cost structure.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3sOm1ZrZvJGgcHWtGvzPB8/2872f752d67f87eb2cbfa95b6ca0cffe/image2-21.png" />
            
            </figure>
    <div>
      <h3>Building without a database next to your code</h3>
      <a href="#building-without-a-database-next-to-your-code">
        
      </a>
    </div>
    <p>Workers provide many benefits, including fast response times, infinite scalability, serverless architecture, and excellent up-time performance. However, if you want to see all these benefits, you need to architect your Workers to assume that you don’t have direct access to a centralized SQL / NoSQL database (or <a href="/whats-new-with-d1/">D1</a>) like you would with a traditional API service. For example, suppose you build your workers to require reaching out to a database to fetch and update user data every time a request is made to your Workers. In that case, your request latency will be tied to the geographic distance between your Worker and the database plus the latency of the database. In addition, your Workers will be able to scale significantly beyond the number of database connections you can support, and your uptime will be tied to the uptime of your external database. Therefore, when architecting your systems to use Workers, we advise relying primarily on data sent as part of the API request and cacheable data on Cloudflare’s global network.</p><p>Cloudflare provides multiple products and services to help with data on their global network:</p><ul><li><p><a href="https://developers.cloudflare.com/workers/learning/how-kv-works/">KV</a>: “global, low-latency, key-value data store.”</p><ul><li><p>However, the lowest latency way of retrieving data from within a Worker is limited by a minimum 60-second TTL. So you’ll need to be ok with cached data that is 60 seconds stale.</p></li></ul></li><li><p><a href="https://developers.cloudflare.com/workers/learning/using-durable-objects/'">Durable Objects</a>: “provide low-latency coordination and consistent storage for the Workers platform through two features: global uniqueness and a transactional storage API.”</p><ul><li><p>Ability to store user-level information closer to the end user.</p></li><li><p>Unfamiliar worker interface for accessing data for developers with SQL / NoSQL experience.</p></li></ul></li><li><p><a href="https://developers.cloudflare.com/r2/data-access/workers-api/workers-api-usage/">R2</a>: “store large amounts of unstructured data.”</p><ul><li><p>Ability to store arbitrarily large amounts of unstructured data using familiar S3 APIs.</p></li><li><p>Cloudflare’s cache can be used to provide low-latency access within workers.</p></li></ul></li><li><p><a href="/introducing-d1/">D1</a>: “<a href="https://www.cloudflare.com/developer-platform/products/d1/">serverless SQLite database</a>”</p></li></ul><p>Each of these tools that Cloudflare provides has made building APIs far more accessible than when Workers launched initially; however, each service has aspects which need to be accounted for when architecting your systems. Being an open platform, you can also access any publically available database you want from a Worker. For example, we are making use of <a href="https://www.macrometa.com/platform/global-data-mesh">Macrometa</a> for our <a href="https://devcycle.com/features/edge-flags">EdgeDB</a> product built into our Workers to help customers access their user data.</p>
    <div>
      <h3>The predictable cost structure of Workers</h3>
      <a href="#the-predictable-cost-structure-of-workers">
        
      </a>
    </div>
    <p>One of the greatest advantages of moving most of our workloads towards Cloudflare Workers is the predictable cost structure that can scale 1:1 with our request loads and can be easily mapped to usage-based billing for our customers. In addition, we no longer have to run excess EC2 instances to handle random spikes in load, just in case they happen.</p><p>Too many SaaS services have opaque billing based on max usage or other metrics that don’t relate directly to their costs. Moving from our legacy AWS architecture with high fixed costs like databases and caching layers to Workers has resulted in our infrastructure spending is directly tied to using our APIs and SDKs. For DevCycle, this architecture has been over ~5x more cost-efficient to operate.</p>
    <div>
      <h3>The future of DevCycle and Cloudflare</h3>
      <a href="#the-future-of-devcycle-and-cloudflare">
        
      </a>
    </div>
    <p>With DevCycle we will continue to invest in leveraging serverless computing and moving our core business logic as close to our users as possible, either on Cloudflare’s global network or locally within our SDKs. We’re excited to integrate even more deeply with the Cloudflare developer platform as new services evolve. We already see future use cases for R2, Queues and Durable Objects and look forward to what’s coming next from Cloudflare.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Guest Post]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">3O0ZAqf35EBtm8oisold23</guid>
            <dc:creator>Jonathan Norris (Guest Author)</dc:creator>
        </item>
        <item>
            <title><![CDATA[Build applications of any size on Cloudflare with the Queues open beta]]></title>
            <link>https://blog.cloudflare.com/cloudflare-queues-open-beta/</link>
            <pubDate>Mon, 14 Nov 2022 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Queues enables developers to build performant and resilient distributed applications that span the globe. Any developer with a paid Workers plan can enroll in the Open Beta and start building today! ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Message queues are a fundamental building block of cloud applications—and today the <a href="https://developers.cloudflare.com/queues/">Cloudflare Queues</a> open beta brings queues to every developer building for Region: Earth. Cloudflare Queues follows <a href="https://www.cloudflare.com/products/workers/">Cloudflare Workers</a> and <a href="https://www.cloudflare.com/products/r2/">Cloudflare R2</a> in a long line of <a href="https://www.cloudflare.com/application-services/">innovative application services</a> built for the Workers Developer Platform, enabling developers to build more complex applications without configuring networks, choosing regions, or estimating capacity. Best of all, like many other Cloudflare services, there are no egregious egress charges!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7L0APU7xTJNF6ByoHcvXpY/581bfdcb37394d7e30ce5bad852b2c58/image8.png" />
            
            </figure><p>If you’ve ever purchased something online and seen a message like “you will receive confirmation of your order shortly,” you’ve interacted with a queue. When you completed your order, your shopping cart and information were stored and the order was placed into a queue. At some later point, the order fulfillment service picks and packs your items and hands it off to the shipping service—again, via a queue. Your order may sit for only a minute, or much longer if an item is out of stock or a warehouse is busy, and queues enable all of this functionality.</p><p>Message queues are great at decoupling components of applications, like the checkout and order fulfillment services for an <a href="https://www.cloudflare.com/ecommerce/">ecommerce site</a>. Decoupled services are easier to reason about, deploy, and implement, allowing you to ship features that delight your customers without worrying about synchronizing complex deployments.</p><p>Queues also allow you to batch and buffer calls to downstream services and <a href="https://www.cloudflare.com/learning/security/api/what-is-an-api/">APIs</a>. This post shows you how to enroll in the open beta, walks you through a practical example of using Queues to build a log sink, and tells you how we built Queues using other Cloudflare services. You’ll also learn a bit about the roadmap for the open beta.</p>
    <div>
      <h2>Getting started</h2>
      <a href="#getting-started">
        
      </a>
    </div>
    
    <div>
      <h3>Enrolling in the open beta</h3>
      <a href="#enrolling-in-the-open-beta">
        
      </a>
    </div>
    <p>Open the <a href="https://dash.cloudflare.com">Cloudflare dashboard</a> and navigate to the <i>Workers</i> section. Select <i>Queues</i> from the <i>Workers</i> navigation menu and choose <i>Enable Queues Beta</i>.</p><p>Review your order and choose <i>Proceed to Payment Details</i>.</p><p><b>Note: If you are not already subscribed to a</b> <a href="https://www.cloudflare.com/plans/developer-platform/#overview"><b>Workers Paid Plan</b></a><b>, one will be added to your order automatically.</b></p><p>Enter your payment details and choose <i>Complete Purchase</i>. That’s it - you’re enrolled in the open beta! Choose <i>Return to Queues</i> on the confirmation page to return to the Cloudflare Queues home page.</p>
    <div>
      <h3>Creating your first queue</h3>
      <a href="#creating-your-first-queue">
        
      </a>
    </div>
    <p>After enabling the open beta, open the Queues home page and choose <i>Create Queue</i>. Name your queue `my-first-queue` and choose <i>Create queue</i>. That’s all there is to it!</p><p>The dash displays a confirmation message along with a list of all the queues in your account.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/8jcg7ga1WIbqJplp8XRsP/cbc7cdf9420a4d51c714aa8b4b96bd80/image1-18.png" />
            
            </figure><p><b>Note: As of the writing of this blog post each account is limited to ten queues. We intend to raise this limit as we build towards general availability.</b></p>
    <div>
      <h3>Managing your queues with Wrangler</h3>
      <a href="#managing-your-queues-with-wrangler">
        
      </a>
    </div>
    <p>You can also manage your queues from the command line using <a href="https://github.com/cloudflare/wrangler2">Wrangler</a>, the CLI for Cloudflare Workers. In this section, you build a simple but complete application implementing a log aggregator or sink to learn how to integrate Workers, Queues, and R2.</p><p><b>Setting up resources</b>To create this application, you need access to a Cloudflare Workers account with a subscription plan, access to the Queues open beta, and an R2 plan.</p><p><a href="https://developers.cloudflare.com/workers/get-started/guide/#1-install-wrangler-workers-cli">Install</a> and <a href="https://developers.cloudflare.com/workers/get-started/guide/#2-authenticate-wrangler">authenticate</a> Wrangler then run <code>wrangler queues create log-sink</code> from the command line to create a queue for your application.</p><p>Run <code>wrangler queues list</code> and note that Wrangler displays your new queue.</p><p><b>Note: The following screenshots use the</b> <a href="https://stedolan.github.io/jq/"><b>jq</b></a> <b>utility to format the JSON output of wrangler commands. You</b> <b><i>do not</i></b> <b>need to install jq to complete this application.</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5DzA0apSCcSq7lDHPCaFwt/d44c7290077d4ddc9e3283614ea7bbb9/8-create-and-list-a-queue.png" />
            
            </figure><p>Finally, run <code>wrangler r2 bucket create log-sink</code> to create an R2 bucket to store your aggregated logs. After the bucket is created, run <code>wrangler r2 bucket list</code> to see your new bucket.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5QT0a9c73sO2l5pTUbROs9/ab6b9e7858c8f2e90149839c275c7aa6/9-create-and-list-a-bucket.png" />
            
            </figure><p><b>Creating your Worker</b>Next, create a Workers application with two handlers: a <code>fetch()</code> handler to receive individual incoming log lines and a <code>queue()</code> handler to aggregate a batch of logs and write the batch to R2.</p><p>In an empty directory, run <code>wrangler init</code> to create a new Cloudflare Workers application. When prompted:</p><ul><li><p>Choose “y” to create a new <i>package.json</i></p></li><li><p>Choose “y” to use TypeScript</p></li><li><p>Choose “Fetch handler” to create a new Worker at <i>src/index.ts</i></p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3PiZnMr0Q4S0fzsLDnHg2G/002b64768711ba224e8cb3a9618607d2/10-wrangler-init.png" />
            
            </figure><p>Open <i>wrangler.toml</i> and replace the contents with the following:</p>
    <div>
      <h4><i><b>wrangler.toml</b></i></h4>
      <a href="#wrangler-toml">
        
      </a>
    </div>
    
            <pre><code>name = "queues-open-beta"
main = "src/index.ts"
compatibility_date = "2022-11-03"
 
 
[[queues.producers]]
 queue = "log-sink"
 binding = "BUFFER"
 
[[queues.consumers]]
 queue = "log-sink"
 max_batch_size = 100
 max_batch_timeout = 30
 
[[r2_buckets]]
 bucket_name = "log-sink"
 binding = "LOG_BUCKET"</code></pre>
            <p>The <code>[[queues.producers]]</code> section creates a <i>producer</i> binding for the Worker at <i>src/index.ts</i> called <code>BUFFER</code> that refers to the <i>log-sink</i> queue. This Worker can place messages onto the <i>log-sink</i> queue by calling <code>await env.BUFFER.send(log);</code></p><p>The <code>[[queues.consumers]]</code> section creates a <i>consumer</i> binding for the <i>log-sink</i> queue for your Worker. Once the <i>log-sink</i> queue has a batch ready to be processed (or <i>consumed</i>), the Workers runtime will look for the <code>queue()</code> event handler in <i>src/index.ts</i> and invoke it, passing the batch as an argument. The <i>queue()</i> function signature looks as follows:</p><p><code>async queue(batch: MessageBatch&lt;Error&gt;, env: Environment): Promise&lt;void&gt; {</code></p><p>The final binding in your <i>wrangler.toml</i> creates a binding for the <i>log-sink</i> R2 bucket that makes the bucket available to your Worker via <i>env.LOG_BUCKET</i>.</p>
    <div>
      <h4><i><b>src/index.ts</b></i></h4>
      <a href="#src-index-ts">
        
      </a>
    </div>
    <p>Open <i>src/index.ts</i> and replace the contents with the following code:</p>
            <pre><code>export interface Env {
 BUFFER: Queue;
 LOG_BUCKET: R2Bucket;
}
 
export default {
 async fetch(request: Request, env: Environment): Promise&lt;Response&gt; {
   let log = await request.json();
   await env.BUFFER.send(log);
   return new Response("Success!");
 },
 async queue(batch: MessageBatch&lt;Error&gt;, env: Environment): Promise&lt;void&gt; {
   const logBatch = JSON.stringify(batch.messages);
   await env.LOG_BUCKET.put(`logs/${Date.now()}.log.json`, logBatch);
 },
};
</code></pre>
            <p>The <code>export interface Env</code> section exposes the two bindings you defined in <i>wrangler.toml</i>: a queue named <i>BUFFER</i> and an R2 bucket named <i>LOG_BUCKET</i>.</p><p>The <code>fetch()</code> handler transforms the request body into JSON, adds the body to the <i>BUFFER</i> queue, then returns an HTTP 200 response with the message <i>Success!</i></p><p>The `queue()` handler receives a batch of messages that each contain log entries, iterates through concatenating each log into a string buffer, then writes that buffer to the <i>LOG_BUCKET</i> R2 bucket using the current timestamp as the filename.</p><p><b>Publishing and running your application</b>To publish your log sink application, run <code>wrangler publish</code>. Wrangler packages your application and its dependencies and deploys it to Cloudflare’s global network.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/131yCNzIgg4qaU5LKSFrcu/eddd596bcf02e369163e057abde27944/11-wrangler-publish.png" />
            
            </figure><p>Note that the output of <code>wrangler publish</code> includes the <i>BUFFER</i> queue binding, indicating that this Worker is a <i>producer</i> and can place messages onto the queue. The final line of output also indicates that this Worker is a <i>consumer</i> for the <i>log-sink</i> queue and can read and remove messages from the queue.</p><p>Use your favorite API client, like <a href="https://curl.se/">curl</a>, <a href="https://httpie.io/">httpie</a>, or <a href="https://www.postman.com/">Postman</a>, to send JSON log entries to the published URL for your Worker via HTTP POST requests. Navigate to your <i>log-sink</i> R2 bucket in the <a href="https://dash.cloudflare.com">Cloudflare dashboard</a> and note that the <i>logs</i> prefix is now populated with aggregated logs from your request.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6W4jmz2ezkbUwrs0uZi4xu/a8f2f124feb5c9a121c38fbfb8a1d1df/image4-9.png" />
            
            </figure><p>Download and open one of the logfiles to view the JSON array inside. That’s it - with fewer than 45 lines of code and config, you’ve built a log aggregator to ingest and store data in R2!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2dw6XLyx7REjzje0Y0i7as/f154e7ed56152651c2f664b0dec9cebe/13-aggregated-log-content.png" />
            
            </figure>
    <div>
      <h2>Buffering R2 writes with Queues in the real world</h2>
      <a href="#buffering-r2-writes-with-queues-in-the-real-world">
        
      </a>
    </div>
    <p>In the previous example, you create a simple Workers application that buffers data into batches before writing the batches to R2. This reduces the number of calls to the downstream service, reducing load on the service and saving you money.</p><p><a href="https://uuid.rocks/">UUID.rocks</a>, the fastest UUIDv4-as-a-service, wanted to confirm whether their API truly generates unique IDs on every request. With 80,000 requests per day, it wasn’t trivial to find out. They decided to write every generated UUID to R2 to compare IDs across the entire population. However, writing directly to R2 at the rate UUIDs are generated is inefficient and expensive.</p><p>To reduce writes and costs, UUID.rocks introduced Cloudflare Queues into their UUID generation workflow. Each time a UUID is requested, a Worker places the value of the UUID into a queue. Once enough messages have been received, the buffered batch of JSON objects is written to R2. This avoids invoking an R2 write on every API call, saving costs and making the data easier to process later.</p><p>The <a href="https://github.com/jahands/uuid-queue">uuid-queue application</a> consists of a single Worker with three event handlers:</p><ol><li><p>A fetch handler that receives a JSON object representing the generated UUID and writes it to a Cloudflare Queue.</p></li><li><p>A queue handler that writes batches of JSON objects to R2 in CSV format.</p></li><li><p>A scheduled handler that combines batches from the previous hour into a single file for future processing.</p></li></ol><p>To view the source or deploy this application into your own account, <a href="https://github.com/jahands/uuid-queue">visit the repository on GitHub</a>.</p>
    <div>
      <h2>How we built Cloudflare Queues</h2>
      <a href="#how-we-built-cloudflare-queues">
        
      </a>
    </div>
    <p>Like many of the Cloudflare services you use and love, we built Queues by composing other Cloudflare services like Workers and Durable Objects. This enabled us to rapidly solve two difficult challenges: securely invoking your Worker from our own service and maintaining a strongly consistent state at scale. Several recent Cloudflare innovations helped us overcome these challenges.</p>
    <div>
      <h3>Securely invoking your Worker</h3>
      <a href="#securely-invoking-your-worker">
        
      </a>
    </div>
    <p>In the <i>Before Times</i> (early 2022), invoking one Worker from another Worker meant a fresh HTTP call from inside your script. This was a brittle experience, requiring you to know your downstream endpoint at deployment time. Nested invocations ran as HTTP calls, passing all the way through the Cloudflare network a second time and adding latency to your request. It also meant security was on you - if you wanted to control how that second Worker was invoked, you had to create and implement your own authentication and authorization scheme.</p><p><b>Worker to Worker requests</b>During Platform Week in May 2022, <a href="/service-bindings-ga/">Service Worker Bindings</a> entered general availability. With Service Worker Bindings, your Worker code has a binding to another Worker in your account that you invoke directly, avoiding the network penalty of a nested HTTP call. This removes the performance and security barriers discussed previously, but it still requires that you hard-code your nested Worker at compile time. You can think of this setup as “static dispatch,” where your Worker has a static reference to another Worker where it can dispatch events.</p><p><b>Dynamic dispatch</b>As Service Worker Bindings entered general availability, we also <a href="/workers-for-platforms-ga/">launched a closed beta</a> of Workers for Platforms, our tool suite to help make any product programmable. With Workers for Platforms, software as a service (SaaS) and platform providers can allow users to upload their own scripts and run them safely via Cloudflare Workers. User scripts are not known at compile time, but are <i>dynamically dispatched</i> at runtime.</p><p><a href="/workers-for-platforms-ga/">Workers for Platforms entered</a> general availability during <a href="https://www.cloudflare.com/ga-week/">GA week</a> in September 2022, and is available for all customers to build with today.</p><p>With dynamic dispatch generally available, we now have the ability to discover and invoke Workers at runtime without the performance penalty of HTTP traffic over the network. We use dynamic dispatch to invoke your queue’s consumer Worker whenever a message or batch of messages is ready to be processed.</p>
    <div>
      <h3>Consistent stateful data with Durable Objects</h3>
      <a href="#consistent-stateful-data-with-durable-objects">
        
      </a>
    </div>
    <p>Another challenge we faced was storing messages durably without sacrificing performance. We took the design goal of ensuring that all messages were persisted to disk in multiple locations before we confirmed receipt of the message to the user. Again, we turned to an existing Cloudflare product—<a href="https://developers.cloudflare.com/workers/learning/using-durable-objects/">Durable Objects</a>—which <a href="/durable-objects-ga/">entered general availability nearly one year ago today</a>.</p><p>Durable Objects are named instances of JavaScript classes that are guaranteed to be unique across Cloudflare’s entire network. Durable Objects process messages in-order and on a single-thread, allowing for coordination across messages and provide a strongly consistent storage API for key-value pairs. Offloading the hard problem of storing data durably in a distributed environment to Distributed Objects allowed us to reduce the time to build Queues and prepare it for open beta.</p>
    <div>
      <h2>Open beta roadmap</h2>
      <a href="#open-beta-roadmap">
        
      </a>
    </div>
    <p>Our open beta process empowers you to influence feature prioritization and delivery. We’ve set ambitious goals for ourselves on the path to general availability, most notably supporting unlimited throughput while maintaining 100% durability. We also have many other great features planned, like first-in first-out (FIFO) message processing and API compatibility layers to ease migrations, but we need your feedback to build what you need most, first.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Cloudflare Queues is a global message queue for the Workers developer. Building with Queues makes your applications more performant, resilient, and cost-effective—but we’re not done yet. <a href="https://dash.cloudflare.com">Join the Open Beta today</a> and <a href="https://discord.gg/aTsevRH3pG">share your feedback</a> to help shape the Queues roadmap as we deliver application integration services for the next generation cloud.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Queues]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">44r8mGfFRsG9yOGC0CDQnu</guid>
            <dc:creator>Rob Sutter</dc:creator>
        </item>
        <item>
            <title><![CDATA[Store and retrieve your logs on R2]]></title>
            <link>https://blog.cloudflare.com/store-and-retrieve-logs-on-r2/</link>
            <pubDate>Wed, 21 Sep 2022 14:15:00 GMT</pubDate>
            <description><![CDATA[ Log Storage on R2: a cost-effective solution to store event logs for any of our products ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Following today’s announcement of General Availability of Cloudflare R2 <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a>, we’re excited to announce that customers can also store and retrieve their logs on R2.</p><p>Cloudflare’s Logging and Analytics products provide vital insights into customers’ applications. Though we have a breadth of capabilities, logs in particular play a pivotal role in understanding what occurs at a granular level; we produce detailed logs containing metadata generated by Cloudflare products via events flowing through our network, and they are depended upon to illustrate or investigate anything (and everything) from the general performance or health of applications to closely examining security incidents.</p><p>Until today, we have only provided customers with the ability to export logs to 3rd-party destinations - to both store and perform analysis. However, with Log Storage on R2 we are able to offer customers a cost-effective solution to store event logs for any of our products.</p>
    <div>
      <h3>The cost conundrum</h3>
      <a href="#the-cost-conundrum">
        
      </a>
    </div>
    <p>We’ve <a href="/logs-r2/">unpacked the commercial impact in a previous blog post,</a> but to recap, the <a href="https://r2-calculator.cloudflare.com/">cost of storage can vary broadly</a> depending on the volume of requests Internet properties receive. On top of that - and specifically pertaining to logs - there’s usually more expensive fees to access that data whenever the need arises. This can be incredibly problematic, especially when customers are having to balance their budget with the need to access their logs - whether it's to mitigate a potential catastrophe or just out of curiosity.</p><p>With R2, not only do we not charge customers <a href="https://developers.cloudflare.com/r2/platform/pricing/">egress costs</a>, but we also provide the opportunity to make further operational savings by centralizing storage and retrieval. Though, most of all, we just want to make it easy and convenient for customers to access their logs via our Retrieval API - all you need to do is provide a time range!</p>
    <div>
      <h3>Logs on R2: get started!</h3>
      <a href="#logs-on-r2-get-started">
        
      </a>
    </div>
    <p>Why would you want to store your logs on <a href="#">Cloudflare R2</a>? First, R2 is S3 API compatible, so your existing tooling will continue to work as is. Second, not only is R2 cost-effective for storage, we also do not charge any egress fees if you want to get your logs out of Cloudflare to be ingested into your own systems. You can store logs for any Cloudflare product, and you can also store what you need for as long as you need; retention is completely within your control.</p>
    <div>
      <h3>Storing Logs on R2</h3>
      <a href="#storing-logs-on-r2">
        
      </a>
    </div>
    <p>To create Logpush jobs pushing to R2, you can use either the dashboard or Cloudflare API. Using the dashboard, you can create a job and select R2 as the destination during configuration:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4LxlofboVHwn12lpAJ3iok/d8a69475331cf09feae68d1523b5dd76/image2-26.png" />
            
            </figure><p>To use the Cloudflare API to create the job, do something like:</p>
            <pre><code>curl -s -X POST 'https://api.cloudflare.com/client/v4/zones/&lt;ZONE_ID&gt;/logpush/jobs' \
-H "X-Auth-Email: &lt;EMAIL&gt;" \
-H "X-Auth-Key: &lt;API_KEY&gt;" \
-d '{
 "name":"&lt;DOMAIN_NAME&gt;",
"destination_conf":"r2://&lt;BUCKET_PATH&gt;/{DATE}?account-id=&lt;ACCOUNT_ID&gt;&amp;access-key-id=&lt;R2_ACCESS_KEY_ID&gt;&amp;secret-access-key=&lt;R2_SECRET_ACCESS_KEY&gt;",
 "dataset": "http_requests",
"logpull_options":"fields=ClientIP,ClientRequestHost,ClientRequestMethod,ClientRequestURI,EdgeEndTimestamp,EdgeResponseBytes,EdgeResponseStatus,EdgeStartTimestamp,RayID&amp;timestamps=rfc3339",
 "kind":"edge"
}' | jq .</code></pre>
            <p>Please see <a href="https://developers.cloudflare.com/logs/get-started/enable-destinations/r2/">Logpush over R2</a> docs for more information.</p>
    <div>
      <h3>Log Retrieval on R2</h3>
      <a href="#log-retrieval-on-r2">
        
      </a>
    </div>
    <p>If you have your logs pushed to R2, you could use the Cloudflare API to retrieve logs in specific time ranges like the following:</p>
            <pre><code>curl -s -g -X GET 'https://api.cloudflare.com/client/v4/accounts/&lt;ACCOUNT_ID&gt;/logs/retrieve?start=2022-09-25T16:00:00Z&amp;end=2022-09-25T16:05:00Z&amp;bucket=&lt;YOUR_BUCKET&gt;&amp;prefix=&lt;YOUR_FILE_PREFIX&gt;/{DATE}' \
-H "X-Auth-Email: &lt;EMAIL&gt;" \
-H "X-Auth-Key: &lt;API_KEY&gt;" \ 
-H "R2-Access-Key-Id: R2_ACCESS_KEY_ID" \
-H "R2-Secret-Access-Key: R2_SECRET_ACCESS_KEY" | jq .</code></pre>
            <p>See <a href="https://developers.cloudflare.com/logs/r2-log-retrieval/">Log Retrieval API</a> for more details.</p><p>Now that you have critical logging infrastructure on Cloudflare, you probably want to be able to monitor the health of these Logpush jobs as well as get relevant alerts when something needs your attention.</p>
    <div>
      <h3>Looking forward</h3>
      <a href="#looking-forward">
        
      </a>
    </div>
    <p>While we have a vision to build out log analysis and forensics capabilities on top of R2 - and a roadmap to get us there - we’d still love to hear your thoughts on any improvements we can make, particularly to our retrieval options.</p><p>Get setup on <a href="https://www.cloudflare.com/products/r2/">R2</a> to start <a href="https://developers.cloudflare.com/logs/get-started/enable-destinations/r2/">pushing logs</a> today! If your current plan doesn’t include Logpush, storing logs on R2 is another great reason to upgrade!</p> ]]></content:encoded>
            <category><![CDATA[GA Week]]></category>
            <category><![CDATA[General Availability]]></category>
            <category><![CDATA[Logs]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Storage]]></category>
            <guid isPermaLink="false">1jAMjJ9M01pYKP2KUM2EVn</guid>
            <dc:creator>Shelley Jones</dc:creator>
            <dc:creator>Duc Nguyen</dc:creator>
        </item>
        <item>
            <title><![CDATA[Using Cloudflare R2 as an apt/yum repository]]></title>
            <link>https://blog.cloudflare.com/using-cloudflare-r2-as-an-apt-yum-repository/</link>
            <pubDate>Thu, 15 Sep 2022 13:00:00 GMT</pubDate>
            <description><![CDATA[ Host your apt/yum repositories like how Cloudflare Tunnel does. Here's how ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/sQTPP1Uhcxo0gXUo6NIKI/dc6d5bfe03b47d171a7f56edefa763b6/R2-as-an-apt-yum-repository.png" />
            
            </figure><p>In this blog post, we’re going to talk about how we use Cloudflare R2 as an <i>apt/yum</i> repository to bring <i>cloudflared</i> (the Cloudflare Tunnel daemon) to your Debian/Ubuntu and CentOS/RHEL systems and how you can do it for your own distributable in a few easy steps!</p><p>I work on <a href="https://www.cloudflare.com/en-gb/products/tunnel/"><i>Cloudflare Tunnel</i></a>, a product which enables customers to quickly connect their private networks and services through the Cloudflare global network without needing to expose any public IPs or ports through their firewall. Cloudflare Tunnel is managed for users by <i>cloudflared</i>, a tool that runs on the same network as the private services. It proxies traffic for these services via Cloudflare, and users can then access these services securely through the Cloudflare network.</p><p>Our connector, <i>cloudflared,</i> was designed to be lightweight and flexible enough to be effectively deployed on a Raspberry Pi, a router, your laptop, or a server running on a data center with applications ranging from IoT control to private networking. Naturally, this means <i>cloudflared</i> comes built for a myriad of operating systems, architectures and package distributions: You could download the appropriate package from our <a href="https://github.com/cloudflare/cloudflared/releases">GitHub releases</a>, <i>brew install</i> it or <i>apt/yum install</i> it (<a href="https://pkg.cloudflare.com">https://pkg.cloudflare.com</a>).</p><p>In the rest of this blog post, I’ll use cloudflared as an example of how to create an apt/yum repository backed by Cloudflare’s <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a> service R2. Note that this can be any binary/distributable. I simply use cloudflared as an example because this is something we recently did and actually use in production.</p>
    <div>
      <h2>How apt-get works</h2>
      <a href="#how-apt-get-works">
        
      </a>
    </div>
    <p>Let’s see what happens when you run something like this on your terminal.</p>
            <pre><code>$ apt-get install cloudflared</code></pre>
            <p>Let’s also assume that apt sources were already added like so:</p>
            <pre><code>  $ echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared buster main' | sudo tee /etc/apt/sources.list.d/cloudflared.list


$ apt-get update</code></pre>
            <p>From the source.list above, apt first looks up the <a href="https://pkg.cloudflare.com/cloudflared/dists/buster/Release">Release</a> file (or <a href="https://pkg.cloudflare.com/cloudflared/dists/buster/InRelease">InRelease</a> if it’s a signed package like cloudflared, but we will ignore this for the sake of simplicity).</p><p>A Release file contains a list of supported architectures and their md5, sha1 and sha256 checksums. It looks something like this:</p>
            <pre><code>$ curl https://pkg.cloudflare.com/cloudflared/dists/buster/Release
Origin: cloudflared
Label: cloudflared
Codename: buster
Date: Thu, 11 Aug 2022 08:40:18 UTC
Architectures: amd64 386 arm64 arm armhf
Components: main
Description: apt repository for cloudflared - buster
MD5Sum:
 c14a4a1cbe9437d6575ae788008a1ef4 549 main/binary-amd64/Packages
 6165bff172dd91fa658ca17a9556f3c8 374 main/binary-amd64/Packages.gz
 9cd622402eabed0b1b83f086976a8e01 128 main/binary-amd64/Release
 5d2929c46648ea8dbeb1c5f695d2ef6b 545 main/binary-386/Packages
 7419d40e4c22feb19937dce49b0b5a3d 371 main/binary-386/Packages.gz
 1770db5634dddaea0a5fedb4b078e7ef 126 main/binary-386/Release
 b0f5ccfe3c3acee38ba058d9d78a8f5f 549 main/binary-arm64/Packages
 48ba719b3b7127de21807f0dfc02cc19 376 main/binary-arm64/Packages.gz
 4f95fe1d9afd0124a32923ddb9187104 128 main/binary-arm64/Release
 8c50559a267962a7da631f000afc6e20 545 main/binary-arm/Packages
 4baed71e49ae3a5d895822837bead606 372 main/binary-arm/Packages.gz
 e472c3517a0091b30ab27926587c92f9 126 main/binary-arm/Release
 bb6d18be81e52e57bc3b729cbc80c1b5 549 main/binary-armhf/Packages
 31fd71fec8acc969a6128ac1489bd8ee 371 main/binary-armhf/Packages.gz
 8fbe2ff17eb40eacd64a82c46114d9e4 128 main/binary-armhf/Release
SHA1:
…
SHA256:
…</code></pre>
            <p>Depending on your system’s architecture, Debian picks the appropriate Packages location. A Packages file contains metadata about the binary apt wants to install, including location and its checksum. Let’s say it’s an amd64 machine. This means we’ll go here next:</p>
            <pre><code>$ curl https://pkg.cloudflare.com/cloudflared/dists/buster/main/binary-amd64/Packages
Package: cloudflared
Version: 2022.8.0
License: Apache License Version 2.0
Vendor: Cloudflare
Architecture: amd64
Maintainer: Cloudflare &lt;support@cloudflare.com&gt;
Installed-Size: 30736
Homepage: https://github.com/cloudflare/cloudflared
Priority: extra
Section: default
Filename: pool/main/c/cloudflared/cloudflared_2022.8.0_amd64.deb
Size: 15286808
SHA256: c47ca10a3c60ccbc34aa5750ad49f9207f855032eb1034a4de2d26916258ccc3
SHA1: 1655dd22fb069b8438b88b24cb2a80d03e31baea
MD5sum: 3aca53ccf2f9b2f584f066080557c01e
Description: Cloudflare Tunnel daemon</code></pre>
            <p>Notice the Filename field. This is where apt gets the deb from before running a dpkg command on it. What all of this means is the apt repository (and the yum repositories too) is basically a structured file system of mostly plaintext files and a deb.</p><p>The deb file is a Debian software package that contains two things: installable data (in our case, the <i>cloudflared</i> binary) and metadata about the installable.</p>
    <div>
      <h2>Building our own apt repository</h2>
      <a href="#building-our-own-apt-repository">
        
      </a>
    </div>
    <p>Now that we know what happens when an apt-get install runs, let’s work our way backwards to construct the repository.</p>
    <div>
      <h3>Create a deb file out of the binary</h3>
      <a href="#create-a-deb-file-out-of-the-binary">
        
      </a>
    </div>
    <p><b>Note:</b> It is optional but recommended one signs the packages. See the section about how apt verifies packages <a href="/dont-use-apt-key/">here</a><i>.</i></p><p>Debian files can be built by the <a href="https://man7.org/linux/man-pages/man1/dpkg-buildpackage.1.html">dpkg-buildpackage</a> tool in a linux or Debian environment. We use a handy command line tool called fpm (<a href="https://fpm.readthedocs.io/en/v1.13.1/">https://fpm.readthedocs.io/en/v1.13.1/</a>) to do this because it works for both rpm and deb.</p>
            <pre><code>$ fpm -s &lt;dir&gt; -t deb -C /path/to/project -name &lt;project_name&gt; –version &lt;version&gt;</code></pre>
            <p>This yields a .deb file.</p>
    <div>
      <h3>Create plaintext files needed by apt to lookup downloads</h3>
      <a href="#create-plaintext-files-needed-by-apt-to-lookup-downloads">
        
      </a>
    </div>
    <p>Again, these files can be built by hand, but there are multiple <a href="https://wiki.debian.org/DebianRepository/Setup?action=show&amp;redirect=HowToSetupADebianRepository#Debian_Repository_Generation_Tools.">tools</a> available to generate this:</p><p>We use <a href="https://wiki.debian.org/DebianRepository/Setup?action=show&amp;redirect=HowToSetupADebianRepository#reprepro.">reprepro</a>.</p><p>Using it is as simple as</p>
            <pre><code>$ reprepro buster includedeb &lt;path/to/the/deb&gt;</code></pre>
            <p>reprepro neatly creates a bunch of folders in the structure we looked into above.</p>
    <div>
      <h3>Upload them to Cloudflare R2</h3>
      <a href="#upload-them-to-cloudflare-r2">
        
      </a>
    </div>
    <p>We use Cloudflare R2 to now be the host for this structured file system. R2 lets us upload and serve objects in this structured format. This means, we just need to upload the files in the same structure reprepro created them.</p><p><a href="https://github.com/cloudflare/cloudflared/blob/135c8e6d13663d2aa2d3c9169cde0cfc1e6e2062/release_pkgs.py#L36">Here</a> is a copyable example of how we do it for cloudflared.</p>
    <div>
      <h3>Serve them from an R2 worker</h3>
      <a href="#serve-them-from-an-r2-worker">
        
      </a>
    </div>
    <p>For fine-grained control, we’ll write a very lightweight Cloudflare Worker to be the service we talk to and serve as the front-end API for apt to talk to. For an apt repository, we only need it to perform the GET function.</p><p>Here’s an example on how-to do this: <a href="https://developers.cloudflare.com/r2/examples/demo-worker/">https://developers.cloudflare.com/r2/examples/demo-worker/</a></p>
    <div>
      <h2>Putting it all together</h2>
      <a href="#putting-it-all-together">
        
      </a>
    </div>
    <p><a href="https://github.com/cloudflare/cloudflared/blob/master/release_pkgs.py">Here</a> is a handy script we use to push cloudflared to R2 ready for apt/yum downloads and includes signing and publishing the pubkey.</p><p>And that’s it! You now have your own apt/yum repo without needing a server that needs to be maintained, there are no egress fees for downloads, and it is on the Cloudflare global network, protecting it against high request volumes. You can automate many of these steps to make it part of a release process.</p><p>Today, this is how cloudflared is distributed on the apt and yum repositories: <a href="https://pkg.cloudflare.com/">https://pkg.cloudflare.com/</a></p> ]]></content:encoded>
            <category><![CDATA[undefined]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Tunnel]]></category>
            <guid isPermaLink="false">1wS0SQnobVypSO6YoT2RnL</guid>
            <dc:creator>Sudarsan Reddy</dc:creator>
        </item>
        <item>
            <title><![CDATA[A New Hope for Object Storage: R2 enters open beta]]></title>
            <link>https://blog.cloudflare.com/r2-open-beta/</link>
            <pubDate>Wed, 11 May 2022 13:01:00 GMT</pubDate>
            <description><![CDATA[ R2 Object Storage is now in open beta, available for all customers to test and deploy! ]]></description>
            <content:encoded><![CDATA[ <p></p><p>In September, we <a href="/introducing-r2-object-storage/">announced</a> that we were building our own <a href="https://www.cloudflare.com/developer-platform/products/r2/">object storage solution: Cloudflare R2</a>. R2 is our answer to egregious <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">egress charges</a> from incumbent cloud providers, letting developers store as much data as they want without worrying about the cost of accessing that data.</p><p>The response has been overwhelming.</p><ul><li><p><b>Independent developers</b> had bills too small for cloud providers to negotiate fair egress rates with them. Egress charges were the largest line-item on their cloud bills, strangling side projects and the new businesses they were building.</p></li><li><p><b>Large corporations</b> had written off multi-cloud storage - and thus multi-cloud itself - as a pipe dream. They came to us with excitement, pitching new products that integrated data with partner companies.</p></li><li><p><b>Non-profit research organizations</b> were paying massive egress fees just to share experiment data with one another. Egress fees were having a real impact on their ability to collaborate, driving silos between organizations and restricting the experiments and analyses they could run.</p></li></ul><p>Cloudflare exists to help build a better Internet. Today, the Internet gets what it deserves: R2 is now in <a href="https://developers.cloudflare.com/r2/get-started/">open beta</a>.</p><p>Self-serve customers can <a href="https://dash.cloudflare.com/?to=/:account/r2/plans">enable R2</a> in the Cloudflare dashboard. Enterprise accounts can reach out to their CSM for onboarding.</p>
    <div>
      <h2>Internal and external APIs</h2>
      <a href="#internal-and-external-apis">
        
      </a>
    </div>
    <p>R2 has two APIs: an API accessible only from within Workers, which we call the In-Worker API, and an S3-compatible API, which exposes your bucket on a URL of the form <a href="http://account.r2.cloudflarestorage.com/bucket">bucket.account.r2storage.com</a>. Before you can make requests to R2, you’ll need to be authenticated — R2 buckets are private by default.</p>
    <div>
      <h3>In-Worker API</h3>
      <a href="#in-worker-api">
        
      </a>
    </div>
    <p>With the in-Worker API, a bucket is “bound” to a specific Worker, which can then perform PUT, GET, DELETE and LIST operations against the bucket.</p>
    <div>
      <h3>S3-compatible API</h3>
      <a href="#s3-compatible-api">
        
      </a>
    </div>
    <p>For the S3-compatible API, authentication is done the same way as on S3: SigV4 against an R2 URL. SigV4 signs requests using a secret key to authenticate them to R2. This means public access to R2 over the Internet is only possible today by hosting a Worker, connecting it to R2, and routing requests through it.</p><p>The easiest way to test the S3-compatible API is to use an S3 client. One of the most popular S3 clients is the <a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html">boto3</a> SDK.</p><p>In Python, copy the following script and fill in the <code>account_id</code>, <code>access_key</code>, and <code>secret_access_key</code> fields with your R2 account credentials.</p>
            <pre><code>main.py
import boto3

s3 = boto3.resource('s3',
  endpoint_url = 'https://&lt;accountid&gt;.r2.cloudflarestorage.com',
  aws_access_key_id = '&lt;access_key_id&gt;',
  aws_secret_access_key = '&lt;access_key_secret&gt;'
)

print('Buckets:')
for bucket in s3.buckets.all():
  print(' - ', bucket.name)

bucket = s3.Bucket('my-bucket-name')

print('Objects:')
for item in bucket.objects.all():
  print(' - ', item.key)
</code></pre>
            
    <div>
      <h3>Features</h3>
      <a href="#features">
        
      </a>
    </div>
    <p>R2 comes with support for all basic create/read/update/delete S3 features through both of its APIs.</p><p>During the open beta period, we’re targeting R2 to sustain 1,000 GET operations per second and 100 PUT operations per second, per bucket. R2 supports objects up to approximately 5 TB in size, with individual parts limited to 5 GB of data.</p><p>R2 provides strongly consistent access to data. Once a PUT is confirmed by R2, future GET operations will always reflect the new key/value pair. The only exception to this is when deleting a bucket. For a short period of time following deletion, the bucket may still exist and continue to allow reads/writes.</p>
    <div>
      <h2>Pricing</h2>
      <a href="#pricing">
        
      </a>
    </div>
    <p>When we initially announced R2, we included preliminary pricing numbers. One of our main goals with R2 has been to serve the developers who can’t negotiate large discounts with cloud vendors. To that end, we’re also announcing a forever-free tier that lets developers start building on R2 with no charges at all.</p><p><a href="https://r2-calculator.cloudflare.com/">R2 charges</a> depend on the total volume of data stored and the type of operation performed on the data:</p><ul><li><p>Storage is priced at \$0.015 / GB, per month.</p></li><li><p>Class A operations (including writes and lists) cost \$4.50 / million.</p></li><li><p>Class B operations cost \$0.36 / million.</p></li></ul><p>Class A operations tend to mutate state, such as creating a bucket, listing objects in a bucket, or writing an object. Class B operations tend to read existing state, for example reading an object from a bucket. You can find more information on pricing and a full list of operation types in the <a href="https://developers.cloudflare.com/r2/platform/pricing/">docs</a>.</p><p>Of course, there is no charge for egress bandwidth from R2. You can access your bucket to your heart’s content.</p><p>R2’s forever-free tier includes:</p><ul><li><p>10 GB-months of stored data</p></li><li><p>1,000,000 Class A operations, per month</p></li><li><p>10,000,000 Class B operations, per month</p></li></ul><p>Free usage resets each month. While in the open beta phase, R2 usage over the free tier will be billed.</p>
    <div>
      <h2>Future plans</h2>
      <a href="#future-plans">
        
      </a>
    </div>
    <p>We’ve spent the past six months in closed beta with a number of design partners, building out our storage solution. Backed by Durable Objects, R2’s novel architecture delivers both high availability and consistent performance.</p><p>While we’ve made great progress on R2, we still have plenty left to build in the coming months.</p>
    <div>
      <h3>Improving performance</h3>
      <a href="#improving-performance">
        
      </a>
    </div>
    <p>Our first priority is to <b>improve performance and reliability</b>. While we’ve thrown internal usage and our design partner’s demands at R2, there’s no substitute for live production traffic.</p><p>During the open beta period, R2 can sustain a maximum of 1,000 GET operations per second and 100 PUT operations per second, per bucket. We’ll look to raise these limits as we get comfortable operating the system. If you have higher needs, reach out to us!</p><p>When you create a bucket, you won’t see a region selector. Our vision for R2 includes automatically globally distributed storage, where R2 seamlessly places each object into the storage region closest to where the request comes from. Today, R2 primarily stores data in North America, which can lead to higher latencies when accessing content from other regions. We’ll first look to address this by adding additional regions where objects can be created, before adding automatic migration of existing objects across regions. Similar to what we’ve built with <a href="/supporting-jurisdictional-restrictions-for-durable-objects/">jurisdictional restrictions for Durable Objects</a>, we’ll also enable restricting where an R2 bucket places data to comply with privacy regulations.</p>
    <div>
      <h3>Expanding R2’s feature set</h3>
      <a href="#expanding-r2s-feature-set">
        
      </a>
    </div>
    <p>We’ll then focus on expanding R2 capabilities beyond the basic S3 API. In the near term, we’re focused on delivering:</p><ul><li><p>Support for TTLs, so data can automatically be deleted from buckets over time.</p></li><li><p>Public buckets, so a bucket can be exposed to the internet without writing a Worker</p></li><li><p>Pre-signed URL support, which delegates read and write access for a specific key to a token.</p></li><li><p>Integration with <a href="https://www.cloudflare.com/cdn/">Cloudflare’s cache</a>, to scale read requests and provide global distribution of data.</p></li></ul><p>If you have additional feature requests that aren’t listed above, we want to hear from you! Join our <a href="https://discord.gg/cloudflaredev">Discord</a> and share what you need to make R2 your new, zero-cost egress <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object store</a> in the r2-open-beta channel.</p> ]]></content:encoded>
            <category><![CDATA[Platform Week]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Developers]]></category>
            <guid isPermaLink="false">5fCtFTYh1FJq6paCxwp296</guid>
            <dc:creator>Greg McKeon</dc:creator>
        </item>
        <item>
            <title><![CDATA[Store your Cloudflare logs on R2]]></title>
            <link>https://blog.cloudflare.com/store-your-cloudflare-logs-on-r2/</link>
            <pubDate>Tue, 07 Dec 2021 13:59:42 GMT</pubDate>
            <description><![CDATA[ We're excited to announce that customers will soon be able to store their Cloudflare logs on Cloudflare R2 storage. Storing your logs on Cloudflare will give CIOs and Security Teams an opportunity to consolidate their infrastructure; creating simplicity, savings and additional security. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6q2ecJTQQuYwhfby8PlIwY/8541c695f397ad88ff1534777cdf382a/unnamed-4.png" />
            
            </figure><p>We're excited to announce that customers will soon be able to store their Cloudflare logs on <a href="#">Cloudflare R2 storage</a>. Storing your logs on Cloudflare will give CIOs and Security Teams an opportunity to <a href="https://www.cloudflare.com/cio/">consolidate their infrastructure</a>; creating simplicity, savings and additional security.</p><p>Cloudflare protects your applications from malicious traffic, speeds up connections, and keeps bad actors out of your network. The logs we produce from our products help customers answer questions like:</p><ul><li><p>Why are requests being blocked by the Firewall rules I’ve set up?</p></li><li><p>Why are my users seeing disconnects from my applications that use Spectrum?</p></li><li><p>Why am I seeing a spike in Cloudflare Gateway requests to a specific application?</p></li></ul><p>Storage on R2 adds to our existing suite of logging products. Storing logs on R2 fills in gaps that our customers have been asking for: a cost-effective solution to store logs for any of our products for any period of time.</p>
    <div>
      <h3>Goodbye to old school logging</h3>
      <a href="#goodbye-to-old-school-logging">
        
      </a>
    </div>
    <p>Let’s rewind to the early 2000s. Most organizations were running their own self-managed infrastructure: network devices, firewalls, servers and all the associated software. Each company has to manage logs coming from hundreds of sources in the IT stack. With dedicated storage needed for retaining an endless volume of logs, specialized teams are required to build an ETL pipeline and make the data actionable.</p><p>Fast-forward to the 2010s. Organizations are transitioning to using managed services for their IT functions. As a result of this shift, the way that customers collect logs for all their services have changed too. With managed services, much of the logging load is shifted off of the customer.</p><p>The challenge now: collecting logs from a combination of managed services, each of which has its own quirks. Logs can be sent at varying latencies, in different formats and some are too detailed while others not detailed enough. To gain a single pane view of their IT infrastructure, companies need to build or buy a <a href="https://www.cloudflare.com/learning/security/what-is-siem/">SIEM</a> solution.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/PCMvAvpkvBostvlml5Rld/ed0c88237d8748b192f30379bc3cbed1/Log-storage-diagram.png" />
            
            </figure><p>Logging changes over the years</p><p>Cloudflare replaces these sets of managed services. When a customer onboards to Cloudflare, we make it super easy to gain visibility to their traffic that hits our network. We’ve built analytics for many of our products, such as CDN, Firewall, Magic Transit and Spectrum to both view high level trends and dig into patterns by slicing and dicing data.</p><p>Analytics are a great way to see data at an aggregate level, but we know that raw logs are important to our customers as well, so we’ve built out a set of logging products.</p>
    <div>
      <h3>Logging today</h3>
      <a href="#logging-today">
        
      </a>
    </div>
    <p>During <a href="/instant-logs/">Speed Week</a> we announced Instant Logs to show customers traffic as it hits their domain. Instant Logs is perfect for live debugging and triaging use cases. Monitor your traffic, make a config change and instantly view its impacts. In cases where you need to retroactively inspect your logs, we have Logpush.</p><p>We’ve built an impressive logging pipeline to get data from the 250+ cities that house our data centers to our customers in under a minute using Logpush. If your organization has existing practices for getting data across your stack into one place, we support Logpush to a variety of cloud storage or SIEM destinations. We also have partnerships in place with major SIEM platforms to surface Cloudflare data in ways that are meaningful to our customers.</p><p>Last but not least is Logpull. Using Logpull, customers can access HTTP request logs using our REST API. Our customers like Logpull because it's easy to configure, they don’t have to worry about storing logs on a third party, and you can pull data ad hoc for up to seven days.</p>
    <div>
      <h3>Why Cloudflare storage?</h3>
      <a href="#why-cloudflare-storage">
        
      </a>
    </div>
    <p>The top four requests we’ve heard from customers when it comes to logs are:</p><ul><li><p>I have tight budgets and need low cost log storage.</p></li><li><p>They should be low effort to set up and maintain.</p></li><li><p>I should be able to store logs for as long as I need to.</p></li><li><p>I want to access my logs on Cloudflare for any product.</p></li></ul><p>For many of our customers, Cloudflare is one of the most important data sources, and it also generates more data than other applications on their IT stack. R2 is significantly cheaper than other cloud providers, so our customers don’t need to compromise by sampling or leaving out logs from products all together in order to cut down on costs.</p><p>Just like the simplicity of Logpull, log storage on R2 will be quick and easy. With a one click setup, we’ll store your logs, and you don’t have to worry about any configuration details. Retention is totally in our customer’s control to match the security and compliance needs of their business. With R2, you can also store your logs for any products we have logging for today (and we’re always adding more as our product line expands).</p>
    <div>
      <h3>Log storage; we’re just getting started</h3>
      <a href="#log-storage-were-just-getting-started">
        
      </a>
    </div>
    <p>With log storage on Cloudflare, we’re creating the building blocks to allow customers to perform log analysis and forensics capabilities directly on Cloudflare. Whether conducting an investigation, responding to a support request or addressing an incident, using analytics for a birds eye view and inspecting logs to determine the root cause is a powerful combination.</p><p>If you’re interested in getting notified when you can store your logs on Cloudflare, sign up through this <a href="https://forms.gle/x24NKoMF8rJBJPpU7">form</a>.</p><p>We’re always looking for talented engineers to take on the challenges of working with data at an incredible scale. If you’re interested <a href="https://boards.greenhouse.io/cloudflare/jobs/2103918?gh_jid=2103918">apply here</a>.</p> ]]></content:encoded>
            <category><![CDATA[CIO Week]]></category>
            <category><![CDATA[Logs]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[undefined]]></category>
            <guid isPermaLink="false">31g0hcyo4g0BZnWK0oC5ut</guid>
            <dc:creator>Tanushree Sharma</dc:creator>
        </item>
        <item>
            <title><![CDATA[Announcing Cloudflare R2 Storage: Rapid and Reliable Object Storage, minus the egress fees]]></title>
            <link>https://blog.cloudflare.com/introducing-r2-object-storage/</link>
            <pubDate>Tue, 28 Sep 2021 13:00:01 GMT</pubDate>
            <description><![CDATA[ Introducing Cloudflare’s S3-compatible Object Storage service, with zero egress bandwidth charges and automatic migration from S3-compatible services. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>We’re excited to announce Cloudflare R2 Storage! By giving developers the ability to store large amounts of unstructured data, we’re expanding what’s possible with Cloudflare while slashing the <a href="https://www.cloudflare.com/learning/cloud/what-are-data-egress-fees/">egress bandwidth fees</a> associated with typical <a href="https://www.cloudflare.com/learning/cloud/what-is-cloud-storage/">cloud storage</a> services to zero.</p><p><a href="https://www.cloudflare.com/developer-platform/products/r2/">Cloudflare R2 Storage</a> includes full S3 API compatibility, working with existing tools and applications as built.</p><p>Let’s get into the R2 details.</p>
    <div>
      <h3>R2 means “Really Requestable”</h3>
      <a href="#r2-means-really-requestable">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">Object Storage</a>, sometimes referred to as <a href="https://www.cloudflare.com/learning/cloud/what-is-blob-storage/">blob storage</a>, stores arbitrarily large, unstructured files. Object storage is well suited to storing everything from media files or log files to application-specific metadata, all retrievable with consistent <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-latency/">latency</a>, high durability, and limitless capacity.</p><p>The most familiar API for Object Storage, and the API R2 implements, is Amazon’s Simple Storage Service (S3). When S3 launched in 2006, cloud storage services were a godsend for developers. It didn’t happen overnight, but over the last fifteen years, developers have embraced cloud storage and its promise of infinite storage space.</p><p>As transformative as cloud storage has been, a downside emerged: actually getting your data back. Over time, companies have amassed massive amounts of data on cloud provider networks. When they go to retrieve that data, they’re hit with <a href="/aws-egregious-egress/">massive egress fees</a> that don’t correspond to any customer value — just a tax developers have grown accustomed to paying.</p><p>Enter R2.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7ueoC6FVML11Wrrpr2ELlt/38ceacbacb2b5891ad12f2e52ee0b666/unnamed-1.png" />
            
            </figure><p>Traditional object storage charges developers for three things: bandwidth, storage size and storage operations.</p><p>R2 builds on Cloudflare’s commitment to the <a href="https://www.cloudflare.com/bandwidth-alliance/">Bandwidth Alliance</a>, providing zero-cost egress for stored objects — no matter your request rate.  <a href="https://www.cloudflare.com/the-net/cloud-egress-fees-challenge-future-ai/">Egress bandwidth</a> is often the largest charge for developers utilizing object storage and is also the hardest charge to predict.  Eliminating it is a huge win for open-access to data stored in the cloud.</p><p>That doesn’t mean we are shifting bandwidth costs elsewhere. Cloudflare R2 will be priced at $0.015 per GB of data stored per month — significantly cheaper than major incumbent providers.</p><p>Infrequent access to objects is often trivial for providers to support yet incurs the same per-operation charges. We don’t think it’s fair that typical object storage bills a developer making one request a second the same rate as an enterprise making thousands of requests a second — or frequently a higher rate when considering negotiated volume discounts.</p><p>On the flip side, providers designed for infrequent access typically can’t scale to heavy usage.</p><p>R2 will zero-rate infrequent storage operations under a threshold — currently planned to be in the single digit requests per second range. Above this range, R2 will charge significantly less per-operation than the major providers. Our object storage will be extremely inexpensive for infrequent access and yet capable of and cheaper than major incumbent providers at scale.</p><p>This cheaper price doesn’t come with reduced scalability. Behind the scenes, R2 automatically and intelligently manages the tiering of data to drive both performance at peak load and low-cost for infrequently requested objects.  We’ve gotten rid of complex, manual tiering policies in favor of what developers have always wanted out of object storage: limitless scale at the lowest possible cost.</p>
    <div>
      <h3>R2 means “Repositioning Records”</h3>
      <a href="#r2-means-repositioning-records">
        
      </a>
    </div>
    <p>Zero egress means you can get objects out easily, but what about putting objects in? <a href="https://www.cloudflare.com/learning/cloud/what-is-data-migration/">Migrating data</a> across cloud providers, even if they both support the complete S3 API, is error-prone and costly.</p><p>To make this easier for you, without requiring you to change any of your tooling, Cloudflare will offer a migration service from <a href="https://www.cloudflare.com/developer-platform/solutions/s3-compatible-object-storage/">S3-compatible cloud storage services</a>. The migrator will reduce the toil of manually uploading objects, by incrementally copying objects to R2 as they are requested. This <a href="https://www.cloudflare.com/learning/cloud/what-is-multicloud/">multi-cloud architecture</a> allows you to start serving objects from R2 as they migrate, saving you money while offering the benefits of multi-cloud.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3qzLEoSEY7elrYpbFtYEkI/a4268c4a8af9903ea459015dfc655222/Two-Paths-Diverged-5.png" />
            
            </figure><p>Our vision for R2 includes multi-region storage that automatically replicates objects to the locations they’re frequently requested from. As with <a href="/supporting-jurisdictional-restrictions-for-durable-objects/">Durable Objects</a>, we plan on introducing jurisdictional restrictions that allow developers to comply with complex <a href="https://www.cloudflare.com/learning/privacy/what-is-data-sovereignty/">data sovereignty requirements</a> via a simple API.</p>
    <div>
      <h3>R2 means “Ridiculously Reliable”</h3>
      <a href="#r2-means-ridiculously-reliable">
        
      </a>
    </div>
    <p>The core of what makes Object Storage great is reliability — we designed R2 for data durability and resilience at its core. R2 will provide 99.999999999% (eleven 9’s) of annual durability, which describes the likelihood of data loss. If you store 1,000,000 objects on R2, you can expect to lose one once every 100,000 years — the same level of durability as other major providers. R2 will be resistant to regional failures, replicating objects multiple times for high availability.</p><p>R2 is designed with redundancy across a large number of regions for reliability. We plan on starting from automatic global distribution and adding back region-specific controls for when data has to be stored locally, as described above.</p>
    <div>
      <h3>R2 means “Radically Reprogrammable”</h3>
      <a href="#r2-means-radically-reprogrammable">
        
      </a>
    </div>
    <p>R2 is fully integrated with the <a href="https://workers.cloudflare.com/">Cloudflare Workers</a> serverless runtime. You can bind a Worker to a specific bucket, dynamically transforming objects as they are written to or read from storage buckets. The deep integration between Workers and R2 makes building data pipelines and manipulating objects incredibly easy.</p><p>Cloudflare R2 is designed to easily integrate with the rest of Cloudflare's products. As a few examples, our plan is to allow Durable Objects to be configured with R2 as a backup target, and provide automatic integration between R2 and Cloudflare cache to greatly extend cache lifetimes for infrequently changing objects.</p>
    <div>
      <h3>What will you be able to build with Cloudflare R2?</h3>
      <a href="#what-will-you-be-able-to-build-with-cloudflare-r2">
        
      </a>
    </div>
    <p>There’s a lot you can do with long-term storage, especially with access to the Workers compute platform just alongside it.</p><p>For example, streaming data from a large number of IoT devices becomes a breeze with R2. Starting with a Worker to transform and manipulate the data, R2 can ingest large volumes of sensor data and store it at low cost. With no egress fees, it becomes simple to migrate volumes of data to multiple databases and analytics solutions as needed, dramatically reducing storage costs. With the ability to run a Worker on the outgoing data as well, the data pipeline itself is more flexible.</p><p>R2 is also a great place for CDN assets and large media files. For large files, R2 can significantly extend cache lifetimes while dramatically slashing egress bills. Combined with the Cache API and Workers, content can be dynamically cached for low-latency access around the globe.</p><p>More than anything, R2’s lack of egress bandwidth charges makes it ideal for storing content that’s accessed frequently. Today, R2 scales well to handle heavy request loads, dynamically tiering your objects to provide the best performance at the lowest cost. This dynamic tiering allows us to offer the <a href="https://r2-calculator.cloudflare.com/">lowest prices</a> while supporting peak performance — with no user configuration required.</p>
    <div>
      <h3>Accessing Cloudflare R2</h3>
      <a href="#accessing-cloudflare-r2">
        
      </a>
    </div>
    <p>R2 is currently under development — you can sign up <a href="https://dash.cloudflare.com/?to=/:account/r2/plans">here</a> to join the waitlist for access. We’re excited to work with a number of earlier users to refine and test the product. We’ll be <a href="/r2-open-beta/">announcing an open beta</a> where any user will be able to sign up for the service soon.</p><p>We’re excited to continue to build the product and push towards open beta, and we have big ideas for what the future of storage at Cloudflare’s edge could look like. If you’re a distributed systems engineer who wants to help us build the future of state at the edge, <a href="https://www.cloudflare.com/careers/jobs/?department=Emerging%20Technology%20and%20Incubation&amp;location=default">come work with us</a>!</p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">2qGzoicoJ6a44dAiisHcf5</guid>
            <dc:creator>Greg McKeon</dc:creator>
        </item>
    </channel>
</rss>