celld:把 Durable Objects 相容 runtime 放進自己的機器
celld 是 Deno Land 開源的 daemon。它在自己的 Linux 機器跑一部分 Cloudflare Workers 與 Durable Objects API,讓每個 named object 有一份 SQLite 狀態,並複寫到自己控制的 S3 相容 bucket 或 Google Cloud Storage。[1][2]
可以把它理解成「自架的 Durable Objects 相容層」。它不等於把整個 Cloudflare 平台搬回家。KV、R2、Cache API、Workers AI、Vectorize、Hyperdrive、Cron、Cloudflare 網域和 TLS 都不在目前範圍內。[3][4]
查核 main commit ae8fac053d79f971bfcb996054bb43eb2f9b05da,版本 v0.2.1,2026-08-14。GitHub 頁面當時約 4,000 stars、126 forks。這些是查核快照,不等於生產成熟度或相容性保證。Repo 採 Apache-2.0。[1][8]
本輪只讀 repo、官方相容性文件、限制與 Cloudflare 官方文件,沒有安裝 binary、建立 bucket、部署 Worker 或啟動任何 listener。
先看結論
- 想做協作文件、聊天室、遊戲房、單一使用者工作區、rate limiter、presence 或每個帳號一份 state,celld 的 object 模型很適合。[2][5]
- 想沿用 Workers + Durable Objects 的大部分程式心智模型,又想把執行地點、bucket、網路與帳單放回自己的基礎設施,celld 值得評估。[2][3]
- 想用 Cloudflare 的 CDN、KV、R2、D1、AI、TLS、流量吸收與全球邊緣調度,直接用 Cloudflare Durable Objects 比較合適。[3][5]
- celld 仍是 alpha,fleet 只有單一 application deployment,沒有管理式 ingress、central placement controller 或自動更新。[4]
- 對外流量要自己放 load balancer/ingress proxy。內部 peer port 只能留在私網或加密 overlay,bucket 憑證等同 fleet 管理權限。[2][4]
Durable Objects 是什麼
Cloudflare Durable Object 是有固定全域名稱的特殊 Worker。每個 object 同時持有 compute 與 durable storage,因此多個 client 可以把請求送到同一個 object,由它協調同一份狀態。[5]
常見形狀如下:
room:general → 一個聊天室狀態與 WebSocket 連線集合
user:42 → 一個使用者的節流計數、偏好或同步隊列
doc:abc → 一份協作文件的 operation 與 snapshot
auction:item-9 → 一場競標的順序、倒數時間與得標狀態
Durable Object 的重點不在「有一個資料庫」,而在「特定名字永遠指向同一個可協調的 stateful entity」。Cloudflare 文件說明,它有 globally unique name,儲存與 object 共置,提供 strongly consistent 的 state。[5]
這個模型避開了多人同時改一列資料時的 coordination 邏輯。聊天室可以依 room ID 分片,協作文件依 doc ID 分片,使用者狀態依 user ID 分片。每個 object 的衝突和故障半徑自然收在自己的 key 裡。
celld 怎麼實作這個模型
celld 把 Durable Object 稱為 cell。每個 cell 是一個 SQLite database。node 內嵌 V8,執行 Wrangler bundle。所有 node 共用一個 bucket,裡面放 deployment、cell state 與小型 ownership record。[2]
flowchart TB U["使用者"] --> I["TLS / Load Balancer"] I --> L["celld public listener"] L --> W["Named cell Worker handler"] W --> S["本地 SQLite state"] S -->|非同步複寫| B["S3 / GCS bucket: deployments, history, ownership"] B -->|ownership 轉移時還原| W
ownership 使用 object storage 的 compare-and-swap。celld 的說法是每個 cell 同一時間只會由一個 node 擁有,不靠 member list、control plane 或 consensus service。cell 移動或重新啟動時,新 owner 從 bucket 還原 SQLite state 後繼續執行。[2]
fencing 的細節值得知道:ownership record 會帶 epoch,複寫資料也依 epoch 分隔;文件主張寫入要先 durable 落到 bucket,並重新讀取 ownership 後才 ack,藉此避免舊 owner 回來覆寫新資料。這是 repo 的設計宣稱,沒有在本輪做故障注入或一致性測試。[9]
alpha 與網路安全邊界
celld 官方把目前版本列為 alpha,明說不適用 hostile multi-tenant workload。它也不終結 TLS;public listener 要放在外部 ingress/TLS proxy 後面。internal listener 是 peer 和 operator API,文件要求留在私網、WireGuard 或 Tailscale,不可暴露到 Internet。peer traffic 有 HMAC、body signature、時鐘上限與 replay 防護,這些不等於替你的 public ingress、bucket policy、tenant isolation 做完安全設計。[10]
這帶來幾個實際效果:
- state 依 object name 自動分片
- 閒置、沒有 node 持有的 cell 幾乎沒有執行成本
- node 是可替換的,bucket 才是 durable source of truth
- 一個 cell 的熱點不會直接鎖住其他 cell
v0.2.0 起,celld 把多個 resident cell 放在共享 isolate pool,並加入壓力下的 idle cell shedding、寫入複寫 compaction、graceful drain 與 public/internal listener 分離。這些都是 repo 的設計與測量敘述,本輪未自行壓測。[1][2]
能做什麼
| 類型 | object key | cell 裡面放什麼 | 為什麼適合 |
|---|---|---|---|
| 即時聊天室 | room:<id> | WebSocket、在線名單、最後訊息、rate limit | 同一個 room 的所有人會到同一份協調 state |
| 協作文件 | doc:<id> | operation log、snapshot、在線編輯者 | 文件邊界就是自然分片單位 |
| 遊戲房間 | match:<id> | 回合、玩家狀態、倒數 alarm | 需要有順序處理事件與定時觸發 |
| 每位使用者 agent | agent:<user-id> | agent session、排隊工作、權限範圍 | 將長期 session 與其他使用者隔離 |
| 防重複下單 | order:<id> | idempotency key、狀態機 | 把同一訂單的並發收在一個 entity |
| API 節流 | token:<customer-id> | token bucket、次數、reset alarm | 計數與判斷在同一處完成 |
Durable Object 也有 alarms 與 hibernatable WebSockets。celld 的相容面列出 SQLite storage、alarms、inbound hibernatable WebSockets、outbound WebSocket client、named-object address 和 stub RPC。[3]
怎麼接
1. 寫 Workers + Durable Objects 專案
celld 的部署工具讀 wrangler.jsonc 或 wrangler.json。它接受 name、main、compatibility_date、compatibility_flags、durable_objects、migrations、assets、services、vars 這些 config key。[3]
示意設定:
{
"name": "collab-app",
"main": "src/index.ts",
"compatibility_date": "2026-08-20",
"durable_objects": {
"bindings": [{ "name": "ROOM", "class_name": "Room" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["Room"] }]
}
程式的概念仍是從 Worker 取得 namespace,再用 name 取 object ID:
export default {
async fetch(request: Request, env: Env) {
const room = new URL(request.url).pathname.split("/").at(-1)!;
const id = env.ROOM.idFromName(room);
return env.ROOM.get(id).fetch(request);
},
};
export class Room extends DurableObject {
async fetch(request: Request) {
// 讀寫本 room 的 state,或升級 WebSocket
return new Response("room ready");
}
}
這段是 Workers/DO 的最小模型示意,沒有對 celld 實跑。實際 API 相容範圍要以 celld 的 compatibility table 為準。[3][5]
2. 準備 state bucket 和 node
celld 需要 S3-compatible bucket 或 Google Cloud Storage。沒有純 local filesystem mode。即使是本機開發,也需要有 conditional writes 的真 bucket 或相容 object store。[2][4]
它用標準 AWS credential chain,或 GCS 的 Application Default Credentials。憑證必須縮到最小 bucket 範圍。拿到 bucket 的憑證,等同拿到 fleet 管理權限。[2][4]
README 的模式是先部署,再讓一台或多台 node 指向同一個 bucket:
celld deploy . --bucket s3://my-cells-bucket
celld \
--bucket s3://my-cells-bucket \
--listen 0.0.0.0:8080 \
--internal-listen 10.0.0.12:8081 \
--advertise node-a.internal:8081
S3 相容服務要補 --endpoint。Google Cloud Storage 使用 gs:// bucket,認證與 S3 不同。[2]
3. 網路要切 public 和 internal
v0.2.0 將資料平面和 control plane 拆開:
- public listener:只服務 Worker routes 和
/__celld/health - internal listener:operator API、node-to-node peer traffic
internal port 不可公開。README 要求放私有網路或加密 overlay,例如 WireGuard、Tailscale,並由外部 ingress proxy 處理 public TLS。[2][4]
部署時至少做這幾件事:
- load balancer 只指向 public listener
- firewall 不開 internal listener 給網際網路
- bucket 權限只給 fleet 所需 prefix
- 把
--advertise指到 node 之間真正可達的 private address - 在更新或擴容前跑
celld diagnose --bucket ...
相容範圍與移植策略
celld 支援 module Worker、fetch、JS RPC、service bindings、DO bindings、vars 與 static assets。無法用的 binding 或 config 原則上應在 deploy 或 first use 報錯。[3]
幾個很重要的空洞:
| Cloudflare 功能 | celld 現況 | 遷移時怎麼處理 |
|---|---|---|
| Durable Objects | 核心支援 | 可先從 DO-only app 評估 |
| D1 | planned | 改用 cell SQLite 或外接資料庫 |
| KV | out of scope | 另接 Redis/Valkey 或保留 Cloudflare KV |
| R2 | out of scope | celld 自己使用 S3/GCS,應用 blob 改接同一類 object storage |
| Cache API | 無 | 交給 CDN/reverse proxy/外部 cache |
| Workers AI/Vectorize | 無 | 外接模型或向量服務 |
| Cron Trigger | 無 | 用 DO alarm、外部 scheduler 或系統排程 |
| TLS、custom domains | 無 | Caddy、Nginx、Traefik 或雲端 LB |
wrangler.toml | 不接受 | 轉成 wrangler.jsonc |
適合的遷移順序是:先抽一個 DO-only 功能,例如 room presence 或 user rate limit,讓它只依賴 Worker + DO + SQLite。確認壓力、失敗轉移、WebSocket 與 bucket 成本後,再決定是否擴大。
費用:Cloudflare 跟自架怎麼比
兩條路的帳單模型不同。
Cloudflare Durable Objects
Cloudflare Durable Objects 有 compute 和 storage 兩種計費。Workers Paid 最低每月 5 美元,DO Paid 含 100 萬 requests、40 萬 GB-s duration、SQLite 250 億 row reads、5,000 萬 row writes 與 5 GB-month storage,超量再按使用計費。[6][7]
| DO Paid 項目 | 包含量 | 超量價格 |
|---|---|---|
| Requests | 100 萬/月 | US$0.15/百萬 |
| Duration | 40 萬 GB-s/月 | US$12.50/百萬 GB-s |
| SQLite rows read | 250 億/月 | US$0.001/百萬 rows |
| SQLite rows written | 5,000 萬/月 | US$1.00/百萬 rows |
| SQLite storage | 5 GB-month | US$0.20/GB-month |
WebSocket 要特別注意 duration。傳統 accept() 會讓 object 在連線期間持續產生 duration;使用 WebSocket Hibernation 才能讓 idle state 不算 duration。Cloudflare 官方的 100 個 objects、各 50 條 socket、每日 8 小時活躍的範例,總估算為每月 US$419.30。採 hibernation 的類似模型能顯著下降。[6]
celld 自架
celld 自身採 Apache-2.0,沒有服務使用費。帳單轉成你選的:
- VM 或裸機的 CPU/記憶體/磁碟
- S3、R2、MinIO 或 GCS 的 storage 與 operation
- load balancer、TLS proxy、private network/overlay
- logging、metrics、on-call、備份與升級的工程成本
celld 用 S3-compatible bucket 當 replicated state 和 ownership authority。它不等同於 R2 binding,但 R2 可作 S3-compatible bucket 端點,這是 README 提到的部署方式。[2][3]
因此自架比較適合已有固定機器、私網與 object storage 的團隊,或資料/合規要求必須離開 managed edge 的情境。若目標是最快開始、全球就近啟動、CDN 與平台服務一起用,Cloudflare 的管理式 DO 通常省下更多操作成本。
目前限制與風險
- alpha,單一 fleet 只跑一個 application deployment,沒有 multi-tenant scheduler 或 account service。[4]
- 沒有 central placement controller。新 node 加入不會主動重平衡,正常流量才會讓 unowned/released cell 找到可用 node。[4]
- TLS 不在 peer protocol 內,必須由 ingress proxy 或私網/overlay 處理。[4]
- peer 跨 node WebSocket reconnect 的測試覆蓋較薄。對 latency 敏感的 cell,應盡量讓 ingress 對到 owner node。[4]
- Windows 不支援,Intel Mac 沒有 prebuilt binary。[4]
- 有些 API 還是 partial,且個別 Node/Cloudflare API 可能提供 inert stub 或缺口。production 前要以 compatibility table 逐項測自己的依賴。[3]
- 不要直接把 internal listener、operator API 或 bucket credentials 曝露到 public internet。[2][4]
結語
celld 的價值在於把「一個 key 對一個可協調、可持久化 stateful entity」這個 Durable Objects 模型帶到自己控制的機器與 bucket。它很適合切出聊天房、文件、遊戲局、每位使用者 agent session 或節流器等 entity 型工作負載。
它目前仍是功能邊界清楚的 alpha runtime。拿它替代 Cloudflare 前,先盤點你依賴的是 Durable Objects 本身,還是 Cloudflare 的整套邊緣平台服務。前者能開始做相容性與故障轉移驗證,後者大多還需要保留 managed platform 或自行補上基礎設施。
Sources
[1] https://github.com/denoland/celld — denoland/celld repository [2] https://raw.githubusercontent.com/denoland/celld/ae8fac053d79f971bfcb996054bb43eb2f9b05da/README.md — celld README v0.2.1 commit ae8fac0 [3] https://celld.dev/docs/cloudflare-compat — celld Cloudflare compatibility [4] https://celld.dev/docs/limitations — celld limitations [5] https://developers.cloudflare.com/durable-objects — Cloudflare Durable Objects overview [6] https://developers.cloudflare.com/durable-objects/platform/pricing — Cloudflare Durable Objects pricing [7] https://developers.cloudflare.com/workers/platform/pricing — Cloudflare Workers pricing [8] https://raw.githubusercontent.com/denoland/celld/ae8fac053d79f971bfcb996054bb43eb2f9b05da/LICENSE — celld Apache-2.0 license [9] https://raw.githubusercontent.com/denoland/celld/ae8fac053d79f971bfcb996054bb43eb2f9b05da/docs/fencing.md — celld fencing design [10] https://raw.githubusercontent.com/denoland/celld/ae8fac053d79f971bfcb996054bb43eb2f9b05da/docs/security.md — celld security boundary
先把邊界講清楚,工具才能變成可靠流程。
Signals
Visits
--
Waiting for Cloudflare metrics.