# 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 ```typescript // ❌ 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 ```typescript async function putWithRetry( kv: KVNamespace, key: string, value: string, maxAttempts = 5 ): Promise { 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 ```typescript // ❌ 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 ```typescript // 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 |