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

Cloudflare Workers Durable Objects Edge Computing Serverless System Architecture

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.

300+Edge locations toàn cầu
<1msCold start trung bình
10GBSQLite storage / Object
FreeTier có sẵn cho DO

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 thực thi tại edge location gần nhất với người dùng

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
Durable Object đảm bảo single-instance consistency — mọi request đều đi qua một instance duy nhất

Ba thuộc tính cốt lõi

Thuộc tínhMô tảÝ nghĩa thực tế
Global UniquenessMỗi DO có ID duy nhất trên toàn hệ thốngGửi request đến đúng object từ bất kỳ đâu trên thế giới
Single-threadedTấ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 StorageSQLite database nằm cùng vị trí với computeZero-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ăngKV Storage (cũ)SQLite Storage (mới)
Query languageget/put/delete/listFull SQL (SELECT, JOIN, INDEX...)
Dung lượng tối đa~50MB10GB per object
TransactionKhôngACID transactions
IndexingChỉ key prefixB-tree indexes tùy chỉnh
ConsistencyStrongSerializable isolation
BackupManual exportPoint-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
Chọn storage phù hợp theo consistency requirement và access pattern
Giải phápConsistencyUse case chínhFree tier
KVEventually consistentConfig, cache, feature flags100K reads/ngày
D1Strong (single-region)CRUD app, relational data5M rows reads/ngày
Durable ObjectsStrong (single-instance)Realtime, coordination, sessionsCó (giới hạn)
R2StrongObject storage, file uploads10GB 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ầnFree TierPaid (Workers Paid $5/tháng)
Workers Requests100K requests/ngày10M requests/tháng (sau đó $0.30/M)
Workers CPU Time10ms CPU/request30s CPU/request
DO RequestsCó (giới hạn)1M requests/tháng (sau đó $0.15/M)
DO StorageCó (giới hạn)1GB included (sau đó $0.20/GB)
DO DurationCó (giới hạn)400K GB-s/tháng
KV100K reads/ngày10M reads/tháng
D15M rows read/ngày25B rows read/tháng
R210GB storage10GB 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 + DOAWS Lambda + DynamoDBAzure Functions + Cosmos DB
Cold start~0ms100-500ms200-1000ms
Edge locations300+30+ regions60+ regions
Stateful computeNative (DO)External (Step Functions)External (Durable Functions)
WebSocketNative + HibernationAPI Gateway WebSocketSignalR Service
Embedded DBSQLite/objectKhôngKhông
Free tierRất hào phóngTốtTốt
Vendor lock-inTrung bìnhCaoCao

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