# DO Storage Gotchas & Troubleshooting

## Concurrency Model (CRITICAL)

Durable Objects use **input/output gates** to prevent race conditions:

### Input Gates

Block new requests during storage reads from CURRENT request:

```typescript
// SAFE: Input gate active during await
async increment() {
  const val = await this.ctx.storage.get("counter"); // Input gate blocks other requests
  await this.ctx.storage.put("counter", val + 1);
  return val;
}
```

### Output Gates

Hold response until ALL writes from current request confirm:

```typescript
// SAFE: Output gate waits for put() to confirm before returning response
async increment() {
  const val = await this.ctx.storage.get("counter");
  this.ctx.storage.put("counter", val + 1); // No await
  return new Response(String(val)); // Response delayed until write confirms
}
```

### Write Coalescing

Multiple writes to same key = atomic (last write wins):

```typescript
// SAFE: All three writes coalesce atomically
this.ctx.storage.put('key', 1)
this.ctx.storage.put('key', 2)
this.ctx.storage.put('key', 3) // Final value: 3
```

### Breaking Gates (DANGER)

**fetch() breaks input/output gates** → allows request interleaving:

```typescript
// UNSAFE: fetch() allows another request to interleave
async unsafe() {
  const val = await this.ctx.storage.get("counter");
  await fetch("https://api.example.com"); // Gate broken!
  await this.ctx.storage.put("counter", val + 1); // Race condition possible
}
```

**Solution:** Use `blockConcurrencyWhile()` or `transaction()`:

```typescript
// SAFE: Block concurrent requests explicitly
async safe() {
  return await this.ctx.blockConcurrencyWhile(async () => {
    const val = await this.ctx.storage.get("counter");
    await fetch("https://api.example.com");
    await this.ctx.storage.put("counter", val + 1);
    return val;
  });
}
```

### allowConcurrency Option

Opt out of input gate for reads that don't need protection:

```typescript
// Allow concurrent reads (no consistency guarantee)
const val = await this.ctx.storage.get('metrics', { allowConcurrency: true })
```

## Common Errors

### "Race Condition in Concurrent Calls"

**Cause:** Multiple concurrent storage operations initiated from same event (e.g., `Promise.all()`) are not protected by input gate  
**Solution:** Avoid concurrent storage operations within single event; input gate only serializes requests from different events, not operations within same event

### "Direct SQL Transaction Statements"

**Cause:** Using `BEGIN TRANSACTION` directly instead of transaction methods  
**Solution:** Use `this.ctx.storage.transactionSync()` for sync operations or `this.ctx.storage.transaction()` for async operations

### "Async in transactionSync"

**Cause:** Using async operations inside `transactionSync()` callback  
**Solution:** Use async `transaction()` method instead of `transactionSync()` when async operations needed

### "TypeScript Type Mismatch at Runtime"

**Cause:** Query doesn't return all fields specified in TypeScript type  
**Solution:** Ensure SQL query selects all columns that match the TypeScript type definition

### "Silent Data Corruption with Large IDs"

**Cause:** JavaScript numbers have 53-bit precision; SQLite INTEGER is 64-bit  
**Symptom:** IDs > 9007199254740991 (Number.MAX_SAFE_INTEGER) silently truncate/corrupt  
**Solution:** Store large IDs as TEXT:

```typescript
// BAD: Snowflake/Twitter IDs will corrupt
this.sql.exec('CREATE TABLE events(id INTEGER PRIMARY KEY)')
this.sql.exec('INSERT INTO events VALUES (?)', 1234567890123456789n) // Corrupts!

// GOOD: Store as TEXT
this.sql.exec('CREATE TABLE events(id TEXT PRIMARY KEY)')
this.sql.exec('INSERT INTO events VALUES (?)', '1234567890123456789')
```

### "Alarm Not Deleted with deleteAll()"

**Cause:** `deleteAll()` doesn't delete alarms automatically  
**Solution:** Call `deleteAlarm()` explicitly before `deleteAll()` to remove alarm

### "Slow Performance"

**Cause:** Using async KV API instead of sync API  
**Solution:** Use sync KV API (`ctx.storage.kv`) for better performance with simple key-value operations

### "High Billing from Storage Operations"

**Cause:** Excessive `rowsRead`/`rowsWritten` or unused objects not cleaned up  
**Solution:** Monitor `rowsRead`/`rowsWritten` metrics and ensure unused objects call `deleteAll()`

### "Durable Object Overloaded"

**Cause:** Single DO exceeding ~1K req/sec soft limit  
**Solution:** Shard across multiple DOs with random IDs or other distribution strategy

## Limits

| Limit                     | Value       | Notes                 |
| ------------------------- | ----------- | --------------------- |
| Max columns per table     | 100         | SQL limitation        |
| Max string/BLOB per row   | 2 MB        | SQL limitation        |
| Max row size              | 2 MB        | SQL limitation        |
| Max SQL statement size    | 100 KB      | SQL limitation        |
| Max SQL parameters        | 100         | SQL limitation        |
| Max LIKE/GLOB pattern     | 50 B        | SQL limitation        |
| SQLite storage per object | 10 GB       | SQLite-backed storage |
| SQLite key+value size     | 2 MB        | SQLite-backed storage |
| KV storage per object     | Unlimited   | KV-style storage      |
| KV key size               | 2 KiB       | KV-style storage      |
| KV value size             | 128 KiB     | KV-style storage      |
| Request throughput        | ~1K req/sec | Soft limit per DO     |
