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/durable-objects/gotchas.md
1# Durable Objects Gotchas23## Common Errors45### "Hibernation Cleared My In-Memory State"67**Problem:** Variables lost after hibernation8**Cause:** DO auto-hibernates when idle; in-memory state not persisted9**Solution:** Use `ctx.storage` for critical data, `ws.serializeAttachment()` for per-connection metadata1011```typescript12// ❌ Wrong - lost on hibernation13private userCount = 0;14async webSocketMessage(ws: WebSocket, msg: string) {15this.userCount++; // Lost!16}1718// ✅ Right - persisted19async webSocketMessage(ws: WebSocket, msg: string) {20const count = this.ctx.storage.kv.get("userCount") || 0;21this.ctx.storage.kv.put("userCount", count + 1);22}23```2425### "setTimeout Didn't Fire After Restart"2627**Problem:** Scheduled work lost on eviction28**Cause:** `setTimeout` in-memory only; eviction clears timers29**Solution:** Use `ctx.storage.setAlarm()` for reliable scheduling3031```typescript32// ❌ Wrong - lost on eviction33setTimeout(() => this.cleanup(), 3600000);3435// ✅ Right - survives eviction36await this.ctx.storage.setAlarm(Date.now() + 3600000);37async alarm() { await this.cleanup(); }38```3940### "Constructor Runs on Every Wake"4142**Problem:** Expensive init logic slows all requests43**Cause:** Constructor runs on every wake (first request after eviction OR after hibernation)44**Solution:** Lazy initialization or cache in storage4546**Critical understanding:** Constructor runs in two scenarios:471. **Cold start** - DO evicted from memory, first request creates new instance482. **Wake from hibernation** - DO with WebSockets hibernated, message/alarm wakes it4950```typescript51// ❌ Wrong - expensive on every wake52constructor(ctx: DurableObjectState, env: Env) {53super(ctx, env);54this.heavyData = this.loadExpensiveData(); // Slow!55}5657// ✅ Right - lazy load58private heavyData?: HeavyData;59private getHeavyData() {60if (!this.heavyData) this.heavyData = this.loadExpensiveData();61return this.heavyData;62}63```6465### "Durable Object Overloaded (503 errors)"6667**Problem:** 503 errors under load68**Cause:** Single DO exceeding ~1K req/s throughput limit69**Solution:** Shard across multiple DOs (see [Patterns: Sharding](./patterns.md))7071### "Storage Quota Exceeded (Write failures)"7273**Problem:** Write operations failing74**Cause:** DO storage exceeding 10GB limit or account quota75**Solution:** Cleanup with alarms, use `deleteAll()` for old data, upgrade plan7677### "CPU Time Exceeded (Terminated)"7879**Problem:** Request terminated mid-execution80**Cause:** Processing exceeding 30s CPU time default limit81**Solution:** Increase `limits.cpu_ms` in wrangler.jsonc (max 300s) or chunk work8283### "WebSockets Disconnect on Eviction"8485**Problem:** Connections drop unexpectedly86**Cause:** DO evicted from memory without hibernation API87**Solution:** Use WebSocket hibernation handlers + client reconnection logic8889### "Migration Failed (Deploy error)"9091**Cause:** Non-unique tags, non-sequential tags, or invalid class names in migration92**Solution:** Check tag uniqueness/sequential ordering and verify class names are correct9394### "RPC Method Not Found"9596**Cause:** compatibility_date < 2024-04-03 preventing RPC usage97**Solution:** Update compatibility_date to >= 2024-04-03 or use fetch() instead of RPC9899### "Only One Alarm Allowed"100101**Cause:** Need multiple scheduled tasks but only one alarm supported per DO102**Solution:** Use event queue pattern to schedule multiple tasks with single alarm103104### "Race Condition Despite Single-Threading"105106**Problem:** Concurrent requests see inconsistent state107**Cause:** Async operations allow request interleaving (await = yield point)108**Solution:** Use `blockConcurrencyWhile()` for critical sections or atomic storage ops109110```typescript111// ❌ Wrong - race condition112async incrementCounter() {113const count = await this.ctx.storage.get("count") || 0;114// ⚠️ Another request could execute here during await115await this.ctx.storage.put("count", count + 1);116}117118// ✅ Right - atomic operation119async incrementCounter() {120return this.ctx.storage.sql.exec(121"INSERT INTO counters (id, value) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET value = value + 1 RETURNING value"122).one().value;123}124125// ✅ Right - explicit locking126async criticalOperation() {127await this.ctx.blockConcurrencyWhile(async () => {128const count = await this.ctx.storage.get("count") || 0;129await this.ctx.storage.put("count", count + 1);130});131}132```133134### "Migration Rollback Not Supported"135136**Cause:** Attempting to rollback a migration after deployment137**Solution:** Test with `--dry-run` before deploying; migrations cannot be rolled back138139### "deleted_classes Destroys Data"140141**Problem:** Migration deleted all data142**Cause:** `deleted_classes` migration immediately destroys all DO instances and data143**Solution:** Test with `--dry-run`; use `transferred_classes` to preserve data during moves144145### "Cold Starts Are Slow"146147**Problem:** First request after eviction takes longer148**Cause:** DO constructor + initial storage access on cold start149**Solution:** Expected behavior; optimize constructor, use connection pooling in clients, consider warming strategy for critical DOs150151```typescript152// Warming strategy (periodically ping critical DOs)153export default {154async scheduled(event: ScheduledEvent, env: Env) {155const criticalIds = ["auth", "sessions", "locks"];156await Promise.all(criticalIds.map(name => {157const id = env.MY_DO.idFromName(name);158const stub = env.MY_DO.get(id);159return stub.ping(); // Keep warm160}));161}162};163```164165## Limits166167| Limit | Free | Paid | Notes |168|-------|------|------|-------|169| SQLite storage per DO | 10 GB | 10 GB | Per Durable Object instance |170| SQLite total storage | 5 GB | Unlimited | Account-wide quota |171| Key+value size | 2 MB | 2 MB | Single KV pair (SQLite/async) |172| CPU time default | 30s | 30s | Per request; configurable |173| CPU time max | 300s | 300s | Set via `limits.cpu_ms` |174| DO classes | 100 | 500 | Distinct DO class definitions |175| SQL columns | 100 | 100 | Per table |176| SQL statement size | 100 KB | 100 KB | Max SQL query size |177| WebSocket message size | 32 MiB | 32 MiB | Per message |178| Request throughput | ~1K req/s | ~1K req/s | Per DO (soft limit - shard for more) |179| Alarms per DO | 1 | 1 | Use queue pattern for multiple events |180| Total DOs | Unlimited | Unlimited | Create as many instances as needed |181| WebSockets | Unlimited | Unlimited | Within 128MB memory limit per DO |182| Memory per DO | 128 MB | 128 MB | In-memory state + WebSocket buffers |183184## Hibernation Caveats1851861. **Memory cleared** - All in-memory variables lost; reconstruct from storage or `deserializeAttachment()`1872. **Constructor reruns** - Runs on wake; avoid expensive operations, use lazy initialization1883. **No guarantees** - DO may evict instead of hibernate; design for both1894. **Attachment limit** - `serializeAttachment()` data must be JSON-serializable, keep small1905. **Alarm wakes DO** - Alarm prevents hibernation until handler completes1916. **WebSocket state not automatic** - Must explicitly persist with `serializeAttachment()` or storage192193## See Also194195- **[Patterns](./patterns.md)** - Workarounds for common limitations196- **[API](./api.md)** - Storage limits and quotas197- **[Configuration](./configuration.md)** - Setting CPU limits198