Design a Durable Object Runtime Like Cloudflare Durable Objects
A system-design walkthrough for building a globally addressable stateful-object runtime with single ownership, local SQLite, durable backing storage, leases, ETags, epochs, route caching, invalidation, hibernation, and failure recovery.
Cloudflare Durable Objects expose a very attractive programming model:
stable object identity
+
single logical execution owner
+
state kept close to compute
+
durable object-local storage
+
WebSockets / timers / in-memory stateThis article asks a different question:
How would we design a runtime with similar semantics from first principles?
This is not a description of Cloudflare's private internal implementation.
System Design Summary
๐ Premium section
Requirements
3. Functional requirements
| ID | Requirement | Priority |
|---|---|---|
| FR-01 | Stable logical object identity independent of physical node placement | P0 |
| FR-02 | Invoke an object from any healthy runtime node and route the request to its current owner | P0 |
| FR-03 | Maintain one authoritative owner session and ownership epoch per object | P0 |
| FR-04 | Provide private transactional persistent storage for each object | P0 |
| FR-05 | Allow a resident object to keep reconstructible in-memory state | P0 |
| FR-06 | Execute one event at a time per object, with explicit semantics for interleaving at await boundaries | P0 |
| FR-07 | Support long-lived and hibernatable WebSocket connections | P1 |
| FR-08 | Persist and deliver timers or alarms, with retry-safe handlers | P1 |
| FR-09 | Recover ownership and durable state after node failure | P0 |
| FR-10 | Hibernate idle objects and reactivate them on demand without keeping one process per object | P1 |
| FR-11 | Detect stale cached routes, invalidate them, and resolve the current owner without a fleet-wide broadcast | P0 |
The application chooses the object boundary. One chat room per object is only one possible mapping; later sections explain how that choice affects concurrency and the single-object scaling ceiling.
4. Non-functional requirements
| Requirement | Target |
|---|---|
| Consistency | At most one authoritative owner session and epoch for each object; prefer temporary unavailability over split brain |
| Durability | RPO = 0 for acknowledged durable writes: loss of the current node must not lose acknowledged state |
| Node-failure availability | Survive an individual node failure after lease expiry, conditional ownership acquisition, and state restoration |
| Horizontal scalability | Adding nodes increases capacity for independent objects; one hot object does not gain multiple writers automatically |
| Warm read path | Resident reads use local memory or SQLite with no object-store lookup |
| Warm routing path | Cached remote routes forward directly to the known owner without reading ownership metadata on every request |
| Coordination overhead | One renewable lease per node session rather than one renewable lease per owned object |
| Recovery cost | Recover failed-node objects lazily as traffic or alarms require them; avoid fleet-wide ownership rewrites |
| Fault tolerance | Reconcile ambiguous CAS results, stale routes, delayed messages, process pauses, partitions, and old-owner recovery |
| Security | Keep peer/operator traffic private and authenticated; scope bucket credentials to one fleet |
The durability target has a latency cost: success cannot be returned until the write crosses the selected remote durability boundary and the process still proves current ownership.
5. Out of scope
The core runtime is not intended to replace global analytics, data-warehouse scans, arbitrary cross-object joins, full-text search across every object, or an OLAP platform. Those workloads belong in separate systems such as Postgres, DynamoDB, Kafka, ClickHouse, Elasticsearch, or a data warehouse. A practical application can combine those systems with durable objects rather than forcing every workload through one abstraction.
Capacity and Scale Assumptions
๐ Premium section
API Design
๐ Premium section
Core Data Model
๐ Premium section
High-Level Architecture
๐ Premium section
Design Roadmap
๐ Premium section
Detailed Design
๐ Premium section
Part II: Ownership
๐ Premium section
Part III: Epoch Fencing
๐ Premium section
Part IV: Node Leases
๐ Premium section
Part V: Routing
๐ Premium section
Part VI: Cold Activation
๐ Premium section
Part VII: Failure Recovery
๐ Premium section
Part VIII: Durability and Acknowledgement
๐ Premium section
Part IX: Where the Metadata Lives
๐ Premium section
Part X: End-to-End Request Paths
๐ Premium section
Part XI: Why This Architecture Is Interesting
๐ Premium section
Part XII: Choosing the Durable Object Boundary
๐ Premium section
Part XIII: What We Would Need to Build
๐ Premium section
Part XIV: A Simplified State Machine
๐ Premium section
Part XV: The Three Different Versions You Must Not Confuse
๐ Premium section
Part XVI: The Three Different S3 Workloads
๐ Premium section
Part XVII: Object Storage Is More Than Backup
๐ Premium section
Part XVIII: Availability versus Consistency
๐ Premium section
Part XIX: WebSockets
๐ Premium section
Part XX: Performance Trade-offs
๐ Premium section
Part XXI: Failure Cases Worth Testing
๐ Premium section
Part XXII: A Complete Mental Model
๐ Premium section
Part XXIII: What Cloudflare Durable Objects Give the Developer
๐ Premium section
Part XXIV: When This Model Is a Good Fit
๐ Premium section
Part XXV: Why Not Just Use DynamoDB?
๐ Premium section
Part XXVI: The Key Design Lessons
๐ Premium section
Conclusion
๐ Premium section
Appendix A โ Quick glossary
๐ Premium section
Appendix B โ Record cheat sheet
๐ Premium section
Appendix C โ Implementation checklist
๐ Premium section
References
๐ Premium section