工程师复盘一起因部署顺序错误与自动重试叠加引发的 LLM API 费用事故,单日账单超过整月服务器开销。
一位工程师近日复盘了一起发生在自家 SaaS 服务中的费用事故:某一天 LLM API 的开销几乎相当于整个月服务器费用之和,账单上出现了一座「富士山」式的尖峰。最初他怀疑是产品负责人(CFO 出身的非工程师)反复测试造成,但深入排查日志后才发现,真正的「纵火犯」是后台的任务队列与一段部署顺序颠倒的代码。
故事起点是一款由非工程师借助 Claude Code 快速搭建、内部已上线运行的 SaaS。工程师接手后逐项审查后端实现,发现某一天 LLM API 的费用曲线异常突兀,单日花费接近其余日总和的一半。「我盯着那张图,胃往下沉——单日 AI 用量比跑一整个月的服务器舰队还贵。」他回忆道。
工程师的第一反应是「人肉压测」:查看当日提交记录,发现从早到晚有 20 多次围绕 AI 生成流程的改动,看似符合「人为反复触发」的特征。但当他核对应用侧日志(任务队列、数据库、请求流水)后,看到的画面完全不同:同一个重量级批量任务被机器完整重跑了 21 次。「人不会在一天里把同一个按钮按 21 次,按按钮的根本不是人。」
这起事故最反直觉的地方在于:所有 LLM 调用本身全部成功,返回了 200,费用正常计费。批量任务的执行链路大致如下:
问题出在第二步:代码假设某个新列已经存在,但生产环境的数据库尚未添加该列,于是写入时报 column does not exist,任务以 500 失败。任务队列识别到 500 后,自动把整个批量任务重新跑了一遍。由于任务不具备幂等性,每次重跑都从零开始,全部 LLM 调用再次计费。工程师用吃饭类比:吃完整顿饭、付完账,刚要道谢就摔倒失忆,回到座位再点一模一样的套餐——重演 21 次。
这就是所谓的「重试风暴(retry storm)」,但与常见的「反复失败」不同,这次是「反复成功后把结果扔掉」。
事故由两个工程隐患叠加而成:
「确定性失败 × 自动重试 × 非幂等任务」三者同时满足时,费用会在无人察觉的情况下持续燃烧。当工程师把链路画给那位非工程师负责人时,对方一脸困惑:「成功了还计费,然后又把成功丢掉?」这正是整个事件中最难消化的一点。
工程师把这次事故的复盘结论总结为几条对所有 LLM 应用开发者都有价值的实践准则: