ADR-005: Durable Objects for State Management
Status: Accepted
Date: 2026-02-01
Deciders: Abdisamed Mohamed
Related ADRs: ADR-001 (Single DO Class), ADR-002 (Edge-First Architecture)
Context
CepatEdge requires persistent state management at the edge for:
- User sessions (authentication state)
- Data caching (frequent query results)
- Usage metrics (API call tracking)
Traditional approaches include:
- External databases (latency, cost)
- Cloudflare KV (no transactions, eventual consistency)
- In-memory caching (not persistent, single-instance)
Durable Objects provide persistent, transactional storage at the edge with strong consistency guarantees.
Decision
Use Durable Objects for all edge state management with a single DO class handling multiple namespaces.
Implementation
DO Class Architecture
typescript
export class CepatEdgeCache {
private state: DurableObjectState;
async fetch(request: Request) {
const url = new URL(request.url);
const pathParts = url.pathname.split('/').filter(Boolean);
// Route by namespace
if (pathParts[0] === 'sessions') {
return this.handleSessionOperations(request, pathParts.slice(1));
} else if (pathParts[0] === 'cache') {
return this.handleCacheOperations(request, pathParts.slice(1));
} else if (pathParts[0] === 'usage') {
return this.handleUsageOperations(request, pathParts.slice(1));
}
}
// Session management methods
async handleSessionOperations(request: Request, path: string[]) {
const sessionId = path[0];
// Session CRUD operations
}
// Cache management methods
async handleCacheOperations(request: Request, path: string[]) {
const cacheKey = path[0];
// Cache operations with TTL
}
// Usage tracking methods
async handleUsageOperations(request: Request, path: string[]) {
// Metrics collection and aggregation
}
}Namespace Strategy
typescript
// Different DO instances for different data types
const sessions = env.CEPATEDGE_CACHE.get(env.CEPATEDGE_CACHE.idFromName('sessions'));
const userCache = env.CEPATEDGE_CACHE.get(env.CEPATEDGE_CACHE.idFromName('cache:users'));
const apiUsage = env.CEPATEDGE_CACHE.get(env.CEPATEDGE_CACHE.idFromName('usage:api'));
// Usage in services
await sessions.fetch('/sessions/user-123', { method: 'GET' });
await userCache.fetch('/cache/profile-456', { method: 'PUT', body: data });
await apiUsage.fetch('/usage', { method: 'POST', body: metrics });Data Persistence
- Automatic Persistence: DO state survives worker restarts
- Transactional: All operations are ACID compliant
- Consistent: Strong consistency across all operations
- Durable: Data survives infrastructure failures
Consequences
Positive
- Edge Performance: Data stored and processed at the edge with multiple instances
- Strong Consistency: ACID transactions for critical operations
- Faster Reads: Optimized for cache hits with timestamp and index-based instances
- Automatic Scaling: DOs scale automatically with usage
- Developer Experience: Familiar async/await API
- Cost Effective: Included in Workers free tier
Negative
- Storage Limits: 1GB per DO instance
- Complexity: Additional abstraction layer
- Cold Starts: Initial requests may have higher latency
- Debugging: Distributed state harder to inspect
Mitigation
- Intelligent Sharding: Multiple DO instances for different data types
- Efficient Storage: Compact data structures, automatic cleanup
- Caching Strategy: Warm DOs with frequently accessed data
- Monitoring: Built-in usage tracking and performance metrics
Performance Characteristics
Latency
- Hot DO: <10ms response time
- Warm DO: <50ms response time
- Cold DO: <200ms response time (first request)
Throughput
- Read Operations: 1000+ ops/second per DO
- Write Operations: 500+ ops/second per DO
- Concurrent Connections: 1000+ simultaneous connections
Scalability
- Horizontal Scaling: Automatic across Cloudflare network
- Storage Scaling: Multiple DO instances for different data
- Global Distribution: Data available at all edge locations
Alternatives Considered
Cloudflare KV
- Pros: Simple API, high throughput, global consistency
- Cons: Eventual consistency, no transactions, higher latency
External Database (Neon, PlanetScale)
- Pros: Familiar SQL interface, advanced querying
- Cons: Network latency, additional cost, connection limits
Redis/Upstash
- Pros: High performance, rich data structures
- Cons: Additional cost (~$10/month), external dependency
In-Memory Caching
- Pros: Fastest possible performance
- Cons: Not persistent, single-instance, data loss on restarts
Monitoring & Observability
Built-in Metrics
typescript
// Usage tracking in DO
async getUsageStats() {
const logs = await this.state.storage.get('usage:logs');
return this.aggregateMetrics(logs);
}
// Cache performance metrics
async getCacheStats() {
const keys = await this.state.storage.list({ prefix: 'cache:' });
return this.calculateHitRates(keys);
}Operational Monitoring
- Usage Patterns: Track which data is frequently accessed
- Performance Metrics: Response times, error rates
- Storage Usage: Monitor storage consumption
- Cache Hit Rates: Optimize caching strategies
References
- Cloudflare Durable Objects Documentation
- CepatEdge Performance Benchmarks
- Edge Computing Patterns
- Distributed Systems Best Practices