氛围编码翻车:CFO 用 Claude Code 两天上线产品,一个 Bug 让 AI 一天烧掉一个月服务器费
云基础设施工程师接手 CFO 用 Claude Code 两天搭出的 SaaS 产品,发现重试风暴 Bug 导致单日 L…
一名高级云基础设施工程师 Jumpei Ueno 近日接手了一个由「氛围编码」留下的项目——它的开发者并非专业工程师,而是公司 CFO。CFO 借助 Claude Code,仅用两天就完成了一款 SaaS 产品的开发并上线,随后将维护工作交接给他。真正进入系统排查后,Ueno 发现最严重的问题不是密钥管理混乱,也不是缺乏测试,而是云服务成本已经失控,单日的 AI 调用费用甚至超过了整个服务器集群一月的运行成本。当 Ueno 询问 CFO 那天究竟做了什么时,得到的回答只有一句:「说实话,我已经不记得那天做了什么。」
排查起点:单日 API 费用几乎占满整个月
Ueno 在查看 LLM API 费用图表时注意到异常:某一天的支出几乎贴着天花板,其余日期则贴近底部,单日费用约相当于该月总开销的一半。换句话说,维持所有服务器运行一个月,比让 AI 工作一天还便宜。最初他推测是开发当天反复在生产环境中测试,累积了大量昂贵的大模型调用。但当他深入排查应用侧日志——任务队列、数据库和请求记录后,发现事实并非如此:并不是人工操作累积出「慢性烧钱」,而是同一批高成本任务被机器完整重跑了 21 次。对于同一个租户,一个本应只运行一次的任务被执行了 21 遍。
真正可怕之处:任务「成功」了,却倒在最后一步
这项批处理任务会先向多个 LLM 发起请求,再把结果写入数据库。代码引用了一个新增字段,但当时数据库迁移尚未执行,该字段在生产环境中并不存在,于是数据库抛出 column does not exist 错误,整个任务以 500 状态码结束。表面上看是任务失败,但所有 LLM 请求都已成功返回 200 并完成计费,模型推理与结果返回都已发生,只是在写入数据库的最后一步出了问题。Ueno 用了一个比喻:这就像在餐厅吃完整套套餐并结完账,正准备起身说「谢谢招待」时摔了一跤、失忆,醒来后又重新点了一整套套餐,循环了 21 次。已经完成的大模型调用不会取消计费,而每一次重试系统都会从头再来。
三个因素叠加,触发「重试风暴」
进一步排查揭示了事故的两个根本原因。其一是部署顺序出错:代码先于数据库迁移部署到生产环境,程序每次访问一个尚未存在的字段,造成确定性失败——无论重试多少次都不可能自行恢复。其二是任务队列对 500 错误的自动重试机制:托管任务队列默认将 500 视为临时故障而自动重跑,但这次的失败原因并非网络抖动,而是数据库结构缺失;同时该批处理任务不具备幂等性,每次重试都会从头重新调用所有 LLM。确定性失败 + 自动重试 + 非幂等设计,三者叠加,使得资金在悄无声息中持续外流。CFO 之所以完全记不起自己做了什么,是因为真正不断「按下按钮」的并不是人,而是任务队列。
给接手者的几条工程教训
- 确定性失败不会因重试而自行恢复:数据库 Schema 不一致或 4xx 等程序性错误应立即终止,而非无限重试;任何重试机制都应设置明确的次数上限。
- 副作用大的任务必须具备幂等性:涉及真实成本的调用,如计费 API 或大模型接口,从第一天起就应具备「跳过已完成任务」的能力,否则每一次重试都意味着重复计费。
- 生产部署应遵循「先更新数据库,再部署代码」的顺序:避免在两者之间的时间窗口内产生确定性错误。
- 成本可观测性不可或缺:本次发现异常纯属偶然,如果没有将生产环境与测试环境使用不同的 API Key,也没有预算告警,几乎不会有人提前察觉。
Ueno 在总结中指出,AI 大幅降低了非工程人员搭建生产系统的门槛,但「知道如何把功能做出来」与「知道系统会如何出错、如何无声无息地烧掉成本」是两种完全不同的能力,后者仍需专业工程师兜底。两天时间足以开发出一个功能,但要避免「善意的自动重试」演变成「丢弃已成功的结果再重新计费」的事故,却绝不是两天能学会的。
