
<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>Mon, 04 May 2026 22:50:28 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Agents that remember: introducing Agent Memory]]></title>
            <link>https://blog.cloudflare.com/introducing-agent-memory/</link>
            <pubDate>Fri, 17 Apr 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Agent Memory is a managed service that gives AI agents persistent memory, allowing them to recall what matters, forget what doesn't, and get smarter over time. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>As developers build increasingly sophisticated <a href="https://developers.cloudflare.com/agents/">agents</a> on Cloudflare, one of the biggest challenges they face is getting the right information into context at the right time. The quality of results produced by models is directly tied to the quality of context they operate with, but even as context window sizes grow past one million (1M) tokens, <a href="https://www.trychroma.com/research/context-rot"><u>context rot</u></a> remains an unsolved problem. A natural tension emerges between two bad options: keep everything in context and watch quality degrade, or aggressively prune and risk losing information the agent needs later.</p><p>Today we're announcing the private beta of <b>Agent Memory</b>, a managed service that extracts information from agent conversations and makes it available when it’s needed, without filling up the context window.</p><p>It gives AI agents persistent memory, allowing them to recall what matters, forget what doesn't, and get smarter over time. In this post, we’ll explain how it works — and what it can help you build.</p>
    <div>
      <h2>The state of agentic memory</h2>
      <a href="#the-state-of-agentic-memory">
        
      </a>
    </div>
    <p>Agentic memory is one of the fastest-moving spaces in AI infrastructure, with new open-source libraries, managed services, and research prototypes launching on a near-weekly basis. These offerings vary widely in what they store, how they retrieve, and what kinds of agents they're designed for. Benchmarks like <a href="https://arxiv.org/abs/2410.10813"><u>LongMemEval</u></a>, <a href="https://arxiv.org/abs/2402.17753"><u>LoCoMo</u></a>, and <a href="https://arxiv.org/pdf/2510.27246"><u>BEAM</u></a> provide useful apples-to-apples comparisons, but they also make it easy to build systems that <a href="https://en.wikipedia.org/wiki/Overfitting"><u>overfit</u></a> for a specific evaluation and break down in production.</p><p>Existing offerings also differ in architecture. Some are managed services that handle extraction and retrieval in the background, others are self-hosted frameworks where you run the memory pipeline yourself. Some expose constrained, purpose-built APIs that keep memory logic out of the agent's main context; others give the model raw access to a database or filesystem and let it design its own queries, burning tokens on storage and retrieval strategy instead of the actual task. Some try to fit everything into the context window, partitioning across multiple agents if needed, while others use retrieval to surface only what's relevant. </p><p>Agent Memory is a managed service with an opinionated API and retrieval-based architecture. We've carefully considered the alternatives, and we believe this combination is the right default for most production workloads. Tighter ingestion and retrieval pipelines are superior to giving agents raw filesystem access. In addition to improved cost and performance, they provide a better foundation for complex reasoning tasks required in production, like temporal logic, supersession, and instruction following. We'll likely expose data for programmatic querying down the road, but we expect that to be useful for edge cases, not common cases.</p><p>We built Agent Memory because the workloads we see on our platform exposed gaps that existing approaches don't fully address. Agents running for weeks or months against real codebases and production systems need memory that stays useful as it grows — not just memory that performs well on a clean benchmark dataset that may fit entirely into a newer model's context window. </p><p>They need fast ingestion. They need retrieval that doesn't block the conversation. And they need to run on models that keep the per-query cost reasonable.</p>
    <div>
      <h2>How you use it</h2>
      <a href="#how-you-use-it">
        
      </a>
    </div>
    <p>Agent Memory stores memories in a profile, which is addressed by name. A profile gives you several operations: ingest a conversation, remember something specific, recall what you need, list memories, or forget a specific memory. <i>Ingest</i> is the bulk path that is typically called when the harness compacts context. <i>Remember</i> is for the model to store something important on the spot. <i>Recall</i> runs the full retrieval pipeline and returns a synthesized answer.</p>
            <pre><code>export default {
  async fetch(request: Request, env: Env): Promise&lt;Response&gt; {
    // Get a profile -- an isolated memory store shared across sessions, agents, and users
    const profile = await env.MEMORY.getProfile("my-project");
    // Ingest -- extract memories from a conversation (typically called at compaction)
    await profile.ingest([
      { role: "user", content: "Set up the project with React and TypeScript." },
      { role: "assistant", content: "Done. Scaffolded a React + TS project targeting Workers." },
      { role: "user", content: "Use pnpm, not npm. And dark mode by default." },
      { role: "assistant", content: "Got it -- pnpm and dark mode as default." },
    ], { sessionId: "session-001" });
    // Remember -- store a single memory explicitly (direct tool use by the model)
    const memory = await profile.remember({
      content: "API rate limit was increased to 10,000 req/s per zone after the April 10 incident.",
      sessionId: "session-001",
    });
    // Recall -- retrieve memories and get a synthesized answer
    const results = await profile.recall("What package manager does the user prefer?");
    console.log(results.result); // "The user prefers pnpm over npm."
    return Response.json({ ok: true });
  },
};</code></pre>
            <p>Agent Memory is accessed via a binding from any Cloudflare Worker. It can also be accessed via a REST API for agents running outside of Workers, following the same pattern as other Cloudflare developer platform APIs. If you’re building with the Cloudflare Agents SDK, the Agent Memory service integrates neatly as the reference implementation for handling compaction, remembering, and searching over memories in <a href="https://developers.cloudflare.com/agents/concepts/memory/"><u>the memory portion</u></a> of the Sessions API.</p>
    <div>
      <h2>What you can build with it</h2>
      <a href="#what-you-can-build-with-it">
        
      </a>
    </div>
    <p>Agent Memory is designed to work across a range of agent architectures:</p><p><b>Memory for individual agents.</b> Regardless of whether you're building with coding agents like Claude Code or OpenCode with a human in the loop, using self-hosted agent frameworks like OpenClaw or Hermes to act on your behalf, or wiring up managed services like <a href="https://www.anthropic.com/engineering/managed-agents"><u>Anthropic’s Managed Agents</u></a>, Agent Memory can serve as the persistent memory layer without any changes to the agent's core loop.</p><p><b>Memory for custom agent harnesses.</b> Many teams are building their own agent infrastructure, including background agents that run autonomously without a human in the loop. <a href="https://builders.ramp.com/post/why-we-built-our-background-agent"><u>Ramp Inspect</u></a> is one public example; <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents"><u>Stripe</u></a> and <a href="https://engineering.atspotify.com/2025/11/spotifys-background-coding-agent-part-1"><u>Spotify</u></a> have described similar systems. These harnesses can also benefit from giving their agents memory that persists across sessions and survives restarts.</p><p><b>Shared memory across agents, people, and tools.</b> A memory profile doesn't have to belong to a single agent. A team of engineers can share a memory profile so that knowledge learned by one person's coding agent is available to everyone: coding conventions, architectural decisions, tribal knowledge that currently lives in people's heads or gets lost when context is pruned. A code review bot and a coding agent can share memory so that review feedback shapes future code generation. The knowledge your agents accumulate stops being ephemeral and starts becoming a durable team asset.</p><p>While search is a component of memory, agent search and agent memory solve distinct problems. <a href="https://developers.cloudflare.com/ai-search/"><u>AI Search</u></a> is our primitive for finding results across unstructured and structured files; Agent Memory is for context recall. The data in Agent Memory doesn't exist as files; it's derived from sessions. An agent can use both, and they are designed to work together. </p>
    <div>
      <h2>Your memories are yours</h2>
      <a href="#your-memories-are-yours">
        
      </a>
    </div>
    <p>As agents become more capable and more deeply embedded in business processes, the memory they accumulate becomes genuinely valuable — not just as an operational state, but as institutional knowledge that took real work to build. We're hearing growing concern from customers about what it means to tie that asset to a single vendor, which is reasonable. The more an agent learns, the higher the switching cost if that memory can't move with it.</p><p>Agent Memory is a managed service, but your data is yours. Every memory is exportable, and we're committed to making sure the knowledge your agents accumulate on Cloudflare can leave with you if your needs change. We think the right way to earn long-term trust is to make leaving easy and to keep building something good enough that you don't want to.</p>
    <div>
      <h2>How Agent Memory works</h2>
      <a href="#how-agent-memory-works">
        
      </a>
    </div>
    <p>To understand what happens behind the API shown above, it helps to break down how agents manage context. An agent has three components:</p><ol><li><p>A <b>harness</b> that drives repeated calls to a model, facilitates tool calls, and manages state.</p></li><li><p>A <b>model</b> that takes context and returns completions.</p></li><li><p><b>State</b> that includes both the current context window and additional information outside context: conversation history, files, databases, memory.</p></li></ol><p>The critical moment in an agent’s context lifecycle is <b>compaction,</b> when the harness decides to shorten context to stay within a model's limits or to avoid context rot. Today, most agents discard information permanently. Agent Memory preserves knowledge on compaction instead of losing it.</p><p>Agent Memory integrates into this lifecycle in two ways:</p><ol><li><p><b>Bulk ingestion at compaction.</b> When the harness compacts context, it ships the conversation to Agent Memory for ingestion. Ingestion extracts facts, events, instructions, and tasks from the message history, deduplicates them against existing memories, and stores them as memories for future retrieval.</p></li><li><p><b>Direct tool use by the model.</b> The model gets tools to interact directly with memories, including the ability to recall (search memories for specific information). The model can also remember (explicitly store memories based on something important), forget (mark a memory as no longer important or true), and list (see what memories are stored). These are lightweight operations that don't require the model to design queries or manage storage. The primary agent should never burn context on storage strategy. The tool surface it sees is deliberately constrained so that memory stays out of the way of the actual task.</p></li></ol>
    <div>
      <h3>The ingestion pipeline</h3>
      <a href="#the-ingestion-pipeline">
        
      </a>
    </div>
    <p>When a conversation arrives for ingestion, it passes through a multi-stage pipeline that extracts, verifies, classifies, and stores memories.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1Cjj8uXbZRCrQVAxirTo7y/d62b5b798990540e78825747ff24afec/image3.png" />
          </figure><p>The first step is deterministic ID generation. Each message gets a content-addressed ID — a SHA-256 hash of session ID, role, and content, truncated to 128 bits. If the same conversation is ingested twice, every message resolves to the same ID, making re-ingestion idempotent. </p><p>Next, the extractor runs two passes in parallel. A full pass chunks messages at roughly 10K characters with two-message overlap and processes up to four chunks concurrently. Each chunk gets a structured transcript with role labels, relative dates resolved to absolutes ("yesterday" becomes "2026-04-14"), and line indices for source provenance. For longer conversations (9+ messages), a detail pass runs alongside the full pass, using overlapping windows that focus specifically on extracting concrete values like names, prices, version numbers, and entity attributes that broad extraction tends to miss. The two result sets are then merged.</p><p>The next step is to verify each extracted memory against the source transcript. The verifier runs eight checks covering entity identity, object identity, location context, temporal accuracy, organizational context, completeness, relational context, and whether inferred facts are actually supported by the conversation. Each item is passed, corrected, or dropped accordingly.</p><p>The pipeline then classifies each verified memory into one of  four types. </p><ul><li><p><b>Facts</b> represent what is true right now, atomic, stable knowledge like "the project uses GraphQL" or "the user prefers dark mode." </p></li><li><p><b>Events</b> capture what happened at a specific time, like a deployment or a decision. </p></li><li><p><b>Instructions </b>describe how to do something, such as procedures, workflows, runbooks. </p></li><li><p><b>Tasks</b> track what is being worked on right now and are ephemeral by design.</p></li></ul><p>Facts and instructions are keyed. Each gets a normalized topic key, and when a new memory has the same key as an existing one, the old memory is superseded rather than deleted. This creates a version chain with a forward pointer from the old memory to the new memory. Tasks are excluded from the vector index entirely to keep it lean but remain discoverable via full-text search.</p><p>Finally, everything is written to storage using INSERT OR IGNORE so that content-addressed duplicates are silently skipped. After returning a response to the harness, background vectorization runs asynchronously. The embedding text prepends the 3-5 search queries generated during classification to the memory content itself, bridging the gap between how memories are written (declaratively: "user prefers dark mode") and how they're searched (interrogatively: "what theme does the user want?"). Vectors for superseded memories are deleted in parallel with new upserts.</p>
    <div>
      <h3>The retrieval pipeline</h3>
      <a href="#the-retrieval-pipeline">
        
      </a>
    </div>
    <p>When an agent searches for a memory, the query goes through a separate retrieval pipeline. During development, we discovered that no single retrieval method works best for all queries, so we run several methods in parallel and fuse the results.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4cH9jB2ghMIQZG0McicyV7/8d75ca8c748054c06cc5e25c4d1d730f/image5.png" />
          </figure><p>The first stage runs query analysis and embedding concurrently. The query analyzer produces ranked topic keys, full-text search terms with synonyms, and a HyDE (Hypothetical Document Embedding), a declarative statement phrased as if it were the answer to the question. This stage embeds the raw query directly, and both embeddings are used downstream.</p><p>In the next stage, five retrieval channels run in parallel. Full-text search with <a href="https://tartarus.org/martin/PorterStemmer/"><u>Porter stemming</u></a> handles keyword precision for queries where you know the exact term but not the surrounding context. Exact fact-key lookup returns results where the query maps directly to a known topic key. Raw message search queries the stored conversation messages directly via full-text search for unclassified conversation fragments that act as a safety net, catching verbatim details that the extraction pipeline may have generalized away. Direct vector search finds semantically similar memories using the embedded query. And HyDE vector search finds memories that are similar to what the answer would look like, which often surfaces results that direct embedding misses — particularly for abstract or multi-hop queries where the question and the answer use different vocabulary.</p><p>In the third and final stage, results from all five retrieval channels are merged using Reciprocal Rank Fusion (RRF), where each result receives a weighted score based on where it ranked within a given channel. Fact-key matches get the highest weight because an exact topic match is the strongest signal. Full-text search, HyDE vectors, and direct vectors are each weighted based on strength of signal. Finally, raw message matches are also included with low weight as a safety net to identify candidate results the extraction pipeline may have missed. Ties are broken by recency, with newer results ranked higher.</p><p>The pipeline then passes the top candidates to the synthesis model, which generates a natural-language answer to the original search query. Some specific query types get special treatment. As an example, temporal computation is handled deterministically via regex and arithmetic, not by the LLM. The results are injected into the synthesis prompt as pre-computed facts. Models are unreliable at things like date math, so we don't ask them to do it.</p>
    <div>
      <h2>How we built it</h2>
      <a href="#how-we-built-it">
        
      </a>
    </div>
    <p>Our initial prototype of Agent Memory was lightweight, with a basic extraction pipeline, vector storage, and simple retrieval. It worked well enough to demonstrate the concept, but not well enough to ship.</p><p>So we put it into an agent-driven loop and iterated. The cycle looked like this: run benchmarks, analyze where we had gaps, propose solutions, have a human review the proposals to select strategies that generalize rather than overfit, let the agent make the changes, repeat.</p><p>This worked well, but came with one specific challenge. LLMs are stochastic, even with temperature set to zero. This caused results to vary across runs, which meant we had to average multiple runs (time-consuming for large benchmarks) and rely on trend analysis alongside raw scores to understand what was actually working. Along the way we had to guard carefully against overfitting the benchmarks in ways that didn't genuinely make the product better for the general case.</p><p>Over time, this got us to a place where benchmark scores improved consistently with each iteration and we had a generalized architecture that would work in the real world. We intentionally tested against multiple benchmarks (including LoCoMo, LongMemEval, and BEAM) to push the system in different ways.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/68NbsLPqv7VXu0ODV6PFGK/89aa2e05b82f24b642d0c4cbddbdf340/image.png" />
          </figure>
    <div>
      <h2>Why Cloudflare</h2>
      <a href="#why-cloudflare">
        
      </a>
    </div>
    <p>We build Cloudflare on Cloudflare, and Agent Memory is no different. Existing primitives that are powerful and easily composable allowed us to ship the first prototype in a weekend and a fully functioning, productionized internal version of Agent Memory in less than a month. In addition to speed of delivery, Cloudflare turned out to be the ideal place to build this kind of service for a few other reasons.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7IWzT1bmOW7Lip9ib17Di8/1a3a83be3d4e7f1450eacd41d18a924f/image4.png" />
          </figure><p>Under the hood, Agent Memory is a Cloudflare Worker that coordinates several systems:</p><ul><li><p>Durable Object: stores the raw messages and classified memories</p></li><li><p>Vectorize: provides vector search over embedded memories</p></li><li><p>Workers AI: runs the LLMs and embedding models</p></li></ul><p>Each memory context maps to its own Durable Object instance and Vectorize index, keeping data fully isolated between contexts. It also allows us to scale easily with higher demands.</p><p><b>Compute isolation via Durable Objects.</b> Each memory profile gets its own <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Object</u></a> (DO) with a SQLite-backed store, providing strong isolation between tenants without any infrastructure overhead. The DO handles FTS indexing, supersession chains, and transactional writes. DO’s getByName() addressing means any request, from anywhere, can reach the right memory profile by name, and ensures that sensitive memories are strongly isolated from other tenants.</p><p><b>Storage across the stack.</b> Memory content lives in SQLite-backed DOs. Vectors live in <a href="https://developers.cloudflare.com/vectorize/"><u>Vectorize</u></a>. In the future, snapshots and exports will go to <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a> for cost-efficient long-term storage. Each primitive is purpose-built for its workload, we don't need to force everything into a single shape or database.</p><p><b>Local model inference with Workers AI.</b> The entire extraction, classification, and synthesis pipeline runs on <a href="https://developers.cloudflare.com/workers-ai/"><u>Workers AI</u></a> models deployed on Cloudflare's network. All AI calls pass a session affinity header routed to the memory profile name, so repeated requests hit the same backend for prompt caching benefits.</p><p>One interesting finding from our model selection: a bigger, more powerful model isn't always better. We currently default to Llama 4 Scout (17B, 16-expert MoE) for extraction, verification, classification, and query analysis, and Nemotron 3 (120B MoE, 12B active parameters) for synthesis. Scout handles the structured classification tasks efficiently, while Nemotron's larger reasoning capacity improves the quality of natural-language answers. The synthesizer is the only stage where throwing more parameters at the problem consistently helped. For everything else, the smaller model hit a better sweet spot of cost, quality, and latency.</p>
    <div>
      <h2>How we've been using it</h2>
      <a href="#how-weve-been-using-it">
        
      </a>
    </div>
    <p>We run Agent Memory internally for our own workflows at Cloudflare, as both a proving ground and a source of ideas for what to build next.</p><p><b>Coding agent memory.</b> We use an internal <a href="https://opencode.ai">OpenCode</a> plugin that wires Agent Memory into the development loop. Agent Memory provides memory of past compaction within sessions and across them. The less obvious benefit has been shared memory across a team: with a shared profile, the agent knows what other members of your team have already learned, which means it can stop asking questions that have already been answered and stop making mistakes that have already been corrected.</p><p><b>Agentic code review.</b> We've connected Agent Memory to our internal agentic code reviewer. Arguably the most useful thing it learned to do was stay quiet. The reviewer now remembers that a particular comment wasn't relevant in a past review, that a specific pattern was flagged, and the author chose to keep it for a good reason. Reviews get less noisy over time, not just smarter.</p><p><b>Chat bots.</b> We've also wired memory into an internal chat bot that ingests message history and then lurks and remembers new messages that are sent. Then, when someone asks a question, the bot can answer based on previous conversations.</p><p>We also have a number of additional use cases that we plan to roll out internally in the near future as we refine and improve the service.</p>
    <div>
      <h2>What's next</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>We're continuing to test and refine Agent Memory internally, improving the extraction pipeline, tuning retrieval quality, and expanding the background processing capabilities. Similar to how the human brain consolidates memories by replaying and strengthening connections during sleep, we see opportunities for memory storage to improve asynchronously and are currently implementing and testing various strategies to make this work.</p><p>We plan to make Agent Memory publicly available soon. If you're building agents on Cloudflare and want early access, <a href="https://forms.gle/RAXbK6gN9Yy89ECw8"><u>contact us to join the waitlist</u></a>.</p><p>If you want to dig into the architecture, share what you're building, or follow along as we develop this further, join us on the<a href="https://discord.cloudflare.com"> <u>Cloudflare Discord</u></a> or start a thread in the<a href="https://community.cloudflare.com"> <u>Cloudflare Community</u></a>. We're actively watching both, and are interested in what production agent workloads actually look like in the wild.</p> ]]></content:encoded>
            <category><![CDATA[Agents Week]]></category>
            <category><![CDATA[Agents]]></category>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[Storage]]></category>
            <guid isPermaLink="false">3VGCjw0ivWk7RfPbPZ9k9H</guid>
            <dc:creator>Tyson Trautmann</dc:creator>
            <dc:creator>Rob Sutter</dc:creator>
        </item>
        <item>
            <title><![CDATA[Artifacts: versioned storage that speaks Git]]></title>
            <link>https://blog.cloudflare.com/artifacts-git-for-agents-beta/</link>
            <pubDate>Thu, 16 Apr 2026 13:01:00 GMT</pubDate>
            <description><![CDATA[ Give your agents, developers, and automations a home for code and data. We’ve just launched Artifacts: Git-compatible versioned storage built for agents. Create tens of millions of repos, fork from any remote, and hand off a URL to any Git client.
 ]]></description>
            <content:encoded><![CDATA[ <p>Agents have changed how we think about source control, file systems, and persisting state. Developers and agents are generating more code than ever — more code will be written over the next 5 years than in all of programming history — and it’s driven an order-of-magnitude change in the scale of the systems needed to meet this demand. Source control platforms are especially struggling here: they were built to meet the needs of humans, not a 10x change in volume driven by agents who never sleep, can work on several issues at once, and never tire.</p><p>We think there’s a need for a new primitive: a distributed, versioned filesystem that’s built for agents first and foremost, and that can serve the types of applications that are being built today.</p><p>We’re calling this Artifacts: a versioned file system that speaks Git. You can create repositories programmatically, alongside your agents, sandboxes, Workers, or any other compute paradigm, and connect to it from any regular Git client.</p><p>Want to give every agent session a repo? Artifacts can do it. Every sandbox instance? Also Artifacts. Want to create 10,000 forks from a known-good starting point? You guessed it: Artifacts again. Artifacts exposes a REST API and native Workers API for creating repositories, generating credentials, and commits for environments where a Git client isn’t the right fit (i.e. in any serverless function).</p><p>Artifacts is available in private beta and we’re aiming to open this up as a public beta by early May.</p>
            <pre><code>// Create a repo
const repo = await env.AGENT_REPOS.create(name)
// Pass back the token &amp; remote to your agent
return { repo.remote, repo.token }</code></pre>
            
            <pre><code># Clone it and use it like any regular git remote
$ git clone https://x:${TOKEN}@123def456abc.artifacts.cloudflare.net/git/repo-13194.git
</code></pre>
            <p>That’s it. A bare repo, ready to go, created on the fly, that any git client can operate it against.</p><p>And if you want to bootstrap an Artifacts repo from an existing git repository so that your agent can work on it independently and push independent changes, you can do that too with .import():</p>
            <pre><code>interface Env {
  ARTIFACTS: Artifacts
}

export default {
  async fetch(request: Request, env: Env) {
    // Import from GitHub
    const { remote, token } = await env.ARTIFACTS.import({
      source: {
        url: "https://github.com/cloudflare/workers-sdk",
        branch: "main",
      },
      target: {
        name: "workers-sdk",
      },
    })

    // Get a handle to the imported repo
    const repo = await env.ARTIFACTS.get("workers-sdk")

    // Fork to an isolated, read-only copy
    const fork = await repo.fork("workers-sdk-review", {
      readOnly: true,
    })

    return Response.json({ remote: fork.remote, token: fork.token })
  },
}</code></pre>
            <p><a href="http://developers.cloudflare.com/artifacts/"><u>Check out the documentation</u></a> to get started, or if you want to understand how Artifacts is being used, how it was built, and how it works under the hood: read on.</p>
    <div>
      <h2>Why Git? What’s a versioned file system?</h2>
      <a href="#why-git-whats-a-versioned-file-system">
        
      </a>
    </div>
    <p>Agents know Git. It’s deep in the training data of most models. The happy path <i>and </i>the edge cases are well known to agents, and code-optimized models (and/or harnesses) are particularly good at using git.</p><p>Further, Git’s data model is not only good for source control, but for <i>anything</i> where you need to track state, time travel, and persist large amounts of small data. Code, config, session prompts and agent history: all of these are things (“objects”) that you often want to store in small chunks (“commits”) and be able to revert or otherwise roll back to (“history”). </p><p>We could have invented an entirely new, bespoke protocol… but then you have the bootstrap problem. AI models don’t know it, so you have to distribute skills, or a CLI, or hope that users are plugged into your docs MCP… all of that adds friction.

If we can just give agents an authenticated, secure HTTPS Git remote URL and have them operate as if it were a Git repo, though? That turns out to work pretty well. And for non-Git-speaking clients — such as a Cloudflare Worker, a Lambda function, or a Node.js app — we’ve exposed a REST API and (soon) language-specific SDKs. Those clients can also use <a href="https://isomorphic-git.org/"><u>isomorphic-git</u></a>, but in many cases a simpler TypeScript API can reduce the API surface needed.</p>
    <div>
      <h3>Not just for source control</h3>
      <a href="#not-just-for-source-control">
        
      </a>
    </div>
    <p>Artifacts’ Git API might make you think it’s just for source control, but it turns out that the Git API and data model is a powerful way to persist state in a way that allows you to fork, time-travel and diff state for <i>any</i> data.</p><p>Inside Cloudflare, we’re using Artifacts for our internal agents: automatically persisting the current state of the filesystem <i>and</i> the session history in a per-session Artifacts repo. This enables us to:</p><ul><li><p>Persist sandbox state without having to provision (and keep) block storage around.</p></li><li><p>Share sessions with others and allow them to time-travel back through both session (prompt) state <i>and</i> file state, irrespective of whether there were commits to the “actual” repository (source control).</p></li><li><p>And the best: <i>fork</i> a session from any point, allowing our team to share sessions with a co-worker and have them pick it up from them. Debugging something and want another set of eyes? Send a URL and fork it. Want to riff on an API? Have a co-worker fork it and pick up from where you left off.</p></li></ul><p>We’ve also spoken to teams who want to use Artifacts in cases where the Git protocol isn’t a requirement at all, but the semantics (reverting, cloning, diffing) <i>are</i>. Storing per-customer config as part of your product, and want the ability to roll back? Artifacts can be a good representation of this.</p><p>We’re excited to see teams explore the non-Git use-cases around Artifacts just as much as the Git-focused ones.</p>
    <div>
      <h2>Under the hood</h2>
      <a href="#under-the-hood">
        
      </a>
    </div>
    <p>Artifacts are built on top of Durable Objects. The ability to create millions (or tens of millions+) of instances of stateful, isolated compute is inherent to how Durable Objects work today, and that’s exactly what we needed for supporting millions of Git repos per namespace.</p><p>Major League Baseball (for live game fan-out), Confluence Whiteboards, and our own <a href="https://developers.cloudflare.com/agents/"><u>Agents SDK</u></a> use Durable Objects under the hood at significant scale, and so we’re building this on a primitive that we’ve had in production for some time.</p><p>What we did need, however, was a Git server implementation that could run on Cloudflare Workers. It needed to be small, as complete as possible, extensible (<a href="https://git-scm.com/docs/git-notes"><u>notes</u></a>, <a href="https://git-lfs.com/"><u>LFS</u></a>), and efficient. So we built one in <a href="https://ziglang.org/"><u>Zig</u></a>, and compiled it to Wasm.</p><p>Why did we use Zig? Three reasons:</p><ol><li><p> The entire git protocol engine is written in pure Zig (no libc), compiled to a ~100KB WASM binary (with room for optimization!). It implements SHA-1, zlib inflate/deflate, delta encoding/decoding, pack parsing, and the full git smart HTTP protocol — all from scratch, with zero external dependencies other than the standard library.</p></li><li><p> Zig gives us  manual control over memory allocation which is important in constrained environments like Durable Objects. The Zig Build System lets us easily share  code between the WASM runtime (production) and native builds (testing against libgit2 for correctness verification).</p></li><li><p>The WASM module communicates with the JS host via a thin callback interface: 11 host-imported functions for storage operations (host_get_object, host_put_object, etc.) and one for streaming output (host_emit_bytes). The WASM side is fully testable in isolation.</p></li></ol><p>Under the hood, Artifacts also uses R2 (for snapshots) and KV (for tracking auth tokens):</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/35SxJbQfntIscpotc0GBt8/48ae11213d7483c9b488321baacf78e7/BLOG-3269_2.png" />
          </figure><p><sup><code><i>How Artifacts works (Workers, Durable Objects, and WebAssembly)</i></code></sup></p><p>A Worker acts as the front-end, handling authentication &amp; authorization, key metrics (errors, latency) and looking up each Artifacts repository (Durable Object) on the fly. </p><p>Specifically:</p><ul><li><p>Files are stored in the underlying Durable Object’s SQLite database.</p><ul><li><p>Durable Object storage has a 2MB max row size, so large Git objects are chunked and stored across multiple rows.</p></li><li><p>We make use of the sync KV API (state.storage.kv)  which is backed by SQLite under the hood.</p></li></ul></li><li><p> DOs have ~128MB memory limits: this means we can spawn tens of millions of them (they’re fast and light) but have to work within those limits.</p><ul><li><p>We make heavy use of streaming in both the fetch and push paths, directly returning a `ReadableStream&lt;Uint8Array&gt;` built from the raw WASM output chunks.</p></li><li><p>We avoid calculating our own git deltas, instead,  the raw deltas and base hashes are persisted alongside the resolved object. On fetch, if the requesting client already has the base object, Zig emits the delta instead of the full object, which saves bandwidth <i>and</i> memory.</p></li></ul></li><li><p>Support for both v1 and v2 of the git protocol.</p><ul><li><p>We support capabilities including ls-refs, shallow clones (deepen, deepen-since, deepen-relative), and incremental fetch with have/want negotiation.</p></li><li><p>We have an extensive test suite with conformance tests against git clients and verification tests against a libgit2 server designed to validate protocol support.</p></li></ul></li></ul><p>On top of this, we have native support for <a href="https://git-scm.com/docs/git-notes"><u>git-notes</u></a>. Artifacts is designed to be agent-first, and notes enable agents to add notes (metadata) to Git objects. This includes prompts, agent attribution and other metadata that can be read/written from the repo without mutating the objects themselves.</p>
    <div>
      <h2>Big repos, big problems? Meet ArtifactFS.</h2>
      <a href="#big-repos-big-problems-meet-artifactfs">
        
      </a>
    </div>
    <p>Most repos aren’t that big, and Git is <a href="https://github.blog/open-source/git/gits-database-internals-i-packed-object-store/"><u>designed to be extremely efficient</u></a> in terms of storage: most repositories take only a few seconds to clone at most, and that’s dominated by network setup time, auth, and <a href="https://git-scm.com/book/ms/v2/Git-Internals-Git-Objects"><u>checksumming</u></a>. In most agent or sandbox scenarios, that’s workable: just clone the repo as the sandbox starts and get to work.</p><p>But what about a multi-GB repository and/or repos with millions of objects? How can we clone that repo quickly, without blocking the agent’s ability to get to work for minutes and consuming compute?</p><p>A popular web framework (at 2.4GB and with a long history!) takes close to 2 minutes to clone. A shallow clone is faster, but not enough to get down to single digit seconds, and we don’t always want to omit history (agents find it useful).</p><p>Can we get large repos down to ~10-15 seconds so that our agent can get to work? Well, yes: with a few tricks.</p><p>As part of our launch of Artifacts, <a href="https://github.com/cloudflare/artifact-fs"><u>we’re open-sourcing ArtifactFS</u></a>, a filesystem driver designed to mount large Git repos as quickly as possible, hydrating file contents on the fly instead of blocking on the initial clone. It's ideal for agents, sandboxes, containers and other use cases where startup time is critical. If you can shave ~90-100 seconds off your sandbox startup time for every large repo, and you’re running 10,000 of those sandbox jobs per month: that’s 2,778 sandbox hours saved.</p><p>You can think of ArtifactFS as “Git clone but async”:</p><ul><li><p>ArtifactFS runs a blobless clone of a git repository: it fetches the file tree and refs, but not the file contents. It can do that during sandbox startup, which then allows your agent harness to get to work.</p></li><li><p>In the background, it starts to hydrate (download) file contents concurrently via a lightweight daemon.</p></li><li><p>It prioritizes files that agents typically want to operate on first: package manifests (<code>package.json, go.mod</code>), configuration files, and code, deprioritizing binary blobs (images, executables and other non-text-files) where possible so that agents can scan the file tree as the files themselves are hydrated.</p></li><li><p>If a file isn’t fully hydrated when the agent tries to read it, the read will block until it has.</p></li></ul><p>The filesystem does not attempt to “sync” files back to the remote repository: with thousands or millions of objects, that’s typically very slow, and since we’re speaking git, we don’t need to. Your agent just needs to commit and push, as it would with any repository. No new APIs to learn.</p><p>Importantly, ArtifactFS works with any Git remote, not just our own Artifacts. If you’re cloning large repos from GitHub, GitLab, or self-hosted Git infrastructure: you can still use ArtifactFS.</p>
    <div>
      <h2>What’s coming?</h2>
      <a href="#whats-coming">
        
      </a>
    </div>
    <p>Our release today is just the beta, and we’re already working on a number of features that you’ll see land over the next few weeks:</p><ul><li><p>Expanding the <a href="https://developers.cloudflare.com/artifacts/observability/metrics/"><u>available metrics</u></a> we expose. Today we’re shipping metrics for key operations counts per namespace, repo and stored bytes per repo, so that managing millions of Artifacts isn’t toilsome.</p></li><li><p>Support for <a href="https://developers.cloudflare.com/queues/event-subscriptions/"><u>Event Subscriptions</u></a> for repo-level events so that we can emit events on pushes, pulls, clones, and forks to any repository within a namespace. This will also allow you to consume events, write webhooks, and use those events to notify end-users, drive lifecycle events within your products, and/or run post-push jobs (like CI/CD).</p></li><li><p>Native TypeScript, Go and Python client SDKs for interacting with the Artifacts API</p></li><li><p>Repo-level search APIs and namespace-wide search APIs, e.g. “find all the repos with a <code>package.json</code> file”. </p></li></ul><p>We’re also planning an API for <a href="https://developers.cloudflare.com/workers/ci-cd/builds/"><u>Workers Builds</u></a>, allowing you to run CI/CD jobs on any agent-driven workflow.</p>
    <div>
      <h2>What will it cost me?</h2>
      <a href="#what-will-it-cost-me">
        
      </a>
    </div>
    <p>We’re still early with Artifacts, but want our pricing to work at agent-scale: it needs to be cost effective to have millions of repos, unused (or rarely used) repos shouldn’t be a drag, and our pricing should match the massively-single-tenant nature of agents.</p><p>You also shouldn’t have to think about whether a repo is going to be used or not, whether it’s hot or cold, and/or whether an agent is going to wake it up. We’ll charge you for the storage you consume and the operations (e.g. clones, forks, pushes &amp; pulls) against each repo.</p><table><tr><th><p></p></th><th><p><b>$/unit</b></p></th><th><p><b>Included</b></p></th></tr><tr><td><p><b>Operations</b></p></td><td><p>$0.15 per 1,000 operations</p></td><td><p>First 10k included (per month)</p></td></tr><tr><td><p><b>Storage</b></p></td><td><p>$0.50/GB-mo</p></td><td><p>First 1GB included.</p></td></tr></table><p>Big, busy repos will cost more than smaller, less-often-used repos, whether you have 1,000, 100,000, or 10 million of them.</p><p>We’ll also be bringing Artifacts to the Workers Free plan (with some fair limits) as the beta progresses, and we’ll provide updates throughout the beta should this pricing change and ahead of billing any usage.</p>
    <div>
      <h2>Where do I start? </h2>
      <a href="#where-do-i-start">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3xLMMKCN1HNGWbkSyq0tDZ/2a8d49383957804f3ce204783e11ae80/BLOG-3269_3.png" />
          </figure><p>Artifacts is launching in private beta, and we expect public beta to be ready in early May (2026, to be clear!). We’ll be allowing customers in progressively over the next few weeks, and <a href="https://forms.gle/DwBoPRa3CWQ8ajFp7"><u>you can register interest for the private beta</u></a> directly.</p><p>In the meantime, you can learn more about Artifacts by:</p><ul><li><p>Reading the <a href="http://developers.cloudflare.com/artifacts/get-started/workers/"><u>getting started guide</u></a> in the docs.</p></li><li><p>Visiting the Cloudflare dashboard (Build &gt; Storage &amp; Databases &gt; Artifacts)</p></li><li><p>Reading through the <a href="http://developers.cloudflare.com/artifacts/api/rest-api/"><u>REST API examples</u></a></p></li><li><p>Learning more about <a href="http://developers.cloudflare.com/artifacts/concepts/how-artifacts-works/"><u>how Artifacts works</u></a> under the hood</p></li></ul><p>Follow <a href="http://developers.cloudflare.com/changelog/product/artifacts/"><u>the changelog</u></a> to track the beta as it progresses.</p>
    <div>
      <h2>Watch on Cloudflare TV</h2>
      <a href="#watch-on-cloudflare-tv">
        
      </a>
    </div>
    <div>
  
</div>
<p></p> ]]></content:encoded>
            <category><![CDATA[Agents Week]]></category>
            <category><![CDATA[Agents]]></category>
            <category><![CDATA[GitHub]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <guid isPermaLink="false">2sshzOlmGVsrtBz2mgeceE</guid>
            <dc:creator>Dillon Mulroy</dc:creator>
            <dc:creator>Matt Carey</dc:creator>
            <dc:creator>Matt Silverlock</dc:creator>
        </item>
        <item>
            <title><![CDATA[Deploy Postgres and MySQL databases with PlanetScale + Workers]]></title>
            <link>https://blog.cloudflare.com/deploy-planetscale-postgres-with-workers/</link>
            <pubDate>Thu, 16 Apr 2026 13:00:22 GMT</pubDate>
            <description><![CDATA[ Learn how to deploy PlanetScale Postgres and MySQL databases via Cloudflare and connect Cloudflare Workers. ]]></description>
            <content:encoded><![CDATA[ <p>Cloudflare announced our PlanetScale partnership last September to give <a href="https://workers.cloudflare.com/"><u>Cloudflare Workers</u></a> direct access to Postgres and MySQL databases for fast, full-stack applications.</p><p>Soon, we’re bringing our technologies even closer: you’ll be able to create PlanetScale Postgres and MySQL databases directly from the Cloudflare dashboard and API, and have them billed to your Cloudflare account. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5Tj4gJrV5hxlIWxlmoXVZe/7661c1e47c0c868b88301b5f4aca4441/BLOG-3213_2.png" />
          </figure><p>You choose the data storage that fits your Worker application needs and keep a single system for billing as a Cloudflare self-serve or enterprise customer. Cloudflare credits like those given in our <a href="https://www.cloudflare.com/forstartups/"><u>startup program</u></a> or Cloudflare committed spend can be used towards PlanetScale databases.</p>
    <div>
      <h2>Postgres &amp; MySQL for Workers</h2>
      <a href="#postgres-mysql-for-workers">
        
      </a>
    </div>
    <p>SQL relational databases like Postgres and MySQL are a foundation of modern applications. In particular, Postgres has risen in developer popularity with its rich tooling ecosystem (ORMs, GUIs, etc) and extensions like <a href="https://github.com/pgvector/pgvector/"><u>pgvector</u></a> for building vector search in AI-driven applications. Postgres is the default choice for most developers who need a powerful, flexible, and scalable database to power their applications.</p><p>You can already connect your PlanetScale account and create Postgres databases directly from the <a href="https://dash.cloudflare.com/?to=/:account/workers/hyperdrive?modal=1">Cloudflare dashboard</a> for your Workers. Starting next month, a new Cloudflare subscription will bill for new PlanetScale databases direct to your Cloudflare account as a self-serve or enterprise user.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1CHTq1qoaNGSNO5atsiS8J/a8eba618b77362aa467d94c4f625c600/BLOG-3213_3.png" />
          </figure><p><sup><i>How to create PlanetScale databases via </i></sup><a href="https://dash.cloudflare.com/?to=/:account/workers/hyperdrive?modal=1"><sup><i><u>Cloudflare dashboard</u></i></sup></a><sup><i> after your PlanetScale account is connected. Cloudflare billing is coming next month.</i></sup></p><p>With our built-in integration, PlanetScale databases automatically work with Workers using Hyperdrive, our database connectivity service. <a href="https://blog.cloudflare.com/how-hyperdrive-speeds-up-database-access/"><u>Hyperdrive</u></a> service manages database connection pools and query caching to make database queries fast and reliable. You just add a <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/"><u>binding</u></a> to your Worker’s <a href="https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive"><u>config file</u></a>: </p>
            <pre><code>// wrangler.jsonc file
{
  "hyperdrive": [
    {
      "binding": "DATABASE",
      "id": &lt;AUTO_CREATED_ID&gt;
    }
  ]
}
</code></pre>
            <p>And start running SQL queries via your Worker with your Postgres client of choice:</p>
            <pre><code>import { Client } from "pg";

export default {
  async fetch(request, env, ctx) {
   
    const client = new Client({ connectionString: env.DATABASE.connectionString });
    await client.connect();

    const result = await client.query("SELECT * FROM pg_tables");
    ...
}
</code></pre>
            
    <div>
      <h2>PlanetScale developer experience</h2>
      <a href="#planetscale-developer-experience">
        
      </a>
    </div>
    <p>PlanetScale was the obvious choice to provide to the Workers community due to it’s unrivaled performance and reliability. Developers can choose from two of the most popular relational databases <a href="https://planetscale.com/docs/postgres/postgres-compatibility"><u>with Postgres</u></a> or Vitess MySQL. PlanetScale matches how Cloudflare treats performance and reliability as key features of a developer platform. And with features like <a href="https://planetscale.com/docs/postgres/monitoring/query-insights"><u>query insights</u></a> and <a href="https://planetscale.com/docs/connect/ai-tooling"><u>agent driven</u></a> workflows for improving SQL query performance and <a href="https://planetscale.com/docs/postgres/branching"><u>branching</u></a> for deploying code safely, including database changes, the PlanetScale database developer experience is first-class.</p><p>Cloudflare users get the exact same PlanetScale database developer experience. Your PlanetScale databases can be deployed directly from Cloudflare with connections managed via Hyperdrive, which already makes your existing regional databases fast with global Workers. This means access to the same PlanetScale <a href="https://planetscale.com/docs/plans/planetscale-skus"><u>database clusters</u></a> at standard PlanetScale <a href="https://planetscale.com/pricing"><u>pricing</u></a> with all features included like query insights and detailed breakdown of <a href="https://planetscale.com/docs/billing#organization-usage-and-billing-page"><u>usage and costs</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Pfh4oM8zQSUGJKGEsxF3W/700f627c38279d9d90337b38de72b44e/BLOG-3213_4.png" />
          </figure><p><sup><i>A single node on PlanetScale Postgres starts at </i></sup><a href="https://planetscale.com/blog/5-dollar-planetscale"><sup><i><u>$5/month</u></i></sup></a><sup><i>.</i></sup></p>
    <div>
      <h2>Workers placement</h2>
      <a href="#workers-placement">
        
      </a>
    </div>
    <p>With centralized databases, Workers can run right next to your primary database to reduce latency with an <a href="https://developers.cloudflare.com/workers/configuration/placement/#configure-explicit-placement-hints"><u>explicit placement hint</u></a>. By default, Workers execute closest to a user request, which adds network latency when querying a central database especially for multiple queries. Instead, you can configure your Worker to execute in the closest Cloudflare data center to your PlanetScale database. In the future, Cloudflare can automatically set a placement hint based on the location of your PlanetScale database and reduce network latency to single digit milliseconds.</p>
            <pre><code>{
  "placement": {
    "region": "aws:us-east-1"
  }
}
</code></pre>
            
    <div>
      <h2>Coming soon</h2>
      <a href="#coming-soon">
        
      </a>
    </div>
    <p>You can deploy a PlanetScale Postgres database or connect an existing PlanetScale database to Workers today via the <a href="https://dash.cloudflare.com/?to=/:account/workers/hyperdrive?modal=1"><u>Cloudflare dashboard</u></a>. Everything today is still billed via PlanetScale.</p><p>Launching next month, new PlanetScale databases can be billed to your Cloudflare account. </p><p>We are building more with our PlanetScale partners, such as Cloudflare API integration, so tell us what you’d like to see next.</p> ]]></content:encoded>
            <category><![CDATA[Agents Week]]></category>
            <category><![CDATA[SQL]]></category>
            <category><![CDATA[Database]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Postgres]]></category>
            <category><![CDATA[MySQL]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[PlanetScale]]></category>
            <guid isPermaLink="false">1IGJnHwj5QVRJm9iCdEqYV</guid>
            <dc:creator>Vy Ton</dc:creator>
            <dc:creator>Matt Silverlock</dc:creator>
        </item>
        <item>
            <title><![CDATA[Project Think: building the next generation of AI agents on Cloudflare]]></title>
            <link>https://blog.cloudflare.com/project-think/</link>
            <pubDate>Wed, 15 Apr 2026 13:01:00 GMT</pubDate>
            <description><![CDATA[ Announcing a preview of the next edition of the Agents SDK — from lightweight primitives to a batteries-included platform for AI agents that think, act, and persist.
 ]]></description>
            <content:encoded><![CDATA[ <p>Today, we're introducing Project Think: the next generation of the <a href="https://developers.cloudflare.com/agents/"><u>Agents SDK</u></a>. Project Think is a set of new primitives for building long-running agents (durable execution, sub-agents, sandboxed code execution, persistent sessions) and an opinionated base class that wires them all together. Use the primitives to build exactly what you need, or use the base class to get started fast.</p><p>Something happened earlier this year that changed how we think about AI. Tools like <a href="https://github.com/badlogic/pi-mono"><u>Pi</u></a>, <a href="https://github.com/openclaw"><u>OpenClaw</u></a>, <a href="https://docs.anthropic.com/en/docs/agents"><u>Claude Code</u></a>, and <a href="https://openai.com/codex"><u>Codex</u></a> proved a simple but powerful idea: give an LLM the ability to read files, write code, execute it, and remember what it learned, and you get something that looks less like a developer tool and more like a general-purpose assistant.</p><p>These coding agents aren't just writing code anymore. People are using them to manage calendars, analyze datasets, negotiate purchases, file taxes, and automate entire business workflows. The pattern is always the same: the agent reads context, reasons about it, writes code to take action, observes the result, and iterates. Code is the universal medium of action.</p><p>Our team has been using these coding agents every day. And we kept running into the same walls:</p><ul><li><p><b>They only run on your laptop or an expensive VPS:</b> there's no sharing, no collaboration, no handoff between devices.</p></li><li><p><b>They're expensive when idle</b>: a fixed monthly cost whether the agent is working or not. Scale that to a team, or a company, and it adds up fast.</p></li><li><p><b>They require management and manual setup</b>: installing dependencies, managing updates, configuring identity and secrets.</p></li></ul><p>And there's a deeper structural issue. Traditional applications serve many users from one instance. As mentioned in our Welcome to Agents Week post, <a href="https://blog.cloudflare.com/welcome-to-agents-week/"><u>agents are one-to-one</u></a>. Each agent is a unique instance, serving one user, running one task. A restaurant has a menu and a kitchen optimized to churn out dishes at volume. An agent is more like a personal chef: different ingredients, different techniques, different tools every time.</p><p>That fundamentally changes the scaling math. If a hundred million knowledge workers each use an agentic assistant at even modest concurrency, you need capacity for tens of millions of simultaneous sessions. At current per-container costs, that's unsustainable. We need a different foundation.</p><p>That's what we've been building.</p>
    <div>
      <h2>Introducing Project Think</h2>
      <a href="#introducing-project-think">
        
      </a>
    </div>
    <p>Project Think ships a set of new primitives for the Agents SDK:</p><ul><li><p><b>Durable execution</b> with fibers: crash recovery, checkpointing, automatic keepalive</p></li><li><p><b>Sub-agents</b>: isolated child agents with their own SQLite and typed RPC</p></li><li><p><b>Persistent sessions</b>: tree-structured messages, forking, compaction, full-text search</p></li><li><p><b>Sandboxed code execution</b>: Dynamic Workers, codemode, runtime npm resolution</p></li><li><p><b>The execution ladder</b>: workspace, isolate, npm, browser, sandbox</p></li><li><p><b>Self-authored extensions</b>: agents that write their own tools at runtime</p></li></ul><p>Each of these is usable directly with the Agent base class. Build exactly what you need with the primitives, or use the Think base class to get started fast. Let's look at what each one does.</p>
    <div>
      <h2>Long-running agents</h2>
      <a href="#long-running-agents">
        
      </a>
    </div>
    <p>Agents, as they exist today, are ephemeral. They run for a session, tied to a single process or device, and then they are gone. A coding agent that dies when your laptop sleeps, that’s a tool. An agent that persists — that can wake up on demand, continue work after interruptions, and carry forward the state without depending on your local runtime — that starts to look like infrastructure. And it changes the scaling model for agents completely.</p><p>The Agents SDK builds on <a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a> to give every agent an identity, persistent state, and the ability to wake on message. This is the <a href="https://en.wikipedia.org/wiki/Actor_model"><u>actor model</u></a>: each agent is an addressable entity with its own SQLite database. It consumes zero compute when hibernated. When something happens (an HTTP request, a WebSocket message, a scheduled alarm, an inbound email) the platform wakes the agent, loads its state, and hands it the event. The agent does its work, then goes back to sleep.</p><table><tr><th><p>
</p></th><th><p><b>VMs / Containers</b></p></th><th><p><b>Durable Objects</b></p></th></tr><tr><td><p><b>Idle cost</b></p></td><td><p>Full compute cost, always</p></td><td><p>Zero (hibernated)</p></td></tr><tr><td><p><b>Scaling</b></p></td><td><p>Provision and manage capacity</p></td><td><p>Automatic, per-agent</p></td></tr><tr><td><p><b>State</b></p></td><td><p>External database required</p></td><td><p>Built-in SQLite</p></td></tr><tr><td><p><b>Recovery</b></p></td><td><p>You build it (process managers, health checks)</p></td><td><p>Platform restarts, state survives</p></td></tr><tr><td><p><b>Identity / routing</b></p></td><td><p>You build it (load balancers, sticky sessions)</p></td><td><p>Built-in (name → agent)</p></td></tr><tr><td><p><b>10,000 agents, each active 1% of the time</b></p></td><td><p>10,000 always-on instances</p></td><td><p>~100 active at any moment</p></td></tr></table><p>This changes the economics of running agents at scale. Instead of "one expensive agent per power user," you can build "one agent per customer" or "one agent per task" or "one agent per email thread." The marginal cost of spawning a new agent is effectively zero.</p>
    <div>
      <h3>Surviving crashes: durable execution with fibers</h3>
      <a href="#surviving-crashes-durable-execution-with-fibers">
        
      </a>
    </div>
    <p>An LLM call takes 30 seconds. A multi-turn agent loop can run for much longer. At any point during that window, the execution environment can vanish: a deploy, a platform restart, hitting resource limits. The upstream connection to the model provider is severed permanently, in-memory state is lost, and connected clients see the stream stop with no explanation.</p><p><code></code><a href="https://developers.cloudflare.com/agents/api-reference/durable-execution/"><code><u>runFiber()</u></code></a> solves this. A fiber is a durable function invocation: registered in SQLite before execution begins, checkpointable at any point via <code>stash()</code>, and recoverable on restart via <code>onFiberRecovered</code>.</p>
            <pre><code>import { Agent } from "agents";

export class ResearchAgent extends Agent {
  async startResearch(topic: string) {
    void this.runFiber("research", async (ctx) =&gt; {
      const findings = [];

      for (let i = 0; i &lt; 10; i++) {
        const result = await this.callLLM(`Research step ${i}: ${topic}`);
        findings.push(result);

        // Checkpoint: if evicted, we resume from here
        ctx.stash({ findings, step: i, topic });

        this.broadcast({ type: "progress", step: i });
      }

      return { findings };
    });
  }

  async onFiberRecovered(ctx) {
    if (ctx.name === "research" &amp;&amp; ctx.snapshot) {
      const { topic } = ctx.snapshot;
      await this.startResearch(topic);
    }
  }
}
</code></pre>
            <p>The SDK keeps the agent alive automatically during fiber execution, no special configuration needed. For work measured in minutes, keepAlive() / keepAliveWhile() prevents eviction during active work. For longer operations (CI pipelines, design reviews, video generation) the agent starts the work, persists the job ID, hibernates, and wakes on callback.</p>
    <div>
      <h3>Delegating work: sub-agents via Facets</h3>
      <a href="#delegating-work-sub-agents-via-facets">
        
      </a>
    </div>
    <p>A single agent shouldn't do everything itself. <a href="https://developers.cloudflare.com/agents/api-reference/sub-agents/"><u>Sub-agents</u></a> are child Durable Objects colocated with the parent via <a href="https://blog.cloudflare.com/durable-object-facets-dynamic-workers/"><u>Facets</u></a>, each with their own isolated SQLite and execution context:</p>
            <pre><code>import { Agent } from "agents";

export class ResearchAgent extends Agent {
  async search(query: string) { /* ... */ }
}

export class ReviewAgent extends Agent {
  async analyze(query: string) { /* ... */ }
}

export class Orchestrator extends Agent {
  async handleTask(task: string) {
    const researcher = await this.subAgent(ResearchAgent, "research");
    const reviewer = await this.subAgent(ReviewAgent, "review");

    const [research, review] = await Promise.all([
      researcher.search(task),
      reviewer.analyze(task)
    ]);

    return this.synthesize(research, review);
  }
}
</code></pre>
            <p>Sub-agents are isolated at the storage level. Each one gets its own SQLite database, and there’s no implicit sharing of data between them. This is enforced by the runtime where sub-agent RPC latency is a function call. TypeScript catches misuse at compile time.</p>
    <div>
      <h3>Conversations that persist: the Session API</h3>
      <a href="#conversations-that-persist-the-session-api">
        
      </a>
    </div>
    <p>Agents that run for days or weeks need more than the typical flat list of messages. The experimental <a href="https://developers.cloudflare.com/agents/api-reference/sessions/"><u>Session API</u></a> models this explicitly. Available on the Agent base class, conversations are stored as trees, where each message has a parent_id. This enables forking (explore an alternative without losing the original path), non-destructive compaction (summarize older messages rather than deleting them), and full-text search across conversation history via <a href="https://www.sqlite.org/fts5.html"><u>FTS5</u></a>.</p>
            <pre><code>import { Agent } from "agents";
import { Session, SessionManager } from "agents/experimental/memory/session";

export class MyAgent extends Agent {
  sessions = SessionManager.create(this);

  async onStart() {
    const session = this.sessions.create("main");
    const history = session.getHistory();
    const forked = this.sessions.fork(session.id, messageId, "alternative-approach");
  }
}
</code></pre>
            <p>Session is usable directly with <code>Agent</code>, and it's the storage layer that the <code>Think</code> base class builds on.</p>
    <div>
      <h2>From tool calls to code execution</h2>
      <a href="#from-tool-calls-to-code-execution">
        
      </a>
    </div>
    <p>Conventional tool-calling has an awkward shape. The model calls a tool, pulls the result back through the context window, calls another tool, pulls that back, and so on. As the tool surface grows, this gets both expensive and clumsy. A hundred files means a hundred round-trips through the model.</p><p>But <a href="https://blog.cloudflare.com/code-mode/"><u>models are better at writing code to use a system than they are at playing the tool-calling game</u></a>. This is the insight behind <a href="https://github.com/cloudflare/agents/tree/main/packages/codemode"><u>@cloudflare/codemode</u></a>: instead of sequential tool calls, the LLM writes a single program that handles the entire task.</p>
            <pre><code>// The LLM writes this. It runs in a sandboxed Dynamic Worker.
const files = await tools.find({ pattern: "**/*.ts" });
const results = [];
for (const file of files) {
  const content = await tools.read({ path: file });
  if (content.includes("TODO")) {
    results.push({ file, todos: content.match(/\/\/ TODO:.*/g) });
  }
}
return results;
</code></pre>
            <p>Instead of 100 round-trips to the model, you just run a single program. This leads to fewer tokens used, faster execution, and better results. The <a href="https://github.com/cloudflare/mcp"><u>Cloudflare API MCP server</u></a> demonstrates this at scale. We expose only two tools <code>(search()</code> and <code>execute())</code>, which consume ~1,000 tokens, vs. ~1.17 million tokens for the naive tool-per-endpoint equivalent. This is a 99.9% reduction.</p>
    <div>
      <h3>The missing primitive: safe sandboxes</h3>
      <a href="#the-missing-primitive-safe-sandboxes">
        
      </a>
    </div>
    <p>Once you accept that models should write code on behalf of users, the question becomes: where does that code run? Not eventually, not after a product team turns it into a roadmap item. Right now, for this user, against this system, with tightly defined permissions.</p><p><a href="https://blog.cloudflare.com/dynamic-workers/"><u>Dynamic Workers</u></a> are that sandbox. A fresh V8 isolate spun up at runtime, in milliseconds, with a few megabytes of memory. That's roughly 100x faster and up to 100x more memory-efficient than a container. You can start a new one for every single request, run a snippet of code, and throw it away.</p><p>The critical design choice is the capability model. Instead of starting with a general-purpose machine and trying to constrain it, Dynamic Workers begin with almost no ambient authority (<code>globalOutbound: null</code>, no network access) and the developer grants capabilities explicitly, resource by resource, through bindings. We go from asking "how do we stop this thing from doing too much?" to "what exactly do we want this thing to be able to do?"</p><p>This is the right question for agent infrastructure.</p>
    <div>
      <h3>The execution ladder</h3>
      <a href="#the-execution-ladder">
        
      </a>
    </div>
    <p>This capability model leads naturally to a spectrum of compute environments, an <b>execution ladder</b> that the agent escalates through as needed:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6yokfTVcg8frH4snf7c4sp/2306d721650b4956b28e2198f7cf915d/BLOG-3200_2.png" />
          </figure><p><b>Tier 0</b> is the Workspace, a durable virtual filesystem backed by SQLite and R2. Read, write, edit, search, grep, diff. Powered by <a href="https://www.npmjs.com/package/@cloudflare/shell"><code><u>@cloudflare/shell</u></code></a>.</p><p><b>Tier 1</b> is a Dynamic Worker: LLM-generated JavaScript running in a sandboxed isolate with no network access. Powered by <a href="https://www.npmjs.com/package/@cloudflare/codemode"><code><u>@cloudflare/codemode</u></code></a>.</p><p><b>Tier 2</b> adds npm. <a href="https://github.com/cloudflare/agents/tree/main/packages/worker-bundler"><code><u>@cloudflare/worker-bundler</u></code></a> fetches packages from the registry, bundles them with esbuild, and loads the result into the Dynamic Worker. The agent writes <code>import { z } from "zod"</code> and it just works.</p><p><b>Tier 3</b> is a headless browser via <a href="https://developers.cloudflare.com/browser-rendering/"><u>Cloudflare Browser Run</u></a>. Navigate, click, extract, screenshot. Useful when the service doesn't support agents yet via MCP or APIs.</p><p><b>Tier 4</b> is a <a href="https://developers.cloudflare.com/sandbox/"><u>Cloudflare Sandbox</u></a> configured with your toolchains, repos, and dependencies: <code>git clone, npm test, cargo build</code>, synced bidirectionally with the Workspace.</p><p>The key design principle: <b>the agent should be useful at Tier 0 alone, where each tier is additive.</b> The user can add capabilities as they go.</p>
    <div>
      <h3>Building blocks, not a framework</h3>
      <a href="#building-blocks-not-a-framework">
        
      </a>
    </div>
    <p>All of these primitives are available as standalone packages. <a href="https://blog.cloudflare.com/dynamic-workers/"><u>Dynamic Workers</u></a>, <a href="https://github.com/cloudflare/agents/tree/main/packages/codemode"><code><u>@cloudflare/codemode</u></code></a>, <a href="https://github.com/cloudflare/agents/tree/main/packages/worker-bundler"><code><u>@cloudflare/worker-bundler</u></code></a>, and <a href="https://www.npmjs.com/package/@cloudflare/shell"><code><u>@cloudflare/shell</u></code></a> (a durable filesystem with tools) are all usable directly with the Agent base class. You can combine them to give any agent a workspace, code execution, and runtime package resolution without adopting an opinionated framework.</p>
    <div>
      <h2>The platform</h2>
      <a href="#the-platform">
        
      </a>
    </div>
    <p>Here's the complete stack for building agents on Cloudflare:</p><table><tr><th><p><b>Capability</b></p></th><th><p><b>What it does</b></p></th><th><p><b>Powered by</b></p></th></tr><tr><td><p>Per-agent isolation</p></td><td><p>Every agent is its own world</p></td><td><p><a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a> (DOs)</p></td></tr><tr><td><p>Zero cost when idle</p></td><td><p>$0 until the agent wakes up</p></td><td><p><a href="https://developers.cloudflare.com/durable-objects/best-practices/websockets/#websocket-hibernation-api"><u>DO Hibernation</u></a></p></td></tr><tr><td><p>Persistent state</p></td><td><p>Queryable, transactional storage</p></td><td><p><a href="https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/"><u>DO SQLite</u></a></p></td></tr><tr><td><p>Durable filesystem</p></td><td><p>Files that survive restarts</p></td><td><p>Workspace (SQLite + <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>)</p></td></tr><tr><td><p>Sandboxed code execution</p></td><td><p>Run LLM-generated code safely</p></td><td><p><a href="https://blog.cloudflare.com/dynamic-workers/"><u>Dynamic Workers</u></a> + <a href="https://github.com/cloudflare/agents/tree/main/packages/codemode"><code><u>@cloudflare/codemode</u></code></a></p></td></tr><tr><td><p>Runtime dependencies</p></td><td><p><code>import * from react</code> just works</p></td><td><p><a href="https://github.com/cloudflare/agents/tree/main/packages/worker-bundler"><code><u>@cloudflare/worker-bundler</u></code></a></p></td></tr><tr><td><p>Web automation</p></td><td><p>Browse, navigate, fill forms</p></td><td><p><a href="https://developers.cloudflare.com/browser-rendering/"><u>Browser Run</u></a></p></td></tr><tr><td><p>Full OS access</p></td><td><p>git, compilers, test runners</p></td><td><p><a href="https://developers.cloudflare.com/sandbox/"><u>Sandboxes</u></a></p></td></tr><tr><td><p>Scheduled execution</p></td><td><p>Proactive, not just reactive</p></td><td><p><a href="https://developers.cloudflare.com/durable-objects/api/alarms/"><u>DO Alarms + Fibers</u></a></p></td></tr><tr><td><p>Real-time streaming</p></td><td><p>Token-by-token to any client</p></td><td><p>WebSockets</p></td></tr><tr><td><p>External tools</p></td><td><p>Connect to any tool server</p></td><td><p>MCP</p></td></tr><tr><td><p>Agent coordination</p></td><td><p>Typed RPC between agents</p></td><td><p>Sub-agents (<a href="https://developers.cloudflare.com/dynamic-workers/usage/durable-object-facets/"><u>Facets</u></a>)</p></td></tr><tr><td><p>Model access</p></td><td><p>Connect to an LLM to power the agent</p></td><td><p><a href="https://developers.cloudflare.com/ai-gateway/"><u>AI Gateway</u></a> + <a href="https://developers.cloudflare.com/workers-ai/"><u>Workers AI</u></a> (or Bring Your Own Model)</p></td></tr></table><p>Each of these is a building block. Together, they form something new: a platform where anyone can build, deploy, and run AI agents as capable as the ones running on your local machine today, but <a href="https://www.cloudflare.com/learning/serverless/what-is-serverless/"><u>serverless</u></a>, durable, and safe by construction. </p>
    <div>
      <h2>The Think base class</h2>
      <a href="#the-think-base-class">
        
      </a>
    </div>
    <p>Now that you've seen the primitives, here's what happens when you wire them all together.</p><p><code>Think</code> is an opinionated harness that handles the full chat lifecycle: agentic loop, message persistence, streaming, tool execution, stream resumption, and extensions. You focus on what makes your agent unique.</p><p>The minimal subclass looks like this:</p>
            <pre><code>import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";

export class MyAgent extends Think&lt;Env&gt; {
  getModel() {
    return createWorkersAI({ binding: this.env.AI })(
      "@cf/moonshotai/kimi-k2.5"
    );
  }
}
</code></pre>
            <p>That’s effectively all you need to have a working chat agent with streaming, persistence, abort/cancel, error handling, resumable streams, and a built-in workspace filesystem. Deploy with <code>npx wrangler deploy</code>.</p><p>Think makes decisions for you. When you need more control, you can override the ones you care about:</p><table><tr><td><p><b>Override</b></p></td><td><p><b>Purpose</b></p></td></tr><tr><td><p><code>getModel()</code></p></td><td><p>Return the <code>LanguageModel</code> to use</p></td></tr><tr><td><p><code>getSystemPrompt()</code></p></td><td><p>System prompt</p></td></tr><tr><td><p><code>getTools()</code></p></td><td><p>AI SDK compatible <code>ToolSet</code> for the agentic loop</p></td></tr><tr><td><p><code>maxSteps</code></p></td><td><p>Max tool-call rounds per turn</p></td></tr><tr><td><p><code>configureSession()</code></p></td><td><p>Context blocks, compaction, search, skills</p></td></tr></table><p>Under the hood, Think runs the complete agentic loop on every turn: it assembles the context (base instructions + tool descriptions + skills + memory + conversation history), calls <code>streamText</code>, executes tool calls (with output truncation to prevent context blowup), appends results, loops until the model is done or the step limit is reached. All messages are persisted after each turn.</p>
    <div>
      <h3>Lifecycle hooks</h3>
      <a href="#lifecycle-hooks">
        
      </a>
    </div>
    <p>Think gives you hooks at every stage of the chat turn, without requiring you to own the whole pipeline:</p>
            <pre><code>beforeTurn()
  → streamText()
    → beforeToolCall()
    → afterToolCall()
  → onStepFinish()
→ onChatResponse()
</code></pre>
            <p>Switch to a lower cost model for follow-up turns, limit the tools it can use, and pass in client-side context on each turn. Also log every tool call to analytics and automatically trigger one more follow-up turn after the model completes, all without replacing <code>onChatMessage</code>.</p>
    <div>
      <h3>Persistent memory and long conversations</h3>
      <a href="#persistent-memory-and-long-conversations">
        
      </a>
    </div>
    <p>Think builds on <a href="https://developers.cloudflare.com/agents/api-reference/sessions/?cf_target_id=E7A3D837FA7DC4C7DDA822B3DE0F831B"><u>Session API</u></a> as its storage layer, giving you tree-structured messages with branching built in.</p><p>On top of that, it adds persistent memory through <b>context blocks</b>. These are structured sections of the system prompt that the model can read and update over time, and they persist across hibernation<b>.</b>The model sees "MEMORY (Important facts, use set_context to update) [42%, 462/1100 tokens]" and can proactively remember things.</p>
            <pre><code>configureSession(session: Session) {
  return session
    .withContext("soul", {
      provider: { get: async () =&gt; "You are a helpful coding assistant." }
    })
    .withContext("memory", {
      description: "Important facts learned during conversation.",
      maxTokens: 2000
    })
    .withCachedPrompt();
}
</code></pre>
            <p>Sessions are flexible. You can run multiple conversations per agent and fork them to try a different direction without losing the original.<b> </b></p><p>As context grows, Think handles limits with non-destructive compaction. Older messages are summarized instead of removed, while the full history remains stored in SQLite.<b> </b></p><p>Search is built in as well. Using FTS5, you can query conversation history within a session or across all the sessions. The agent is also able to search its own past using<b> </b><code>search_context</code> tool.</p>
    <div>
      <h3>The full execution ladder, wired in</h3>
      <a href="#the-full-execution-ladder-wired-in">
        
      </a>
    </div>
    <p>Think integrates the entire execution ladder into a single <code>getTools()</code> return:</p>
            <pre><code>import { Think } from "@cloudflare/think";
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
import { createExecuteTool } from "@cloudflare/think/tools/execute";
import { createBrowserTools } from "@cloudflare/think/tools/browser";
import { createSandboxTools } from "@cloudflare/think/tools/sandbox";
import { createExtensionTools } from "@cloudflare/think/tools/extensions";

export class MyAgent extends Think&lt;Env&gt; {
  extensionLoader = this.env.LOADER;

  getModel() {
    /* ... */
  }

  getTools() {
    return {
      execute: createExecuteTool({
        tools: createWorkspaceTools(this.workspace),
        loader: this.env.LOADER
      }),
      ...createBrowserTools(this.env.BROWSER),
      ...createSandboxTools(this.env.SANDBOX), // configured per-agent: toolchains, repos, snapshots
      ...createExtensionTools({ manager: this.extensionManager! }),
      ...this.extensionManager!.getTools()
    };
  }
}
</code></pre>
            
    <div>
      <h3>Self-authored extensions</h3>
      <a href="#self-authored-extensions">
        
      </a>
    </div>
    <p>Think takes code execution one step further. An agent can write its own extensions: TypeScript programs that run in Dynamic Workers, declaring permissions for network access and workspace operations.</p>
            <pre><code>{
  "name": "github",
  "description": "GitHub integration: PRs, issues, repos",
  "tools": ["create_pr", "list_issues", "review_pr"],
  "permissions": {
    "network": ["api.github.com"],
    "workspace": "read-write"
  }
}
</code></pre>
            <p>Think's <code>ExtensionManager</code> bundles the extension (optionally with npm deps via <code>@cloudflare/worker-bundler</code>), loads it into a Dynamic Worker, and registers the new tools. The extension persists in DO storage and survives hibernation. The next time the user asks about pull requests, the agent has a <code>github_create_pr </code>tool that didn't exist 30 seconds ago.</p><p>This is the kind of self-improvement loop that makes agents genuinely more useful over time. Not through fine-tuning or RLHF, but through code. The agent is able to write new capabilities for itself, all in sandboxed, auditable, and revocable TypeScript.</p>
    <div>
      <h3>Sub-agent RPC</h3>
      <a href="#sub-agent-rpc">
        
      </a>
    </div>
    <p>Think also works as a sub-agent, called via <code>chat()</code> over RPC from a parent, with streaming events via callback:</p>
            <pre><code>const researcher = await this.subAgent(ResearchSession, "research");
const result = await researcher.chat(`Research this: ${task}`, streamRelay);
</code></pre>
            <p>Each child gets its own conversation tree, memory, tools, and model. The parent doesn't need to know the details.</p>
    <div>
      <h3>Getting started</h3>
      <a href="#getting-started">
        
      </a>
    </div>
    <p>Project Think is experimental. The API surface is stable but will continue to evolve in the coming days and weeks. We're already using it internally to build our own background agent infrastructure, and we're sharing it early so you can build alongside us.</p>
            <pre><code>npm install @cloudflare/think agents ai @cloudflare/shell zod workers-ai-provider</code></pre>
            
            <pre><code>// src/server.ts
import { Think } from "@cloudflare/think";
import { createWorkersAI } from "workers-ai-provider";
import { routeAgentRequest } from "agents";

export class MyAgent extends Think&lt;Env&gt; {
  getModel() {
    return createWorkersAI({ binding: this.env.AI })(
      "@cf/moonshotai/kimi-k2.5"
    );
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return (
      (await routeAgentRequest(request, env)) ||
      new Response("Not found", { status: 404 })
    );
  }
} satisfies ExportedHandler&lt;Env&gt;;
</code></pre>
            
            <pre><code>// src/client.tsx
import { useAgent } from "agents/react";
import { useAgentChat } from "@cloudflare/ai-chat/react";

function Chat() {
  const agent = useAgent({ agent: "MyAgent" });
  const { messages, sendMessage, status } = useAgentChat({ agent });
  // Render your chat UI
}
</code></pre>
            <p>Think speaks the same WebSocket protocol as <code>@cloudflare/ai-chat</code>, so existing UI components work out of the box. If you've built on <a href="https://developers.cloudflare.com/agents/api-reference/chat-agents/"><code><u>AIChatAgent</u></code></a>, your client code doesn't change.</p>
    <div>
      <h2>The third wave</h2>
      <a href="#the-third-wave">
        
      </a>
    </div>
    <p>We see three waves of AI agents:</p><p><b>The first wave was chatbots.</b> They were stateless, reactive, and fragile. Every conversation started from scratch with no memory, no tools, and no ability to act. This made them useful for answering questions, but limited them to only answering questions.</p><p><b>The second wave was coding agents.</b> These are stateful, tool-using and far more capable tools like Pi, Claude Code, OpenClaw, and Codex. These agents can read codebases, write code, execute it, and iterate. These proved that an LLM with the right tools is a general-purpose machine, but they run on your laptop, for one user, with no durability guarantees.</p><p><b>Now we are entering the third wave: agents as infrastructure.</b> Durable, distributed, structurally safe, and serverless. These are agents that run on the Internet, survive failures, cost nothing when idle, and enforce security through architecture rather than behavior. Agents that any developer can build and deploy for any number of users.</p><p>This is the direction we’re betting on.</p><p>The Agents SDK is already powering thousands of production agents. With Project Think and the the primitives it introduces, we're adding the missing pieces to make those agents dramatically more capable: persistent workspaces, sandboxed code execution, durable long-running tasks, structural security, sub-agent coordination, and self-authored extensions.</p><p>It's available today in preview. We're building alongside you, and we'd genuinely love to see what you (and your coding agent) create with it.</p><hr /><p><sup><i>Think is part of the Cloudflare Agents SDK, available as @cloudflare/think. The features described in this post are in preview. APIs may change as we incorporate feedback. Check the </i></sup><a href="https://github.com/cloudflare/agents/blob/main/docs/think/index.md"><sup><i><u>documentation</u></i></sup></a><sup><i> and </i></sup><a href="https://github.com/cloudflare/agents/tree/main/examples/assistant"><sup><i><u>example</u></i></sup></a><sup><i> to get started.</i></sup></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/161Wz7Tf8Cpzn2u2cBCH3V/37633c016734590005edd280732e89b9/BLOG-3200_3.png" />
          </figure><p></p> ]]></content:encoded>
            <category><![CDATA[Agents Week]]></category>
            <category><![CDATA[Agents]]></category>
            <category><![CDATA[Storage]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Durable Objects]]></category>
            <category><![CDATA[AI]]></category>
            <guid isPermaLink="false">3r2ykMs0LTSPwVHmVWldCy</guid>
            <dc:creator>Sunil Pai</dc:creator>
            <dc:creator>Kate Reznykova</dc:creator>
        </item>
        <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[Agents Week]]></category>
            <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>
    </channel>
</rss>