Storage backends
workerd supports two storage implementations:ActorCache (RPC-backed)
An in-memory write-back cache layer over theActorStorage::Stage RPC interface:
- LRU eviction policy
- Write batching and coalescing
- Optimistic local caching
- Maximum operation size: 16 MiB
src/workerd/io/actor-cache.{h,c++}
ActorSqlite (SQLite-backed)
A SQLite-based implementation providing synchronous access:- Direct SQLite database operations
- Automatic transaction management
- Implicit batching of operations without awaits
- Full SQL query support through the DO SQL API
src/workerd/io/actor-sqlite.{h,c++}
Cache architecture
Entry states
Each cache entry has two status dimensions: Value status:PRESENT: Entry exists with a valueABSENT: Entry is known not to existUNKNOWN: Entry state is not cached
CLEAN: Value matches persistent storageDIRTY: Local modifications not yet flushedNOT_IN_CACHE: Entry is no longer cached
Memory management
The cache uses a shared LRU across all Durable Objects in a process:- Soft limit exceeded: Clean entries are evicted (LRU)
- Hard limit exceeded: Throws an exception and resets the isolate
- Dirty limit exceeded: Applies backpressure until writes flush
Write batching
workerd automatically batches writes to improve performance:Flush triggers
Writes are flushed when:- The output gate needs to confirm a response
- 2 seconds elapse since the first write in the batch
- The dirty list exceeds the byte limit
- The application explicitly calls
storage.sync()
Batching example
SQLite backend details
Automatic transactions
The SQLite backend automatically wraps operations without intervening awaits into a single transaction:Explicit transactions
For complex atomic operations:Explicit transactions are recommended for operations that read and then write based on the read value, ensuring atomicity.
Storage options
Read options
Write options
Consistency guarantees
Input gate
Controls concurrent request handling:- Ensures requests see a consistent view of state
- Can be broken by critical section failures
- Location:
src/workerd/io/io-gate.{h,c++}
Output gate
Blocks outgoing responses:- Responses wait for pending writes to flush
- Prevents returning results based on unconfirmed writes
allowUnconfirmedoption bypasses this wait
Example: Consistency in action
Performance considerations
Optimize read patterns
Optimize write patterns
Use list() efficiently
Limits and constraints
| Limit | Value | Notes |
|---|---|---|
| Maximum key size | 2 KiB | Keys are UTF-8 strings |
| Maximum value size | 128 KiB | Values are raw bytes |
| Maximum operation size | 16 MiB | Total size of single RPC request |
| Cache soft limit | 32 MiB | Default, configurable |
| Cache hard limit | 64 MiB | Default, isolate reset on exceed |
| Dirty list limit | 8 MiB | Backpressure applied when exceeded |
Local development
For local workerd instances:Local storage currently always runs on the same machine that requested the Durable Object. In production, you would typically want Durable Objects distributed across machines.
Debugging tips
- Monitor cache size: Large list operations can exceed cache limits
- Check flush timing: Use
storage.sync()to force flushes for testing - Review transaction boundaries: Ensure atomic operations are in explicit transactions
- Profile write batching: Unnecessary awaits between writes reduce performance