BuildBot

Compute at the Edge

KV & caching

Lesson 3 of 5

What you'll learn

  • Read and write Workers KV, with expirationTtl for self-cleaning keys
  • Reason about KV's eventual consistency instead of being surprised by it
  • Use the Cache API with ctx.waitUntil, and choose between KV and cache deliberately

Workers KV is the platform's global key-value store. Declare a namespace binding in config and it appears on env with an async get/put/delete API:

export default {
  async fetch(request, env) {
    const flags = await env.CONFIG.get("feature-flags", { type: "json" });
    await env.CONFIG.put("last-seen", new Date().toISOString(), {
      expirationTtl: 3600, // seconds; the key deletes itself in an hour (minimum 60)
    });
    return Response.json(flags ?? {});
  },
};

expirationTtl is how KV data stays fresh without a cron job: cached API responses, sessions, rate-limit windows — write once, let the key expire.

Eventual consistency, eyes open

KV is optimized for read-heavy workloads: reads are served from the data center handling the request and are extremely fast once hot. The price is that writes are eventually consistent — a put in Frankfurt can take up to ~60 seconds to be visible in Sydney, and if two data centers write the same key concurrently, the last write wins. That's perfect for config, feature flags, and cached content, and wrong for counters, inventory, or anything where two writers race. (Lesson 4 covers what to use when you do need coordination.)

The Cache API

Separately from KV, every data center has an HTTP cache your Worker can use directly — caches.default, keyed by Request, storing Response objects. The idiom pairs it with ctx.waitUntil, which lets work continue after you've returned the response:

export default {
  async fetch(request, env, ctx) {
    const cache = caches.default;
    let response = await cache.match(request);
    if (response) return response;

    response = await fetch("https://origin.example.com" + new URL(request.url).pathname);
    response = new Response(response.body, response);
    response.headers.set("Cache-Control", "s-maxage=300");
    ctx.waitUntil(cache.put(request, response.clone())); // don't make the user wait for the write
    return response;
  },
};

KV or cache? Ask two questions

Is the data an HTTP response, and is per-location caching fine? Use the Cache API — it's free, fast, and evicts on its own, but each data center has its own copy. Do you need the value available everywhere, on your own keys, with TTLs you control? Use KV. Plenty of Workers use both: cache in front, KV behind.

A KV-backed cache with expirationTtl

Run it. The stub implements KV's real get/put contract including expirationTtl. The first request computes and writes; the second is a KV hit; after the TTL passes, the key has expired and the Worker recomputes.

Loading editor…
Knowledge check

A Worker in Frankfurt puts a new value to KV. What might a read in Sydney see moments later?

Next: real state at the edge — D1's SQLite and Durable Objects, and how to pick between them.

Saved on this device. Sign in to sync your progress everywhere.