03 — Workers KV: Eventually Consistent Global Key-Value
"A key-value store, but at the edge" was my KV summary, and it made the consistency surprises look like bugs. The model that pinned them down: Workers KV is an eventually consistent, read-heavy global key-value store, optimized for the case where the same value is read far more often than it's written. [1] Once I internalized _eventually consistent_, every weird behavior fell into a predictable pattern.
The framing that clicked is the access pattern. KV is built for data that's read constantly, written rarely, and where stale-for-a-few-seconds is tolerable [1][3]. Configuration, feature flags, session tokens, precomputed lookup tables — these fit. The current balance of a bank account, an inventory count, anything that must be exactly right at every edge the instant it changes — these do not fit, and that's what Durable Objects or D1 are for.
Key-value operations
The API is deliberately tiny — three operations cover most of what a Worker does with KV [2][4]:
await env.MY_KV.put("user:42", JSON.stringify(profile));
const profile = await env.MY_KV.get("user:42");
await env.MYKV.delete("user:42");That's it for the basics. get returns a string by default, or a ReadableStream/ArrayBuffer if you ask for binary. put accepts a string, a stream, or an ArrayBuffer, plus an optional TTL in seconds (expirationTtl) or absolute timestamp (expirationTime) — useful for data that should self-destruct.
The mental note I keep: every get is a network call to the nearest edge copy, not an in-memory lookup. It's fast (single-digit milliseconds from a warm edge cache), but it's not free. If a handler reads the same key multiple times, it should read it once into a local variable.
Metadata handling
Every request carries a standard set of metadata available in the handler — method, URL, headers, CF-specific properties like cf.country (the user's country, derived from their IP) [5]. KV values can carry their own metadata too: put accepts a metadata object, and getWithMetadata returns both the value and its metadata in one call. This is how I attach lightweight context — a content type, a version, a tag — without parsing the value body.
There's also the scheduled event handler, distinct from fetch, which fires on a cron trigger [6]. KV is a natural fit for scheduled jobs: a cron Worker refreshes a cache every five minutes, and reads stay sub-millisecond the rest of the time.
Bulk operations
For loading or updating many keys at once, KV exposes bulk APIs that batch many operations into a single request [7]:
await env.MY_KV.bulkPut([
{ key: "a", value: "1" },
{ key: "b", value: "2" },
]);
await env.MY_KV.bulkGet(["a", "b", "c"]);This is the right tool for initial data import, large cache refreshes, or migration. It avoids the latency of issuing hundreds of individual put calls sequentially. Bulk writes are subject to per-batch size limits, so for very large datasets the pattern is chunked batches in a loop.
Caching patterns
KV is itself a cache-shaped store, and it pairs with Workers' Cache API to cover two layers [8]:
- Cache API — per-edge, in-memory/short-TTL cache of a Response. Fastest, but each edge has its own copy.
- KV — global, eventually consistent, longer-lived. One authoritative value propagates to all edges.
The combined pattern for read-heavy data is: check the Cache API first (free, instant if hit); on miss, check KV; on KV miss, fetch from origin and write back to both layers. The Cache API catches the hottest reads at no cost; KV catches the rest globally; origin is the last resort.
The three cache strategies from earlier (cache-first, network-first, stale-while-revalidate) all apply here, and they compose with KV cleanly. Stale-while-revalidate is the one I reach for most: serve whatever the edge has immediately, refresh from origin in the background.
Where KV fits (and where it doesn't)
The decision that took me longest to internalize was the consistency boundary. KV is eventually consistent: after a put, the new value propagates to edge locations over the next ~60 seconds (a Cloudflare-documented bound) [1][3]. During that window, different edges may serve different values. This is fine for a feature flag (every edge gets the new flag within a minute) and unacceptable for a payment (every edge must agree, immediately).
The rule I use:
- Use KV for: configuration, feature flags, session lookup, A/B test assignments, rate-limit counters that tolerate drift, cached rendered output, lookup tables that change rarely.
- Don't use KV for: anything transactional, anything where two writes must agree immediately, anything where reading a stale value causes real harm.
For the cases KV doesn't cover, the answer is either D1 (SQL, per-database consistency) or Durable Objects (single-actor, strongly consistent), covered in later notes.
How I use this
The pattern that crystallized is "KV as the first read-through layer." Almost every Worker I write that needs state starts with a KV namespace for its hot reads — config, cached upstream responses, session lookups — and only reaches for D1 or Durable Objects when the consistency requirement actually demands it. Getting the consistency model right up front is the whole game; the API itself is small enough to learn in an afternoon.
References
[1] Cloudflare, "Workers KV Documentation," Cloudflare Docs, 2024. [Online]. Available: https://developers.cloudflare.com/kv/
[2] Cloudflare, "Read key value pairs," Cloudflare KV API, 2024. [Online]. Available: https://developers.cloudflare.com/kv/api/read-key-value-pairs/
[3] Cloudflare, "Workers KV Runtime API," Cloudflare Workers Runtime APIs, 2024. [Online]. Available: https://developers.cloudflare.com/workers/runtime-apis/kv
[4] Cloudflare, "Cloudflare Workers KV — Cloudflare Docs," Cloudflare Workers Runtime APIs. [Online]. Available: https://developers.cloudflare.com/workers/runtime-apis/kv/
[5] Cloudflare, "Request and Response," Cloudflare Workers Runtime APIs, 2024. [Online]. Available: https://developers.cloudflare.com/workers/runtime-apis/request
[6] Cloudflare, "Scheduled event handler," Cloudflare Workers Runtime APIs, 2024. [Online]. Available: https://developers.cloudflare.com/workers/runtime-apis/scheduled-event
[7] Cloudflare, "Workers KV is GA," Cloudflare Blog. [Online]. Available: https://blog.cloudflare.com/workers-kv-is-ga/
[8] Cloudflare, "How the cache works · Cloudflare Workers," Cloudflare Docs, 2024. [Online]. Available: https://developers.cloudflare.com/workers/reference/how-the-cache-works/
Knowledge check · Question 1 of 5
After a `put()` to Workers KV, when is the new value visible at all edges?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!