Cloudflare Durable Objects — Stateful Edge Computing không cần Server

Posted on: 5/5/2026 10:11:35 AM

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
  • 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
10 GB SQLite storage / object
0 ms Coordination latency (single-threaded)
300+ Edge locations toàn cầu
$0 Free tier (5GB storage)

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ạnhDurable ObjectsTruyền thống (Redis + WS Server)
ConsistencyStrong by design (single-threaded)Cần distributed locks / Redis WATCH
ScalingTự động — 1 instance / entityManual sharding, sticky sessions
LatencyEdge gần user (sub-50ms)Centralized (100-400ms cross-region)
WebSocketNative + Hibernation (tiết kiệm 90% cost idle)Always-on, trả tiền idle connections
Storage10GB SQLite / entity, PITR 30 ngàyRedis: in-memory (đắt), PostgreSQL: shared
OperationsZero — 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

Tháng 4/2025
SQLite storage GA (10GB), Free tier mở cho tất cả
Tháng 1/2026
SQLite storage billing bắt đầu tính phí, PITR 30 ngày
Tháng 4/2026 — Agents Week
Durable Object Facets: dynamic workers với isolated SQLite. Flagship Feature Flags built on DO + KV
Tương lai
Outgoing WebSocket Hibernation, multi-region replication (eventual consistency mode), larger storage limits

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: