Cloudflare Workers + Durable Objects — Xây dựng ứng dụng Serverless có trạng thái trên Edge
Posted on: 4/26/2026 11:07:46 AM
Table of contents
- 1. Bài toán trạng thái trên Serverless
- 2. Cloudflare Workers — Nền tảng Edge Computing
- 3. Durable Objects — "Actor Model" trên Edge
- 4. SQLite Storage — Database tích hợp zero-latency
- 5. Kiến trúc thực tế: Khi nào dùng Workers, KV, D1, DO?
- 6. Durable Objects Facets — Tính năng mới 2026
- 7. Các use case phổ biến
- 8. Triển khai và cấu hình
- 9. Pricing và Free Tier
- 10. So sánh với các giải pháp tương đương
- 11. Best Practices
- Tham khảo
1. Bài toán trạng thái trên Serverless
Serverless đã cách mạng hóa cách chúng ta triển khai ứng dụng: không cần quản lý server, tự động scale, chỉ trả tiền khi có request. Nhưng có một vấn đề cố hữu — serverless vốn dĩ là stateless. Mỗi request được xử lý bởi một instance hoàn toàn độc lập, không biết gì về các request trước đó.
Điều này khiến nhiều bài toán thực tế trở nên cực kỳ phức tạp: collaborative editing (Google Docs), multiplayer game, chat room, rate limiting, hay bất kỳ ứng dụng nào cần giữ trạng thái chia sẻ giữa nhiều client. Giải pháp truyền thống là thêm một layer database hoặc cache bên ngoài — nhưng điều đó đi ngược lại triết lý "zero infrastructure" của serverless.
Cloudflare Workers + Durable Objects giải quyết bài toán này một cách thanh lịch: kết hợp compute và storage trong cùng một đơn vị, chạy trên hơn 300 data center toàn cầu, với latency gần bằng zero giữa logic xử lý và dữ liệu.
2. Cloudflare Workers — Nền tảng Edge Computing
Cloudflare Workers là nền tảng serverless chạy code JavaScript/TypeScript trên edge network của Cloudflare. Thay vì code chạy ở một region duy nhất (us-east-1, ap-southeast-1...), Workers thực thi tại data center gần người dùng nhất — giảm latency xuống mức tối thiểu.
graph LR
A[👤 User Vietnam] -->|Request| B[Edge Hanoi]
C[👤 User US] -->|Request| D[Edge New York]
E[👤 User EU] -->|Request| F[Edge Frankfurt]
B --> G[Worker Instance]
D --> H[Worker Instance]
F --> I[Worker Instance]
G --> J[(KV / D1 / DO)]
H --> J
I --> J
Workers sử dụng V8 isolates (cùng engine với Chrome) thay vì container hay VM. Mỗi request được xử lý trong một isolate riêng biệt — khởi động trong vài microsecond thay vì millisecond như Lambda/Cloud Functions.
Ví dụ Worker cơ bản
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/api/hello") {
return Response.json({
message: "Xin chào từ Edge!",
location: request.cf?.city,
country: request.cf?.country
});
}
// Proxy đến origin nếu không match
return fetch(request);
}
};
Điểm mạnh của V8 Isolates
V8 Isolates cho phép Workers khởi động trong ~0ms cold start (so với 100-500ms của AWS Lambda), sử dụng ít memory hơn 10-100x so với container, và có thể chạy hàng nghìn isolate trên cùng một process. Đây là lý do Workers có pricing rẻ hơn đáng kể so với các FaaS truyền thống.
3. Durable Objects — "Actor Model" trên Edge
Durable Objects (DO) là phần mở rộng quan trọng nhất của Workers, giải quyết triệt để vấn đề state. Mỗi Durable Object là một instance duy nhất trên toàn cầu (single-instance), có tên định danh unique, và đi kèm với database SQLite tích hợp.
Nếu bạn quen với Actor Model (Akka, Erlang, Microsoft Orleans), Durable Objects chính là implementation của pattern đó trên edge: mỗi actor xử lý message tuần tự, có state riêng, và được quản lý vòng đời tự động.
graph TB
subgraph "Durable Object: chat-room-42"
DO[DO Instance] --> DB[(SQLite DB)]
DO --> MEM[In-Memory State]
DO --> WS[WebSocket Sessions]
end
U1[Client 1] -->|WebSocket| DO
U2[Client 2] -->|WebSocket| DO
U3[Client 3] -->|WebSocket| DO
W1[Worker Edge HN] -->|stub.fetch| DO
W2[Worker Edge SG] -->|stub.fetch| DO
Ba thuộc tính cốt lõi
| Thuộc tính | Mô tả | Ý nghĩa thực tế |
|---|---|---|
| Global Uniqueness | Mỗi DO có ID duy nhất trên toàn hệ thống | Gửi request đến đúng object từ bất kỳ đâu trên thế giới |
| Single-threaded | Tất cả request đến một DO được xử lý tuần tự | Không cần lock, mutex hay lo race condition |
| Co-located Storage | SQLite database nằm cùng vị trí với compute | Zero-latency giữa code và data, strong consistency |
Ví dụ: Chat Room với Durable Objects
// Worker entry point — route đến Durable Object
export default {
async fetch(request, env) {
const url = new URL(request.url);
const roomId = url.searchParams.get("room") || "default";
// Lấy stub đến Durable Object theo tên
const id = env.CHAT_ROOM.idFromName(roomId);
const stub = env.CHAT_ROOM.get(id);
return stub.fetch(request);
}
};
// Durable Object class
export class ChatRoom {
constructor(state, env) {
this.state = state;
this.sessions = [];
}
async fetch(request) {
// Upgrade lên WebSocket
const [client, server] = Object.values(new WebSocketPair());
this.state.acceptWebSocket(server);
// Load lịch sử từ SQLite
const history = this.state.storage.sql.exec(
"SELECT sender, message, timestamp FROM messages ORDER BY timestamp DESC LIMIT 50"
).toArray().reverse();
// Gửi lịch sử cho client mới
server.send(JSON.stringify({ type: "history", data: history }));
return new Response(null, { status: 101, webSocket: client });
}
async webSocketMessage(ws, msg) {
const data = JSON.parse(msg);
// Lưu vào SQLite
this.state.storage.sql.exec(
"INSERT INTO messages (sender, message, timestamp) VALUES (?, ?, ?)",
data.sender, data.message, Date.now()
);
// Broadcast đến tất cả clients
for (const session of this.state.getWebSockets()) {
session.send(JSON.stringify({
type: "message",
sender: data.sender,
message: data.message
}));
}
}
async webSocketClose(ws) {
ws.close();
}
}
WebSocket Hibernation
Durable Objects hỗ trợ WebSocket Hibernation — khi không có message nào được gửi/nhận, DO có thể "ngủ đông" để tiết kiệm tài nguyên mà vẫn giữ kết nối WebSocket. Khi có message mới, DO tự động "thức dậy" và xử lý. Điều này cho phép bạn duy trì hàng nghìn kết nối WebSocket đồng thời mà chi phí gần bằng zero khi idle.
4. SQLite Storage — Database tích hợp zero-latency
Từ năm 2025, Cloudflare đã chuyển Durable Objects Storage sang dùng SQLite làm backend chính thay vì key-value store trước đó. Đây là bước tiến lớn: bạn có full relational database ngay bên trong mỗi Durable Object, không cần kết nối đến bất kỳ database service nào bên ngoài.
| Tính năng | KV Storage (cũ) | SQLite Storage (mới) |
|---|---|---|
| Query language | get/put/delete/list | Full SQL (SELECT, JOIN, INDEX...) |
| Dung lượng tối đa | ~50MB | 10GB per object |
| Transaction | Không | ACID transactions |
| Indexing | Chỉ key prefix | B-tree indexes tùy chỉnh |
| Consistency | Strong | Serializable isolation |
| Backup | Manual export | Point-in-time recovery |
// Khởi tạo schema trong constructor
async initialize() {
this.state.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
data TEXT,
expires_at INTEGER,
created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX IF NOT EXISTS idx_sessions_user
ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires
ON sessions(expires_at);
`);
}
// Query với prepared statements
getActiveSessions(userId) {
return this.state.storage.sql.exec(
`SELECT * FROM sessions
WHERE user_id = ? AND expires_at > ?`,
userId, Date.now()
).toArray();
}
5. Kiến trúc thực tế: Khi nào dùng Workers, KV, D1, DO?
Cloudflare cung cấp nhiều storage option, mỗi loại phù hợp với use case khác nhau. Việc chọn đúng giải pháp là quyết định kiến trúc quan trọng nhất.
graph TB
REQ[Incoming Request] --> W[Worker]
W --> |"Static config, cache"| KV[(KV Store)]
W --> |"Relational queries, analytics"| D1[(D1 Database)]
W --> |"Stateful logic, realtime"| DO[Durable Objects]
W --> |"Large files, media"| R2[(R2 Storage)]
DO --> |"Per-object data"| SQL[(SQLite tích hợp)]
KV -.-> |"Eventually consistent"| KV
D1 -.-> |"Strongly consistent"| D1
DO -.-> |"Single-instance consistent"| DO
| Giải pháp | Consistency | Use case chính | Free tier |
|---|---|---|---|
| KV | Eventually consistent | Config, cache, feature flags | 100K reads/ngày |
| D1 | Strong (single-region) | CRUD app, relational data | 5M rows reads/ngày |
| Durable Objects | Strong (single-instance) | Realtime, coordination, sessions | Có (giới hạn) |
| R2 | Strong | Object storage, file uploads | 10GB storage |
Khi nào KHÔNG nên dùng Durable Objects
DO không phải silver bullet. Không dùng DO cho: read-heavy workload không cần coordination (dùng KV), relational queries phức tạp cross nhiều entity (dùng D1), hay static content serving (dùng R2 + Cache). DO tốt nhất khi bạn cần coordination giữa nhiều client trên cùng một entity.
6. Durable Objects Facets — Tính năng mới 2026
Facets là tính năng mới nhất của Durable Objects, cho phép dynamically loaded code quản lý persistent state. Thay vì mỗi Durable Object chỉ có một class duy nhất xử lý mọi logic, Facets cho phép bạn chia logic thành các module độc lập — mỗi facet quản lý một phần state riêng biệt.
Điều này đặc biệt quan trọng cho AI Agent: một agent có thể có facet riêng cho memory, facet cho tool execution, facet cho conversation history — mỗi phần được develop và deploy độc lập.
// Facet cho AI Agent memory
export class AgentMemoryFacet {
constructor(state) {
this.state = state;
state.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS memories (
id TEXT PRIMARY KEY,
content TEXT,
embedding BLOB,
created_at INTEGER
)
`);
}
async store(memory) {
this.state.storage.sql.exec(
"INSERT INTO memories (id, content, created_at) VALUES (?, ?, ?)",
crypto.randomUUID(), memory.content, Date.now()
);
}
async recall(query, limit = 10) {
return this.state.storage.sql.exec(
"SELECT * FROM memories ORDER BY created_at DESC LIMIT ?",
limit
).toArray();
}
}
// Facet cho tool execution tracking
export class ToolExecutionFacet {
constructor(state) {
this.state = state;
state.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS executions (
id TEXT PRIMARY KEY,
tool_name TEXT,
input TEXT,
output TEXT,
duration_ms INTEGER,
executed_at INTEGER
)
`);
}
async logExecution(toolName, input, output, durationMs) {
this.state.storage.sql.exec(
`INSERT INTO executions (id, tool_name, input, output, duration_ms, executed_at)
VALUES (?, ?, ?, ?, ?, ?)`,
crypto.randomUUID(), toolName,
JSON.stringify(input), JSON.stringify(output),
durationMs, Date.now()
);
}
}
7. Các use case phổ biến
7.1 Rate Limiter phân tán
export class RateLimiter {
constructor(state) {
this.state = state;
state.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS requests (
ip TEXT, timestamp INTEGER
);
CREATE INDEX IF NOT EXISTS idx_ip_ts ON requests(ip, timestamp);
`);
}
async fetch(request) {
const ip = request.headers.get("CF-Connecting-IP");
const windowMs = 60000; // 1 phút
const maxRequests = 100;
const now = Date.now();
// Xóa records cũ và đếm requests trong window
this.state.storage.sql.exec(
"DELETE FROM requests WHERE timestamp < ?", now - windowMs
);
const count = this.state.storage.sql.exec(
"SELECT COUNT(*) as cnt FROM requests WHERE ip = ? AND timestamp > ?",
ip, now - windowMs
).one().cnt;
if (count >= maxRequests) {
return new Response("Rate limit exceeded", { status: 429 });
}
this.state.storage.sql.exec(
"INSERT INTO requests (ip, timestamp) VALUES (?, ?)", ip, now
);
return new Response("OK", {
headers: { "X-RateLimit-Remaining": String(maxRequests - count - 1) }
});
}
}
7.2 Distributed Counter (E-commerce Flash Sale)
export class InventoryCounter {
constructor(state) {
this.state = state;
}
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/reserve") {
// Single-threaded = không race condition
const current = (await this.state.storage.get("stock")) || 0;
if (current <= 0) {
return Response.json({ success: false, reason: "out_of_stock" });
}
await this.state.storage.put("stock", current - 1);
return Response.json({ success: true, remaining: current - 1 });
}
if (url.pathname === "/stock") {
const stock = (await this.state.storage.get("stock")) || 0;
return Response.json({ stock });
}
}
}
8. Triển khai và cấu hình
Cấu hình Workers + Durable Objects qua file wrangler.toml:
# wrangler.toml
name = "my-app"
main = "src/index.ts"
compatibility_date = "2026-04-01"
# Khai báo Durable Object bindings
[durable_objects]
bindings = [
{ name = "CHAT_ROOM", class_name = "ChatRoom" },
{ name = "RATE_LIMITER", class_name = "RateLimiter" }
]
# Khai báo migrations cho Durable Objects
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ChatRoom", "RateLimiter"]
# KV namespace (nếu cần)
[[kv_namespaces]]
binding = "CACHE"
id = "abc123"
# D1 database (nếu cần)
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "xyz789"
Deploy bằng Wrangler CLI:
# Cài đặt
npm install -g wrangler
# Login
wrangler login
# Dev local
wrangler dev
# Deploy lên production
wrangler deploy
CI/CD với GitHub Actions
Cloudflare cung cấp cloudflare/wrangler-action@v3 để tích hợp deploy tự động. Chỉ cần thêm CLOUDFLARE_API_TOKEN vào GitHub Secrets và cấu hình workflow — mỗi push lên main sẽ tự động deploy lên production.
9. Pricing và Free Tier
| Thành phần | Free Tier | Paid (Workers Paid $5/tháng) |
|---|---|---|
| Workers Requests | 100K requests/ngày | 10M requests/tháng (sau đó $0.30/M) |
| Workers CPU Time | 10ms CPU/request | 30s CPU/request |
| DO Requests | Có (giới hạn) | 1M requests/tháng (sau đó $0.15/M) |
| DO Storage | Có (giới hạn) | 1GB included (sau đó $0.20/GB) |
| DO Duration | Có (giới hạn) | 400K GB-s/tháng |
| KV | 100K reads/ngày | 10M reads/tháng |
| D1 | 5M rows read/ngày | 25B rows read/tháng |
| R2 | 10GB storage | 10GB free, $0.015/GB sau đó |
So sánh chi phí với AWS
Một ứng dụng realtime chat với 10K concurrent users trên AWS cần: API Gateway WebSocket ($1/M messages) + Lambda ($0.20/M requests) + DynamoDB ($1.25/M writes) + ElastiCache. Trên Cloudflare: Workers + Durable Objects với chi phí thấp hơn 3-5x, không cần quản lý infrastructure, và latency thấp hơn nhờ edge computing.
10. So sánh với các giải pháp tương đương
| Tiêu chí | CF Workers + DO | AWS Lambda + DynamoDB | Azure Functions + Cosmos DB |
|---|---|---|---|
| Cold start | ~0ms | 100-500ms | 200-1000ms |
| Edge locations | 300+ | 30+ regions | 60+ regions |
| Stateful compute | Native (DO) | External (Step Functions) | External (Durable Functions) |
| WebSocket | Native + Hibernation | API Gateway WebSocket | SignalR Service |
| Embedded DB | SQLite/object | Không | Không |
| Free tier | Rất hào phóng | Tốt | Tốt |
| Vendor lock-in | Trung bình | Cao | Cao |
11. Best Practices
Thiết kế Durable Object hợp lý
1 DO = 1 entity logic. Mỗi chat room là 1 DO, mỗi user session là 1 DO, mỗi game match là 1 DO. Không nên tạo 1 DO "thần" xử lý mọi thứ — điều đó tạo ra bottleneck vì DO là single-threaded. Hãy nghĩ DO như microservice có state riêng.
Checklist kiến trúc:
- Partition theo entity: mỗi Durable Object đại diện cho một entity nghiệp vụ (user, room, order)
- Dùng Alarms cho background work: thay vì polling, dùng
state.storage.setAlarm()để schedule task tương lai - WebSocket Hibernation: luôn dùng
state.acceptWebSocket()thay vì quản lý WebSocket thủ công — tiết kiệm billable duration đáng kể - Batch writes: nhóm nhiều storage operations vào một transaction để giảm I/O
- Graceful migration: dùng migration tags trong wrangler.toml khi thay đổi storage schema
- Error boundary: luôn wrap logic trong try-catch vì uncaught exception sẽ reset DO state trong memory
Tham khảo
- Cloudflare Durable Objects Documentation — Cloudflare Docs
- Cloudflare Workers Documentation — Cloudflare Docs
- Workers Durable Objects Beta: A New Approach to Stateful Serverless — Cloudflare Blog
- Build stateful apps with Durable Objects — Cloudflare
- Edge Computing with Cloudflare Workers: Complete Guide 2026 — CalmOps
Debugger Agent trong Visual Studio 2026 — Khi AI tự debug code cho bạn
Progressive Web Apps 2026: Offline-First với Service Worker, Workbox và Vue.js
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.