Deno 开源 celld:用 S3 协调的自托管分布式 Durable Objects

celld — self-hosted, distributed Durable Objects

Deno Land Inc. 开源了 celld,一个用 Rust 编写的守护进程,把 Cloudflare Workers 和 Durable Objects 的运行模型搬到了你自己的机器上。它不依赖任何控制平面或共识协议,所有协调都通过一个 S3 兼容存储桶完成。每个 Durable Object 是一个独立的 SQLite 数据库,以名称寻址,复制到你拥有的存储桶中。节点仅通过存储桶协调,没有成员协议、故障检测器或共识服务。

celld 架构示意图

核心设计:存储桶即协调器

celld 的工作方式可以用一行公式概括:

celld = V8 + SQLite + LTX

每个 celld 节点内嵌 V8 引擎,执行 Wrangler 打包后的 Worker 代码。一个 fleet 共享一个 S3 兼容存储桶,其中存放部署包、cell 状态和少量所有权记录。对象存储的 compare-and-swap(CAS)操作确保同一时刻只有一个节点拥有某个 cell,无需成员协议、故障检测器或共识服务。

每个 cell 的 SQLite 数据库持续复制到存储桶。当 cell 迁移或唤醒时,新拥有者从存储桶恢复数据库并继续执行。存储桶是持久化数据的唯一真相来源(source of truth),节点本身是可替换的。

celld 在数据真正写入存储桶之前不会确认写操作(RPO=0)。这意味着杀死一个节点不会丢失任何已确认的写入。节点丢失后的故障转移在约 20 秒内完成,且零数据丢失。

性能特征

官方公布的基准数据(单节点,Apple M 系列笔记本,loopback 测试):

指标数值
无状态请求 p50 / p990.2 / 0.3 ms
无状态吞吐 / 线程~94k req/s
唤醒休眠 cell~4 ms
持久写延迟(区域内)~90 ms
每个常驻 cell 内存占用4 MB
8 GB 节点可容纳常驻 cell1,000
常驻 cell 月成本~$0.05
节点丢失后故障转移~20 秒
已确认写丢失0(RPO=0)

成本对比

celld 的核心卖点之一是成本优势。Cloudflare Durable Objects 的计费模型按 128 MB × 挂钟时间的常驻时长计算,每个常驻 cell 月成本约 $4.15。celld 通过在自己拥有的 $48/月 8 GB 节点上容纳 1,000 个常驻 cell,将每个 cell 的月成本压到约 $0.05。

在 1,000 个常驻 cell 的规模下,Durable Objects 月费约 $4,150,celld 约 $49。在 10,000 个 cell 规模下,DO 需要 $41,500/月,celld 只需 $486/月。celld 通过按 1,000 个 cell 的步进增加整节点来扩展容量。

当然,这个比较只计算了常驻 cell 的基线成本。实际工作负载还会产生应用流量和存储桶写入的额外费用,但数量级差异已经清晰。

安装与部署

celld 的安装器会下载一个 58 MB 的静态二进制文件,可通过 GitHub Actions build attestation 验证来源:

sh
curl -fsSL https://celld.dev/install.sh | sh

部署流程分三步:

1. 配置对象存储

celld 使用标准 AWS 凭据链。以 Cloudflare R2 为例:

sh
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=auto
export S3_ENDPOINT=https://ACCOUNT_ID.r2.cloudflarestorage.com
export CELLD_BUCKET=s3://YOUR-BUCKET

存储桶凭据拥有 fleet 的完全控制权,需要严格保管。存储桶中包含部署包、SQLite 副本、所有权记录、节点租约和共享的 peer 认证密钥。

2. 部署应用

从 Wrangler 项目目录执行部署。如果项目包含 Worker 代码,需要 PATH 上有 esbuild:

sh
celld deploy . \
  --bucket s3://my-cells-bucket \
  --endpoint https://ACCOUNT.r2.cloudflarestorage.com \
  --region auto

celld deploy 接受 module Workers、Durable Object 绑定和静态资源。纯资源项目(asset-only)可以在没有 Worker 代码的情况下部署。部署器接受受支持的 Wrangler 配置子集,包括共部署的静态资源或纯静态资源,以及 _headers_redirects。如果 Wrangler 配置中包含未知字段,部署会直接报错停止。

3. 启动节点

sh
celld \
  --bucket s3://my-cells-bucket \
  --endpoint https://ACCOUNT.r2.cloudflarestorage.com \
  --region auto \
  --listen 0.0.0.0:8080 \
  --advertise node-a.internal:8080

节点通过存储桶中的租约发现彼此。没有 join 命令,没有固定的成员列表。每个节点从存储桶加载最新的成功提交的部署。

也可以用 Docker 运行:

sh
docker volume create celld-state
docker run --rm --network host \
  -e AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY \
  -e AWS_SESSION_TOKEN \
  -e CELLD_WATCH=/var/lib/celld/state \
  -v celld-state:/var/lib/celld \
  ghcr.io/denoland/celld \
  --bucket s3://my-cells-bucket \
  --endpoint https://ACCOUNT.r2.cloudflarestorage.com \
  --region auto \
  --listen 0.0.0.0:8080 \
  --advertise node-a.internal:8080

Cloudflare Workers 兼容性

celld 运行 Workers 运行时,以 Durable Objects 作为有状态核心。它的兼容性边界有一个明确的规则:如果 Cloudflare 在 Durable Objects 之上构建某个功能,celld 就可以实现该功能;如果是基于不同原语的功能,则不在范围内。

已支持的服务和 API

  • Workers:module Workers、fetch、JS RPC、service bindings、Durable Object bindings、vars
  • Durable Objects:SQLite 存储、alarms、入站可休眠 WebSockets、出站 WebSocket 客户端、每个 cell 单写者
  • 静态资源:从 fleet 存储桶提供不可变文件,支持 assets.directory、binding、html_handling、not_found_handling、run_worker_first,以及 _headers_redirects
  • Worker Loader(实验性):通过 CELLD_WORKER_LOADER 绑定加载器,Worker 可以在运行时启动沙箱隔离体
  • Runtime API:Fetch、Streams(包括 byte streams、BYOB readers、CompressionStream)、WebSockets(入站可休眠 + 出站)、Web Crypto(部分)、WebAssembly(V8 原生,无限制)、Node.js 兼容(部分)

计划中:D1(D1 数据库的结构就是一个带 SQL API 的 Durable Object,celld 已具备核心机制)、Workflows(基于 cells 和 alarms 的持久执行)、Queues(Durable Object 的一种形态)。

不在计划中:KV(不同的一致性模型)、R2(celld 运行在 blob 存储之上,不提供 blob 存储)、Cache API、Workers AI、Vectorize、Hyperdrive、Browser Rendering、Email(这些是托管平台服务)、cron triggers、自定义域名、TLS 终止。

一个未知的配置字段或 API 调用会以显式错误失败,而非静默跳过。静默的兼容性缺口被视为 bug。

压力卸载机制

压力卸载(pressure shedding)在首个版本中是可选的。在负载较重的节点上设置常驻 cell 的高低水位线:

sh
CELLD_MAX_RESIDENT_CELLS=1000 \
CELLD_RESIDENT_LOW_WATER=800 \
celld --bucket s3://my-cells-bucket --listen 0.0.0.0:8080 \
  --advertise node-a.internal:8080

在 Linux 上,CELLD_MAX_RSS_MBCELLD_MAX_CPU_PERCENT 额外提供进程内存和 CPU 触发器。在压力下,celld 会持久复制并隔离最近最少使用的空闲 cell,将它们发布为无主状态(不重置其 epoch),并拒绝重新获取新的无主 cell,直到到达低水位线。

一个备用节点不会收到分配,而是通过同一个存储桶协议在正常流量到达时获取释放的 cell。有活跃工作或有活跃宿主 WebSocket 连接的 cell 不会被卸载。

安全模型

celld 目前是 alpha 阶段,不适用于敌对的多租户环境。安全修复只推送到最新版本,旧 alpha 构建不会获得修复。

信任始于存储桶:S3 兼容存储桶是 fleet 的权限根。每个 cell 的所有权是该存储桶中的一个 CAS 租约。掌握存储桶凭据的人控制整个 fleet,因此凭据应仅授予单个 fleet 存储桶的访问范围。

Peer 认证但不加密:每个节点间请求包含 HMAC、body 签名、时钟限制和重放保护,因此伪造的 peer 无法获得访问,被捕获的请求无法重发。但 celld 不在 peer 协议上终止 TLS——节点间流量是明文 HTTP。节点应仅广播到受信任的私有网络或加密覆盖(如 WireGuard 或 Tailscale),不要将 peer 端口暴露到公网。

单写者隔离:每个 cell 是一个只有一个写者的 SQLite 数据库。一个节点在同一时间拥有一个 cell,ownership epoch 对每个 cell 进行围栏(fencing),因此丢失租约的节点无法破坏状态。fleet 的故障域是其自身的机器、网络和存储桶,没有共享的多租户调度器或共享放置层,一个有缺陷的 cell 只能触及它自己的数据库。

测试策略

celld 做了三个关键承诺:已确认的写是持久的;一个 cell 同时只有一个写者;为 Cloudflare Workers 和 Durable Objects 编写的代码在 celld 上行为一致。测试分三层:

差分执行(Differential execution):每个 Workers 和 Durable Objects 程序在 workerd(Cloudflare 生产环境运行的运行时二进制文件)和 celld 上各跑一次,使用相同的字节,输出必须一致。这同时验证了程序是真正的 Cloudflare 代码(因为 workerd 接受了它)和 celld 遵守了契约(因为输出一致)。

确定性模拟:协调协议的纯决策核心没有自己的 I/O——时钟、随机数和对象存储都是接口,由模拟器驱动。模拟存储注入延迟、CAS 竞争和丢失响应;时钟漂移;节点可以在每个 await 点崩溃。V8 被排除在模拟之外,因为 V8 不是确定性的。每个属性必须在数万个 seed 下存活,核心协议已跑过数百万个不同的调度。

真实 fleet 实验室:永久运行的 fleet 实验室使用标准 VM 和真实存储桶。工作负载轮换:多 WebSocket 连接的聊天室、跨数万个 cell 的变化工作集、负载下的部署切换、填满节点到内存极限的运行。故障在验证轮次之间注入——用 SIGKILL 停止写入流中的节点并删除其本地数据库,冻结拥有者节点再解冻,验证 ownership epoch 是否正确阻止旧状态被服务。

开发者需要注意的限制

  • 一个 fleet 运行一个应用部署。没有多租户调度器、账户服务、托管入口或全局放置层
  • peer HTTP 协议不终止 TLS,需要在私有网络或加密覆盖上运行
  • 存储桶凭据是管理权限,celld 不会使共享的对象存储凭据变安全
  • setInterval 会抛出异常(使用 setTimeout 递归替代)
  • performance.now() 只有毫秒级分辨率
  • Intel Mac 没有预编译二进制文件,Windows 不可用,均可从源码构建
  • Pull requests 被禁用——维护者认为 coding agent 太容易产生大量低上下文的变更,贡献通过 git format-patch 邮件发送

与 Cloudflare Durable Objects 的关系

celld 的文档中明确表达了对 Cloudflare 的尊重。celld 官方页面本身由 Cloudflare Worker 提供服务。Durable Objects 的设计——一个带自己存储的、按名称寻址的单线程对象——被 celld 团队视为分布式系统领域近年来最好的原语之一,这一设计来自 Kenton Varda 和 Cloudflare Workers 团队。celld 的定位是把这套编程模型带到任何地方运行。

celld 采用 Apache-2.0 许可证,项目地址在 github.com/denoland/celld,文档站点在 celld.dev