mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-20 06:11:27 -07:00
132 lines
4.4 KiB
Markdown
132 lines
4.4 KiB
Markdown
# 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<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
|
|
|
|
```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 |
|