Storage · Workers KV

Fast global reads with Workers KV

After this lesson you'll be able to read and write Workers KV from a Worker, reason about its eventual-consistency window instead of getting surprised by it, and pick KV correctly (or reject it) for a given workload.

Workers KV is a key-value store replicated across Cloudflare's edge, built for workloads that are read constantly and written rarely. A value written to KV is cached close to wherever it's read, so a Worker in Tokyo and a Worker in Frankfurt can both fetch the same key with single-digit-millisecond latency — but that speed comes from a deliberate tradeoff: KV is eventually consistent, not strongly consistent. Understanding that tradeoff is most of what you need to use KV well.

How it works: cached at the edge, eventually consistent

KV's storage is centralized, but reads are served from a hybrid push/pull cache at the edge location closest to the request. The first read of a key at a given location pulls it from central storage and caches it there; subsequent reads at that location are served from the local cache. When you write a new value, it's usually visible immediately at the location where the write happened — but other locations that already have a cached copy keep serving their cached version until it expires, which can take up to 60 seconds or more. This applies symmetrically: deleting a key can leave other locations still returning the old value, and a location that previously got a "not found" for a key can keep returning "not found" for a while after the key is created.

This is not a bug to work around — it's the mechanism that makes KV fast. Cloudflare trades a bounded staleness window for the ability to serve reads from cache at every edge location instead of round-tripping to a single source of truth on every request. If your access pattern is read-heavy and a few seconds (occasionally up to a minute) of staleness is tolerable, this trade is free performance. If it isn't tolerable, KV is the wrong tool — see the pitfall below.

Concretely: if Worker A in São Paulo writes a key, and Worker B in Sydney read that same key 10 seconds ago (and cached it), Worker B can keep seeing the old value for up to roughly a minute after A's write, even though both are hitting "the same" KV namespace. Same namespace, different cached snapshots, temporarily.

Worked example: reading and writing from a Worker

Bind a namespace in wrangler.jsonc:

{
  "kv_namespaces": [
    { "binding": "CONFIG", "id": "<your-namespace-id>" }
  ]
}

Create the namespace once with wrangler, then read and write it through the binding:

npx wrangler kv namespace create CONFIG
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    if (url.pathname === "/flag") {
      // type: "json" parses the stored string for you;
      // cacheTtl tells this edge location how long it may serve
      // its local cached copy before re-checking (minimum 60s).
      const flags = await env.CONFIG.get("feature-flags", {
        type: "json",
        cacheTtl: 300,
      });
      return Response.json(flags ?? {});
    }

    if (url.pathname === "/session" && request.method === "POST") {
      const { userId } = await request.json();
      const token = crypto.randomUUID();

      // expirationTtl is in seconds from now; KV enforces a 60s minimum.
      // The key self-deletes — no cleanup job needed.
      await env.CONFIG.put(`session:${token}`, JSON.stringify({ userId }), {
        expirationTtl: 60 * 60 * 24, // 24 hours
      });

      return Response.json({ token });
    }

    return new Response("Not found", { status: 404 });
  },
};

put(key, value, options) accepts either expirationTtl (seconds from now, minimum 60) or expiration (an absolute Unix timestamp) — use whichever reads more naturally at the call site. Values are capped at 25 MiB, keys at 512 bytes, and KV enforces roughly one write per second to the same key before returning a 429, which is another sign it's built for infrequent writes, not a hot counter.

Pricing

Free plan (per day): 100,000 reads, 1,000 writes, 1,000 deletes, 1,000 list operations, 1 GB stored.

Paid plan (Workers Paid, per month, then metered overage): 10 million reads included ($0.50/million after), 1 million writes/deletes/list operations each included ($5.00/million after), 1 GB storage included ($0.50/GB-month after). Every operation counts toward billing even if it returns null or 404 — a miss still costs a read. There's no data transfer charge.

These numbers move over time — verify against the live page before quoting them to someone else: developers.cloudflare.com/kv/platform/pricing.

Use cases

Pitfall: reaching for KV when you need strong consistency. A very common mistake is using KV for things like inventory counts, payment state, or "read your own write" flows — write a value, then immediately read it back somewhere else and expect the update to be there. KV does not guarantee that. If correctness depends on every reader seeing the latest write immediately, use D1 (for relational/transactional data) or Durable Objects (for single-threaded, strongly consistent per-key state, like a counter or a lock). Reach for KV specifically because you've decided eventual consistency is fine — not by default.
Primary source

Cloudflare Workers KV — How KV works is the authoritative explanation of the consistency model and propagation timing; pair it with the KV API reference for method signatures and KV pricing for current rates.

A Worker writes a new value to a KV key, then a Worker running in a different region reads that same key one second later. What should you expect?
Without scrolling up: why is KV eventually consistent instead of strongly consistent, and what should you use instead if your data needs read-after-write guarantees?
Reveal

KV caches values at edge locations to serve reads with very low latency from wherever the request lands, rather than always routing to one source of truth. That caching is what makes writes take up to ~60 seconds to propagate everywhere — it's a deliberate tradeoff of staleness for read speed, aimed at read-heavy, write-rare workloads.

If you need every reader to see the latest write immediately, use D1 (relational, transactional) or Durable Objects (strongly consistent per-key actor state) instead of KV.

Anything above unclear — the propagation window, expirationTtl vs. expiration, or when to pick D1/Durable Objects over KV — ask your AI teacher before moving on.
← Previous: Direct uploads and public access patterns in R2 Next: Decouple work with Queues →