Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/do-storage/gotchas.md
1# DO Storage Gotchas & Troubleshooting23## Concurrency Model (CRITICAL)45Durable Objects use **input/output gates** to prevent race conditions:67### Input Gates8Block new requests during storage reads from CURRENT request:910```typescript11// SAFE: Input gate active during await12async increment() {13const val = await this.ctx.storage.get("counter"); // Input gate blocks other requests14await this.ctx.storage.put("counter", val + 1);15return val;16}17```1819### Output Gates20Hold response until ALL writes from current request confirm:2122```typescript23// SAFE: Output gate waits for put() to confirm before returning response24async increment() {25const val = await this.ctx.storage.get("counter");26this.ctx.storage.put("counter", val + 1); // No await27return new Response(String(val)); // Response delayed until write confirms28}29```3031### Write Coalescing32Multiple writes to same key = atomic (last write wins):3334```typescript35// SAFE: All three writes coalesce atomically36this.ctx.storage.put("key", 1);37this.ctx.storage.put("key", 2);38this.ctx.storage.put("key", 3); // Final value: 339```4041### Breaking Gates (DANGER)4243**fetch() breaks input/output gates** → allows request interleaving:4445```typescript46// UNSAFE: fetch() allows another request to interleave47async unsafe() {48const val = await this.ctx.storage.get("counter");49await fetch("https://api.example.com"); // Gate broken!50await this.ctx.storage.put("counter", val + 1); // Race condition possible51}52```5354**Solution:** Use `blockConcurrencyWhile()` or `transaction()`:5556```typescript57// SAFE: Block concurrent requests explicitly58async safe() {59return await this.ctx.blockConcurrencyWhile(async () => {60const val = await this.ctx.storage.get("counter");61await fetch("https://api.example.com");62await this.ctx.storage.put("counter", val + 1);63return val;64});65}66```6768### allowConcurrency Option6970Opt out of input gate for reads that don't need protection:7172```typescript73// Allow concurrent reads (no consistency guarantee)74const val = await this.ctx.storage.get("metrics", { allowConcurrency: true });75```7677## Common Errors7879### "Race Condition in Concurrent Calls"8081**Cause:** Multiple concurrent storage operations initiated from same event (e.g., `Promise.all()`) are not protected by input gate82**Solution:** Avoid concurrent storage operations within single event; input gate only serializes requests from different events, not operations within same event8384### "Direct SQL Transaction Statements"8586**Cause:** Using `BEGIN TRANSACTION` directly instead of transaction methods87**Solution:** Use `this.ctx.storage.transactionSync()` for sync operations or `this.ctx.storage.transaction()` for async operations8889### "Async in transactionSync"9091**Cause:** Using async operations inside `transactionSync()` callback92**Solution:** Use async `transaction()` method instead of `transactionSync()` when async operations needed9394### "TypeScript Type Mismatch at Runtime"9596**Cause:** Query doesn't return all fields specified in TypeScript type97**Solution:** Ensure SQL query selects all columns that match the TypeScript type definition9899### "Silent Data Corruption with Large IDs"100101**Cause:** JavaScript numbers have 53-bit precision; SQLite INTEGER is 64-bit102**Symptom:** IDs > 9007199254740991 (Number.MAX_SAFE_INTEGER) silently truncate/corrupt103**Solution:** Store large IDs as TEXT:104105```typescript106// BAD: Snowflake/Twitter IDs will corrupt107this.sql.exec("CREATE TABLE events(id INTEGER PRIMARY KEY)");108this.sql.exec("INSERT INTO events VALUES (?)", 1234567890123456789n); // Corrupts!109110// GOOD: Store as TEXT111this.sql.exec("CREATE TABLE events(id TEXT PRIMARY KEY)");112this.sql.exec("INSERT INTO events VALUES (?)", "1234567890123456789");113```114115### "Alarm Not Deleted with deleteAll()"116117**Cause:** `deleteAll()` doesn't delete alarms automatically118**Solution:** Call `deleteAlarm()` explicitly before `deleteAll()` to remove alarm119120### "Slow Performance"121122**Cause:** Using async KV API instead of sync API123**Solution:** Use sync KV API (`ctx.storage.kv`) for better performance with simple key-value operations124125### "High Billing from Storage Operations"126127**Cause:** Excessive `rowsRead`/`rowsWritten` or unused objects not cleaned up128**Solution:** Monitor `rowsRead`/`rowsWritten` metrics and ensure unused objects call `deleteAll()`129130### "Durable Object Overloaded"131132**Cause:** Single DO exceeding ~1K req/sec soft limit133**Solution:** Shard across multiple DOs with random IDs or other distribution strategy134135## Limits136137| Limit | Value | Notes |138|-------|-------|-------|139| Max columns per table | 100 | SQL limitation |140| Max string/BLOB per row | 2 MB | SQL limitation |141| Max row size | 2 MB | SQL limitation |142| Max SQL statement size | 100 KB | SQL limitation |143| Max SQL parameters | 100 | SQL limitation |144| Max LIKE/GLOB pattern | 50 B | SQL limitation |145| SQLite storage per object | 10 GB | SQLite-backed storage |146| SQLite key+value size | 2 MB | SQLite-backed storage |147| KV storage per object | Unlimited | KV-style storage |148| KV key size | 2 KiB | KV-style storage |149| KV value size | 128 KiB | KV-style storage |150| Request throughput | ~1K req/sec | Soft limit per DO |151