Cloudflare Durable Objects — Stateful Edge Computing không cần Server
Posted on: 5/5/2026 10:11:35 AM
Table of contents
- Tại sao Stateful ở Edge lại khó?
- Durable Objects là gì?
- Actor Model trên Edge — Kiến trúc
- SQLite Storage — Database riêng cho mỗi Entity
- WebSocket Hibernation — Giữ kết nối, tiết kiệm chi phí
- Alarms API — Scheduled Tasks cho mỗi Entity
- System Design: Real-time Chat với Durable Objects
- Patterns nâng cao
- Free Tier — Bắt đầu miễn phí
- Khi nào KHÔNG nên dùng Durable Objects
- Roadmap & Tương lai
- Kết luận
Tại sao Stateful ở Edge lại khó?
Serverless functions (AWS Lambda, Cloudflare Workers, Vercel Edge Functions) được thiết kế stateless — mỗi request là một lần thực thi độc lập, không chia sẻ bộ nhớ giữa các invocation. Khi bạn cần state (session, counter, game state, collaborative document), giải pháp truyền thống là kết nối đến một database trung tâm — Redis, PostgreSQL, DynamoDB — đặt ở một region cụ thể.
Vấn đề: database trung tâm tạo ra latency bottleneck. User ở Singapore gọi Worker chạy tại edge Singapore, nhưng Worker phải round-trip đến database ở US-East để đọc/ghi state. Latency tăng 150-300ms mỗi request, phá vỡ lợi thế edge computing.
Bài toán cốt lõi
Làm sao để có strong consistency + low latency + zero ops cho stateful workloads trên edge? Cloudflare Durable Objects giải quyết chính xác bài toán này bằng Actor Model.
Durable Objects là gì?
Durable Objects (DO) là compute instances globally unique, single-threaded chạy trên Cloudflare's edge network. Mỗi DO instance:
- Có một unique ID toàn cầu — chỉ tồn tại đúng một instance tại một thời điểm
- Xử lý requests tuần tự (single-threaded) — không có race condition
- Có private persistent storage riêng (SQLite hoặc Key-Value, tối đa 10GB)
- Chạy tại location gần nhất với user đầu tiên truy cập nó
- Tự động hibernate khi không có request, tự thức dậy khi cần
Actor Model trên Edge — Kiến trúc
Durable Objects triển khai Actor Model — mỗi entity trong hệ thống (user session, chat room, game match, document) là một actor độc lập với state riêng, xử lý message tuần tự.
graph TB
subgraph "Cloudflare Edge Network"
W1[Worker - Singapore] --> DO1[DO: Room-abc
SQLite + WebSocket]
W2[Worker - Tokyo] --> DO1
W3[Worker - Sydney] --> DO1
W4[Worker - London] --> DO2[DO: Room-xyz
SQLite + WebSocket]
W5[Worker - Frankfurt] --> DO2
end
U1[User Singapore] --> W1
U2[User Tokyo] --> W2
U3[User Sydney] --> W3
U4[User London] --> W4
U5[User Frankfurt] --> W5
style DO1 fill:#e94560,stroke:#fff,color:#fff
style DO2 fill:#e94560,stroke:#fff,color:#fff
style W1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style W2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style W3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style W4 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style W5 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Workers ở mọi edge location route requests đến đúng DO instance — strong consistency đảm bảo bởi single-threaded execution
Tại sao single-threaded lại là lợi thế?
Trong hệ thống phân tán truyền thống, bạn cần distributed locks, optimistic concurrency control, hoặc consensus protocols (Raft, Paxos) để đảm bảo consistency. Với DO, bản thân kiến trúc đã loại bỏ hoàn toàn race condition — không cần mutex, không cần transaction isolation levels phức tạp, không cần "read-your-writes" logic.
SQLite Storage — Database riêng cho mỗi Entity
Từ tháng 4/2025, Cloudflare khuyến nghị dùng SQLite storage backend cho tất cả DO classes mới. Mỗi DO instance có một SQLite database riêng biệt với dung lượng tối đa 10GB.
// wrangler.toml
[[durable_objects.bindings]]
name = "CHAT_ROOM"
class_name = "ChatRoom"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ChatRoom"]
// ChatRoom Durable Object
export class ChatRoom extends DurableObject {
constructor(ctx, env) {
super(ctx, env);
// Tạo bảng khi DO khởi tạo lần đầu
ctx.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
}
async addMessage(userId, content) {
// Single-threaded: không cần lock hay transaction isolation
this.ctx.storage.sql.exec(
`INSERT INTO messages (user_id, content) VALUES (?, ?)`,
userId, content
);
// Broadcast cho tất cả WebSocket clients
const recent = this.ctx.storage.sql.exec(
`SELECT * FROM messages ORDER BY id DESC LIMIT 1`
).toArray();
this.broadcast(JSON.stringify(recent[0]));
}
}
Point-in-Time Recovery (PITR)
SQLite trong DO hỗ trợ khôi phục dữ liệu về bất kỳ thời điểm nào trong vòng 30 ngày qua. Đặc biệt hữu ích cho ứng dụng collaborative editing khi cần rollback document state.
WebSocket Hibernation — Giữ kết nối, tiết kiệm chi phí
Một trong những tính năng quan trọng nhất của DO là WebSocket Hibernation. Khi DO không nhận event nào trong một khoảng thời gian, nó bị evict khỏi memory nhưng WebSocket connections vẫn được duy trì bởi Cloudflare's infrastructure.
sequenceDiagram
participant C as Client
participant CF as Cloudflare Edge
participant DO as Durable Object
C->>CF: WebSocket Connect
CF->>DO: acceptWebSocket()
DO->>DO: Active state (xử lý messages)
Note over DO: 30s không có event
DO-->>CF: Hibernate (evict từ memory)
Note over CF: WebSocket connections vẫn active
C->>CF: Gửi message
CF->>DO: Wake up DO
DO->>DO: deserializeAttachment()
DO->>C: Xử lý + phản hồi
WebSocket Hibernation: DO ngủ đông nhưng connections vẫn sống — chỉ tốn chi phí khi có traffic thực sự
export class ChatRoom extends DurableObject {
async fetch(request) {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
// Dùng acceptWebSocket thay vì server.accept()
// để enable hibernation
this.ctx.acceptWebSocket(server);
// Lưu metadata vào attachment (persist qua hibernation)
server.serializeAttachment({
joinedAt: Date.now(),
userId: new URL(request.url).searchParams.get('userId')
});
return new Response(null, { status: 101, webSocket: client });
}
// Được gọi khi DO thức dậy từ hibernation
async webSocketMessage(ws, message) {
const meta = ws.deserializeAttachment();
await this.addMessage(meta.userId, message);
}
async webSocketClose(ws) {
// Cleanup khi client disconnect
ws.close();
}
}
Alarms API — Scheduled Tasks cho mỗi Entity
Alarms cho phép lên lịch một DO thức dậy tại thời điểm cụ thể trong tương lai. Mỗi DO chỉ có một alarm active tại một thời điểm — nếu set alarm mới, alarm cũ bị ghi đè.
export class GameMatch extends DurableObject {
async startMatch(players) {
// Lưu state
this.ctx.storage.sql.exec(
`INSERT INTO matches (players, status, started_at)
VALUES (?, 'active', datetime('now'))`,
JSON.stringify(players)
);
// Set alarm: tự động kết thúc match sau 10 phút
await this.ctx.storage.setAlarm(Date.now() + 10 * 60 * 1000);
}
// Được gọi khi alarm kích hoạt
async alarm() {
this.ctx.storage.sql.exec(
`UPDATE matches SET status = 'completed' WHERE status = 'active'`
);
this.broadcast(JSON.stringify({ event: 'match_ended' }));
}
}
System Design: Real-time Chat với Durable Objects
Hãy thiết kế một hệ thống chat real-time hoàn chỉnh sử dụng DO — minh họa cách các tính năng kết hợp trong production.
graph LR
subgraph "Client Layer"
A[Browser/Mobile App]
end
subgraph "Edge Layer - Cloudflare Workers"
B[Auth Worker]
C[Router Worker]
end
subgraph "Durable Objects Layer"
D[DO: User-123
Online status + prefs]
E[DO: Room-general
Messages + members]
F[DO: Room-engineering
Messages + members]
end
subgraph "Storage"
G[(SQLite per DO
Messages history)]
H[(Cloudflare KV
Room directory)]
end
A -->|WebSocket| B
B -->|Validate JWT| C
C -->|Route to room| E
C -->|Route to room| F
E --> G
F --> G
C -.->|Lookup| H
D --> G
style D fill:#e94560,stroke:#fff,color:#fff
style E fill:#e94560,stroke:#fff,color:#fff
style F fill:#e94560,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
style C fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
Kiến trúc chat system: mỗi room là một DO instance với SQLite riêng, WebSocket Hibernation giảm chi phí idle rooms
Ưu điểm so với kiến trúc truyền thống
| Khía cạnh | Durable Objects | Truyền thống (Redis + WS Server) |
|---|---|---|
| Consistency | Strong by design (single-threaded) | Cần distributed locks / Redis WATCH |
| Scaling | Tự động — 1 instance / entity | Manual sharding, sticky sessions |
| Latency | Edge gần user (sub-50ms) | Centralized (100-400ms cross-region) |
| WebSocket | Native + Hibernation (tiết kiệm 90% cost idle) | Always-on, trả tiền idle connections |
| Storage | 10GB SQLite / entity, PITR 30 ngày | Redis: in-memory (đắt), PostgreSQL: shared |
| Operations | Zero — Cloudflare quản lý toàn bộ | Provisioning, patching, monitoring, failover |
| Cold start | ~5ms (wake from hibernation) | N/A (always-on) nhưng trả tiền idle |
Patterns nâng cao
1. Fan-out Pattern — Broadcast hiệu quả
export class ChatRoom extends DurableObject {
broadcast(message, excludeWs = null) {
// getWebSockets() trả về tất cả connected clients
// kể cả những client đang trong hibernation state
for (const ws of this.ctx.getWebSockets()) {
if (ws !== excludeWs && ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}
}
}
2. Rate Limiting Pattern — Zero-storage coordination
export class RateLimiter extends DurableObject {
// In-memory only — không cần persist
requests = [];
async checkLimit(limit = 100, windowMs = 60000) {
const now = Date.now();
// Xóa requests cũ
this.requests = this.requests.filter(t => now - t < windowMs);
if (this.requests.length >= limit) {
return { allowed: false, retryAfter: windowMs - (now - this.requests[0]) };
}
this.requests.push(now);
return { allowed: true, remaining: limit - this.requests.length };
}
}
3. Distributed Counter — Atomic increment không cần lock
export class PageViewCounter extends DurableObject {
async increment(pageId) {
// Single-threaded guarantee: không race condition
const result = this.ctx.storage.sql.exec(
`INSERT INTO counters (page_id, count) VALUES (?, 1)
ON CONFLICT(page_id) DO UPDATE SET count = count + 1
RETURNING count`,
pageId
).toArray();
return result[0].count;
}
}
Free Tier — Bắt đầu miễn phí
Từ tháng 4/2025, Cloudflare mở Durable Objects cho Workers Free plan:
Free Plan bao gồm
- SQLite-backed Durable Objects (bắt buộc dùng SQLite, không KV)
- 5 GB tổng storage cho toàn bộ account
- Unlimited requests (trong giới hạn Workers Free: 100K requests/ngày)
- WebSocket Hibernation
- Alarms API
Với free tier này, bạn hoàn toàn có thể build và deploy các ứng dụng real-time như chat, collaborative editing, multiplayer games mà không tốn đồng nào cho infrastructure.
Khi nào KHÔNG nên dùng Durable Objects
Hạn chế cần biết
- Single-region cho mỗi object: DO chạy tại một location — nếu users phân tán toàn cầu truy cập cùng một object, users xa sẽ có latency cao hơn
- Không phải database quan hệ: Không có JOIN giữa các DO — mỗi entity isolated. Cần cross-entity queries? Dùng D1 hoặc Hyperdrive
- 10GB limit / object: Nếu một entity cần hơn 10GB storage, cần partition thủ công
- Cold start khi hibernate: ~5ms, nhưng vẫn tồn tại — không phù hợp ultra-low-latency trading systems
Roadmap & Tương lai
Kết luận
Cloudflare Durable Objects giải quyết một trong những thách thức lớn nhất của edge computing: state management. Bằng cách áp dụng Actor Model — mỗi entity một instance đơn luồng với storage riêng — DO loại bỏ hoàn toàn distributed locking, race conditions, và consistency headaches mà không đánh đổi latency hay tạo ops burden.
Với SQLite 10GB per object, WebSocket Hibernation giảm 90% chi phí idle connections, Alarms cho scheduled work, và free tier 5GB — đây là nền tảng lý tưởng cho real-time collaboration, multiplayer games, chat systems, và AI agent sessions trên edge.
Tham khảo:
RAG Pipeline 2026 — Xây dựng kiến trúc AI không ảo giác cho Production
AI Agent Evaluation — Cách kiểm thử và đánh giá AI Agent trong Production
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.