独立开发·阅读约 2 分钟·
Durable Objects 的 Alarm 漏写一行判断,8 天烧掉 34,895 美元,全程没预警

Durable Objects 的 Alarm 漏写一行判断,8 天烧掉 34,895 美元,全程没预警

一位开发者公开完整时间线:onStart() 每次唤醒都无条件重排 Alarm,叠加 60 多个预览环境,峰值期每天跑了约 9300 亿次行读取。Workers 用量通知只监控 CPU 时间,DO 读写没有任何告警,也没有硬性花费上限。

原文来源:Hacker News — 一位开发者公开自己被 Durable Objects 烧掉 34,895 美元的完整时间线、根因和修复代码,以及平台为什么一句提醒都没发。

一位开发者在 Hacker News 上贴出了自己的账单事故,原文标题很直白:Durable Object 的 Alarm 循环,8 天 34,000 美元,零用户,平台没有任何预警。

文中提到的 Alarm 指的是 Durable Objects 的定时唤醒接口,也就是代码里的 setAlarmgetAlarm——不要和 Cloudflare 的 Cron Triggers 混为一谈。

事故时间线

  • 4 月 3 日:循环开始(此前这个项目的 DO 用量为零)
  • 4 月 4 日到 5 日:峰值达到每天约 9300 亿次行读取
  • 4 月 11 日:发现问题并修复
  • 4 月 15 日:收到 34,895 美元账单,此时还没有收到计费方的任何回应

整个循环跑了 8 天,期间这个项目一个真实用户都没有。截至发帖,这笔账单仍在申诉中;作者也提到,作为一个产品还没上线的个体开发者,这笔钱是他的全部积蓄。

—— 广告 ——

根因:一行缺少判断的代码

问题出在 DO 实例的 onStart() 处理函数里。每次实例被唤醒,它都无条件调用 setAlarm 重排下一次 Alarm,而不检查是否已经有一个 Alarm 在排队:

code
// 危险写法
async onStart() {
  await this.ctx.storage.setAlarm(Date.now() + 60_000)
}

单看这一行没什么问题。真正放大它的是另一个因素:这位开发者开了 60 多个预览环境,每个部署都会创建独立的 DO 实例。这些实例各自唤醒自己、各自重排 Alarm,最后叠成一个自我健康检查的死循环。

修复方式就是加一次读取判断:

code
// 安全写法
async onStart() {
  const existing = await this.ctx.storage.getAlarm()
  if (!existing) {
    await this.ctx.storage.setAlarm(Date.now() + 60_000)
  }
}

作者还列了几条同样值得做的事:预览环境里干脆去掉 DO 绑定;部署一个预算监控的 kill switch Worker;在安排 Alarm 之前加一个检查当前 Alarm 状态的熔断器。

为什么平台没有预警

这是整件事里最值得警惕的部分。

作者的说法是:Cloudflare 的 Workers Usage Notifications 只监控 CPU 时间,不监控 Durable Object 的行读取和行写入。仪表盘和 Wrangler 配置里也没有为 DO 操作提供硬性花费上限。也就是说,行读取从零涨到每天 9300 亿次的过程中,没有任何一个机制会触发告警。他是在账单出现的时候才知道出事了。

为什么这件事值得写进你的部署清单

少写一个判断,是每个开发者都可能犯的错误。真正让错误变成 3.4 万美元的,是三层防护同时缺失:

  1. 代码层:没有先检查 Alarm 是否已经存在
  2. 环境层:预览环境照搬了生产环境的绑定,60 多个实例各自跑循环
  3. 平台层:缺少针对 DO 读写的用量告警,缺少可配置的花费上限

他认为,平台层缺失的那部分,目前获得的关注远远不够。作者还提到一个时间点上的巧合:事发时正值 Cloudflare 的 Agents Week,官方正在密集推广让个人开发者用 Durable Objects 搭 AI 智能体——博客、公告、一整套宣传。用他的话概括,这是在主动把独立开发者拉进一个能静默产生五位数账单的产品,而计费护栏却不到位。

这个批评是否公允,可以讨论。云平台按用量计费、不设硬上限是行业惯例,AWS 和 GCP 也不提供真正意义上的消费天花板(这句是编者的判断,不属于原帖内容)。但基础事实是清楚的:把一个"专门面向个人开发者的用量型产品"和"只监控 CPU 的通知系统"配在一起,风险敞口就是不对称的。付费的一方拿到的信息,比收费的一方少得多。

用量计费的风险,结构上就是不对称的

(下面是编者自己的分析,不是原帖内容。)

把这件事放大看,它暴露的是用量计费模式的一个结构性缺陷。

按用量付费对开发者是好事:业务小的时候成本接近于零,不用提前买容量。但它有一个隐含前提——付费方必须能实时看到用量。这个前提在传统云资源上基本成立:CPU、内存、带宽都有现成的监控图表,超了能收到告警。而像 DO 行读取这类新的计费维度,监控和告警往往滞后于产品发布:计费口径先上线,防护工具后补,中间的时间差全由用户承担。

更麻烦的是,这类维度的单位量级和直觉差得很远。9300 亿次行读取听上去像个天文数字,但它只是"唤醒频率 × 实例数 × 每次读取行数"层层相乘的结果——每个乘数单独看都不起眼,写代码的时候也没人会把这个乘法算一遍。

所以对个人开发者来说,一条可操作的原则是:任何新接入的按量计费资源,先写一行监控再写第一行业务代码。监控不需要多复杂——每天拉一次用量、跟昨天比、涨了 3 倍就发一条通知给自己。这次的账单之所以能滚到 3.4 万美元,核心原因不是 bug 难修,而是从第 1 天到第 8 天,作者完全不知道数字在涨。

还有一条更实际的建议:把云账号的账单告警阈值设低一点。大多数平台都支持"预计月度支出超过 X 时通知",这个功能的默认值通常高得没有意义。花十分钟把它调到"超过 50 美元就提醒",能挡掉的正是这类失控。

落到自己的项目上

对独立开发者来说,这件事可以直接转成几条部署习惯:

  • 任何按量计费的资源,都要自己写一个余额或用量监控,不要指望平台通知。通知系统通常只覆盖一部分指标,且默认关闭或默认阈值很高。
  • 预览、测试、CI 环境要与生产环境的绑定隔离。一个容易被忽略的点是:预览环境不是免费的,它按同样的单价计费。
  • Alarm、轮询、重试这类自我驱动的逻辑,必须能回答"现在有几层循环在跑"。任何无条件重排自身的代码,都要先想清楚它的触发源有几个。
  • 给关键资源设一个外部 kill switch。它不一定优雅,但在凌晨三点它能救你。

一场事故的代价是 34,895 美元和八天。把上面的清单抄一遍,成本是半小时。这个性价比,大概是今天读到的最划算的一笔交易。

分享到
微博Twitter

© 2026 四月

原文链接:https://www.aprilzz.com/indie/cloudflare-durable-objects-34k-bill