Files
gh-jezweb-claude-skills-ski…/references/best-practices.md
2025-11-30 08:24:13 +08:00

417 lines
8.5 KiB
Markdown

# Durable Objects Best Practices
Production patterns and optimization strategies.
---
## Performance
### Minimize Constructor Work
Heavy work in constructor delays request handling and hibernation wake-up.
```typescript
// ✅ GOOD
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Minimal initialization
this.sessions = new Map();
// Load from storage with blockConcurrencyWhile
ctx.blockConcurrencyWhile(async () => {
this.data = await ctx.storage.get('data') || defaultData;
});
}
// ❌ BAD
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Expensive operations delay all requests
await this.loadMassiveDataset();
await this.computeComplexState();
}
```
### Use Indexes for SQL Queries
```typescript
// Create indexes for frequently queried columns
this.sql.exec(`
CREATE INDEX IF NOT EXISTS idx_user_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages(created_at);
`);
// Use EXPLAIN QUERY PLAN to verify index usage
const plan = this.sql.exec('EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?', email);
```
### Batch Operations
```typescript
// ✅ GOOD: Batch inserts
this.sql.exec(`INSERT INTO messages (text, user_id) VALUES ${rows.map(() => '(?, ?)').join(', ')}`, ...flatValues);
// ❌ BAD: Individual inserts
for (const row of rows) {
this.sql.exec('INSERT INTO messages (text, user_id) VALUES (?, ?)', row.text, row.userId);
}
```
### Use Transactions
```typescript
// Atomic multi-step operations
this.ctx.storage.transactionSync(() => {
this.sql.exec('UPDATE users SET balance = balance - ? WHERE id = ?', amount, senderId);
this.sql.exec('UPDATE users SET balance = balance + ? WHERE id = ?', amount, receiverId);
this.sql.exec('INSERT INTO transactions ...');
});
```
---
## Cost Optimization
### Use WebSocket Hibernation
```typescript
// ✅ GOOD: Hibernates when idle (~90% cost savings)
this.ctx.acceptWebSocket(server);
// ❌ BAD: Never hibernates (high duration charges)
server.accept();
```
### Use Alarms, Not setTimeout
```typescript
// ✅ GOOD: Allows hibernation
await this.ctx.storage.setAlarm(Date.now() + 60000);
// ❌ BAD: Prevents hibernation
setTimeout(() => this.doWork(), 60000);
```
### Minimize Storage Size
```typescript
// Periodic cleanup with alarms
async alarm(): Promise<void> {
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
this.sql.exec('DELETE FROM messages WHERE created_at < ?', oneDayAgo);
// Schedule next cleanup
await this.ctx.storage.setAlarm(Date.now() + 3600000);
}
```
---
## Reliability
### Implement Idempotent Operations
```typescript
// ✅ GOOD: Idempotent (safe to retry)
async processPayment(paymentId: string, amount: number): Promise<void> {
// Check if already processed
const existing = await this.ctx.storage.get(`payment:${paymentId}`);
if (existing) {
return; // Already processed
}
// Process payment
await this.chargeCustomer(amount);
// Mark as processed
await this.ctx.storage.put(`payment:${paymentId}`, { processed: true, amount });
}
// ❌ BAD: Not idempotent (duplicate charges on retry)
async processPayment(amount: number): Promise<void> {
await this.chargeCustomer(amount);
}
```
### Limit Alarm Retries
```typescript
async alarm(info: { retryCount: number }): Promise<void> {
if (info.retryCount > 3) {
console.error('Giving up after 3 retries');
await this.logFailure();
return;
}
await this.doWork();
}
```
### Graceful Error Handling
```typescript
async processMessage(message: string): Promise<void> {
try {
await this.handleMessage(message);
} catch (error) {
console.error('Message processing failed:', error);
// Store failed message for retry
await this.ctx.storage.put(`failed:${Date.now()}`, message);
// Don't throw - prevents retry storm
}
}
```
---
## Security
### Validate Input
```typescript
async createUser(email: string, username: string): Promise<void> {
// Validate input
if (!email || !email.includes('@')) {
throw new Error('Invalid email');
}
if (!username || username.length < 3) {
throw new Error('Invalid username');
}
// Use parameterized queries (prevents SQL injection)
this.sql.exec(
'INSERT INTO users (email, username) VALUES (?, ?)',
email,
username
);
}
```
### Use Parameterized Queries
```typescript
// ✅ GOOD: Parameterized (safe from SQL injection)
this.sql.exec('SELECT * FROM users WHERE email = ?', userEmail);
// ❌ BAD: String concatenation (SQL injection risk)
this.sql.exec(`SELECT * FROM users WHERE email = '${userEmail}'`);
```
### Authenticate Requests
```typescript
async fetch(request: Request): Promise<Response> {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !this.validateToken(authHeader)) {
return new Response('Unauthorized', { status: 401 });
}
// Handle authenticated request
}
```
---
## Data Management
### Monitor Storage Size
```typescript
async getStorageSize(): Promise<number> {
// Approximate size (sum of all values)
const map = await this.ctx.storage.list();
let size = 0;
for (const value of map.values()) {
size += JSON.stringify(value).length;
}
return size;
}
async checkStorageLimit(): Promise<void> {
const size = await this.getStorageSize();
if (size > 900_000_000) { // 900MB (90% of 1GB limit)
console.warn('Storage approaching limit');
await this.triggerCleanup();
}
}
```
### Cleanup Old Data
```typescript
// Regular cleanup with alarms
async alarm(): Promise<void> {
const cutoff = Date.now() - (30 * 24 * 60 * 60 * 1000); // 30 days
this.sql.exec('DELETE FROM messages WHERE created_at < ?', cutoff);
// Schedule next cleanup
await this.ctx.storage.setAlarm(Date.now() + 86400000); // 24 hours
}
```
### Backup Critical Data
```typescript
async backup(): Promise<void> {
// Export to R2 or D1
const data = await this.exportData();
await this.env.BUCKET.put(`backup-${Date.now()}.json`, JSON.stringify(data));
}
```
---
## Testing
### Local Development
```bash
# Start local dev server
npx wrangler dev
# Test with curl
curl -X POST http://localhost:8787/api/increment
```
### Integration Tests
```typescript
// Test DO behavior
describe('Counter DO', () => {
it('should increment', async () => {
const stub = env.COUNTER.getByName('test-counter');
const count1 = await stub.increment();
expect(count1).toBe(1);
const count2 = await stub.increment();
expect(count2).toBe(2);
});
});
```
### Simulate Hibernation
```typescript
// Test hibernation wake-up
constructor(ctx, env) {
super(ctx, env);
console.log('DO woke up!', {
websockets: ctx.getWebSockets().length,
});
// Restore state
ctx.getWebSockets().forEach(ws => {
const metadata = ws.deserializeAttachment();
this.sessions.set(ws, metadata);
});
}
```
---
## Monitoring
### Log Important Events
```typescript
async importantOperation(): Promise<void> {
console.log('Starting important operation', {
doId: this.ctx.id.toString(),
timestamp: Date.now(),
});
await this.doWork();
console.log('Important operation completed');
}
```
### Track Metrics
```typescript
async recordMetric(metric: string, value: number): Promise<void> {
// Store metrics
await this.ctx.storage.put(`metric:${metric}:${Date.now()}`, value);
// Or send to Analytics Engine
// await this.env.ANALYTICS.writeDataPoint({
// indexes: [metric],
// doubles: [value],
// });
}
```
### Use Tail Logs
```bash
# Tail live logs
npx wrangler tail
# Filter by DO
npx wrangler tail --search "DurableObject"
```
---
## Common Patterns
### Rate Limiting
```typescript
async checkRateLimit(userId: string, limit: number, window: number): Promise<boolean> {
const key = `rate:${userId}`;
const now = Date.now();
const requests = await this.ctx.storage.get<number[]>(key) || [];
const validRequests = requests.filter(t => now - t < window);
if (validRequests.length >= limit) {
return false; // Rate limited
}
validRequests.push(now);
await this.ctx.storage.put(key, validRequests);
return true;
}
```
### Leader Election
```typescript
async electLeader(workerId: string): Promise<boolean> {
try {
this.sql.exec(
'INSERT INTO leader (id, worker_id) VALUES (1, ?)',
workerId
);
return true; // Became leader
} catch {
return false; // Someone else is leader
}
}
```
### Session Management
See `templates/state-api-patterns.ts` for complete example.
---
**Official Docs**: https://developers.cloudflare.com/durable-objects/best-practices/