Consistency and patterns

Choose KV patterns that fit

Use KV for cacheable configuration and preferences while avoiding correctness-critical locks, quotas, and hot counters.

Good KV patterns share one property: a brief stale read does not break 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. 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’s 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 publish a read-friendly result to KV if you need cheap reads everywhere:

// 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.

Try this: 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 part of a paid contract.

Lesson completed