Files
dotfiles/.agents/skills/cloudflare-deploy/references/kv/gotchas.md
2026-03-17 16:53:22 -07:00

4.4 KiB

KV Gotchas & Troubleshooting

Common Errors

"Stale Read After Write"

Cause: Eventual consistency means writes may not be immediately visible in other regions
Solution: Don't read immediately after write; return confirmation without reading or use the local value you just wrote. Writes visible immediately in same location, ≤60s globally

// ❌ BAD: Read immediately after write
await env.KV.put("key", "value");
const value = await env.KV.get("key"); // May be null in other regions!

// ✅ GOOD: Use the value you just wrote
const newValue = "value";
await env.KV.put("key", newValue);
return new Response(newValue); // Don't re-read

"429 Rate Limit on Concurrent Writes"

Cause: Multiple concurrent writes to same key exceeding 1 write/second limit
Solution: Use sequential writes, unique keys for concurrent operations, or implement retry with exponential backoff

async function putWithRetry(
  kv: KVNamespace,
  key: string,
  value: string,
  maxAttempts = 5
): Promise<void> {
  let delay = 1000;
  for (let i = 0; i < maxAttempts; i++) {
    try {
      await kv.put(key, value);
      return;
    } catch (err) {
      if (err instanceof Error && err.message.includes("429")) {
        if (i === maxAttempts - 1) throw err;
        await new Promise(r => setTimeout(r, delay));
        delay *= 2; // Exponential backoff
      } else {
        throw err;
      }
    }
  }
}

"Inefficient Multiple Gets"

Cause: Making multiple individual get() calls instead of bulk operation
Solution: Use bulk get with array of keys: env.USERS.get(["user:1", "user:2", "user:3"]) to reduce to 1 operation

"Null Reference Error"

Cause: Attempting to use value without checking for null when key doesn't exist
Solution: Always handle null returns - KV returns null for missing keys, not undefined

// ❌ BAD: Assumes value exists
const config = await env.KV.get("config", "json");
return config.theme; // TypeError if null!

// ✅ GOOD: Null checks
const config = await env.KV.get("config", "json");
return config?.theme ?? "default";

// ✅ GOOD: Early return
const config = await env.KV.get("config", "json");
if (!config) return new Response("Not found", { status: 404 });
return new Response(config.theme);

"Negative Lookup Caching"

Cause: Keys that don't exist are cached as "not found" for up to 60s
Solution: Creating a key after checking won't be visible until cache expires

// Check → create pattern has race condition
const exists = await env.KV.get("key"); // null, cached as "not found"
if (!exists) {
  await env.KV.put("key", "value");
  // Next get() may still return null for ~60s due to negative cache
}

// Alternative: Always assume key may not exist, use defaults
const value = await env.KV.get("key") ?? "default-value";

Performance Tips

Scenario Recommendation Why
Large values (>1MB) Use stream type Avoids buffering entire value in memory
Many small keys Coalesce into one JSON object Reduces operations, improves cache hit rate
High write volume Spread across different keys Avoid 1 write/second per-key limit
Cold reads Increase cacheTtl parameter Reduces latency for frequently-read data
Bulk operations Use array form of get() Single operation, better performance

Cost Examples

Free tier:

  • 100K reads/day = 3M/month
  • 1K writes/day = 30K/month
  • 1GB storage

Example paid workload:

  • 10M reads/month = $5.00
  • 100K writes/month = $0.50
  • 1GB storage = $0.50
  • Total: ~$6/month

Limits

Limit Value Notes
Key size 512 bytes Maximum key length
Value size 25 MiB Maximum value; 413 error if exceeded
Metadata size 1024 bytes Maximum metadata per key
cacheTtl minimum 60s Minimum cache TTL
Write rate per key 1 write/second All plans; 429 error if exceeded
Propagation time ≤60s Global propagation time
Bulk get max 100 keys Maximum keys per bulk operation
Operations per Worker 1,000 Per request (bulk counts as 1)
Reads pricing $0.50 per 10M Per million reads
Writes pricing $5.00 per 1M Per million writes
Deletes pricing $5.00 per 1M Per million deletes
Storage pricing $0.50 per GB-month Per GB per month