Consistency and patterns
Choose KV patterns that fit
Use KV for cacheable configuration and preferences while avoiding correctness-critical locks, quotas, and hot counters.
8 minute lesson
Good KV patterns share one property: a brief stale read does not violate a hard rule. That single test sorts almost every design decision.
Public settings, feature configuration, derived responses, and user preferences fit. If a preference change lands 30 seconds late in another region, nothing breaks and nobody notices.
A strict rate limit, an inventory decrement, a uniqueness check, or a distributed lock does not fit KV alone. Each of those has a hard rule — never over the limit, never below zero, never two of the same — and stale reads plus non-atomic writes break hard rules.
A worked example: rate limiting
I built rate limiting on KV for a real product, and it works because I chose a soft rule. The design counts requests in fixed time windows:
const window = Math.floor(Date.now() / 1000 / windowSeconds)
const kvKey = `rl:roast:${callerId}:${window}`
const count = parseInt(await env.CACHE.get(kvKey) ?? '0', 10)
if (count >= limit) return { allowed: false }
await env.CACHE.put(kvKey, String(count + 1), {
expirationTtl: Math.max(60, windowSeconds),
})
The increment is not atomic. Two concurrent requests can both read 0 and both write 1, so a limit of five sometimes admits six. For “stop a bot from hammering a free endpoint,” that is acceptable — this is abuse protection, not a billing-grade quota. The same code applied to a paid API quota would be a bug.
The hybrid pattern
When part of the job needs correctness, split it. Use a Durable Object or a D1 transaction for the authoritative operation, then optionally publish a read-friendly result to KV:
// Durable Object decides, atomically
const decision = await stub.consumeQuota(userId)
// KV serves the cheap, global "current plan" read
const plan = await env.CONFIG.get(`plan:${userId}`, 'json')
The strong store owns the rule. KV owns the fan-out. You get atomicity where it matters and cheap global reads everywhere else, instead of forcing one store to do both jobs badly.
Now review the rate-limiting design above and mark each part as approximate or strict: the counter, the window rollover, the TTL cleanup, and the block decision. Then name which parts would need stronger coordination if the limit became contractual.
Lesson completed