KV is a cache with sixty-second edges, not a database

A write is visible at once where it happened and within about 60 s elsewhere; a key takes one write per second. Right for config and keys, wrong for counters.

KV/R2/D1

· Chapter

48

·

3

min read

The answer. Workers KV is a central store with a cache in every Cloudflare location. Reads are fast because they are cache hits; writes go to the center and invalidate outward, which is why a value can differ between two locations for up to a minute (or the cacheTtl you passed on read). That makes KV ideal for things read a thousand times per write: feature flags, signing keys, rendered fragments, per-tenant config. It makes KV wrong for anything that needs read-after-write or coordination: counters, locks, inventory, "was this token already used". Concurrent writes to one key are last-writer-wins, and the same key accepts one write per second before returning 429. When you need the strong version, that is a Durable Object; when you need SQL, that is D1.

The pattern.

await env.CACHE.put("jwks:v1", json, { expirationTtl: 3600, metadata: { fetchedAt: Date.now() } });
const { value, metadata } = await env.CACHE.getWithMetadata<{ fetchedAt: number }>("jwks:v1", { cacheTtl: 300 });
const keys = await env.CACHE.list({ prefix: "site:", limit: 100 });      // paginate with list_complete / cursor
Fact Value
Consistency eventual; same location immediate, elsewhere up to 60 s or cacheTtl
cacheTtl on reads min 30 s, default 60 s
Writes to one key 1 per second (429 above)
Key / value / metadata 512 B / 25 MiB / 1 KiB
Free plan per day 100k reads, 1k writes, 1 GB stored

Watch out.

  • cacheTtl pins a stale value in that location for the whole window, including negative lookups (a missing key stays "missing").
  • list() is paginated and billed per call; do not scan a namespace on every request.
  • Bulk writes (up to 10,000 pairs) exist only on the REST API, not the binding.

Related: kv-cache-upstream-json-with-ttl-and-stale-fallback · wrangler-kv-r2-default-local-use-remote · durable-object-one-instance-per-id-with-sqlite