凌晨两点我收到某云平台发来的费用告警邮件看了一眼数字差点从椅子上弹起来这个月的账单已经逼近一万美元而我的项目明明只跑了两周。更讽刺的是那段时间我一直觉得自己在用 VibeCoding 的方式“高效工作”——用自然语言描述需求让 AI 助手生成代码我只需要做做验收和部署自以为省下了几周的开发时间。结果这段“省下来的时间”变成了一张接近五位数的费用账单。事情是这样的我在做一个老照片批量修复服务需要把扫描出来的旧照片交给某个开源修复大模型做瑕疵修复和上色总共几万张图。为保证效率我让 AI 助手帮我写了一套异步批处理队列多消费者进程并发拉取任务、调用图像生成接口、保存结果。试点阶段跑了 100 张一切非常顺利于是直接切到全量生产队列。之后的每天晚上系统日志都在不断打印“处理中”监控面板上的任务量稳定上涨我以为系统在满负荷干活。直到费用失控的那一刻才发现系统确实在“满负荷”干活——只不过是在无限循环处理同一批坏样本每次循环都在真实地烧钱。这篇文章是完整的事故复盘。我不会只抛出结论而是把从账单异常到揪出代码根因的完整排查链路、VibeCoding 模式下这类 Bug 为什么如此容易产生、以及我后来重新建立的成本防护体系都写清楚。无论你本身用不用 AI 写代码只要你的业务跑在按量计费的云服务上这份复盘都能帮你避开同样的坑。1. 一万美金的账单是怎么来的一次云 GPU 事故的始末1.1 项目背景用 VibeCoding 赶工的一个图像修复服务我接到的需求本身不复杂把几千张扫描老照片做自动修复去掉划痕、噪点再统一补色。模型选型比较快开源社区里现成的图像修复方案已经够用真正花时间的反而是“怎么批量把这些图喂进去、结果怎么存下来、失败怎么处理”。按传统方式我可能要花两三天写调度代码、队列、日志和断点续跑逻辑。但当时我正赶进度索性全程用 AI 生成了整条批处理链路。那段时间我迷上了 VibeCoding 这种工作方式不给 AI 设太细的边界只告诉它“我要做一个批量处理队列从源目录读取图片调用修复接口把结果写入输出目录要有日志和失败重试”。AI 很快生成了一套看起来非常完整的 Python 批处理程序多消费者、任务去重、日志、断点续跑全都有。我做了简单的本地测试处理 100 张图全数成功没有报错。现在回头想问题恰恰出在“100 张全部成功”这个测试样本上。它让我对代码产生了过度信任。这批图都是正常的 JPEG 文件请求参数合法接口也稳定根本不可能触发异常路径。可生产环境里不是这样的——总有几张图片是损坏的、格式伪装的、色彩空间怪异的它们会被接口识别为非法输入并返回错误。而 AI 生成的代码里对这些错误路径的处理方式是“无限重试”。1.2 从“一切正常”到“账单爆炸”的七个晚上我用两周的经历做个时间线复盘你会发现这个事故不是某一瞬间炸开的而是一点点失控、却始终没人真正重视时间发生了什么当时我的判断第 1 天试点 100 张图全部成功速度符合预期方案可行可以上量第 3 天全量队列启动峰值并发拉到几十路看监控详情里的处理计数似乎一切正常第 5 天日志开始出现某张图片反复报错的重试记录以为是网络抖动让系统自己重试就好第 7 天同一张图片的重试记录一天超过几万次费用曲线明显抬头有点慌但没找到直接原因心存侥幸第 9 天云平台发送费用异常告警邮件以为只是预算预估不准关掉邮件继续睡第 12 天账单页面数字触目惊心逼近 10000 美元才开始真正审视那批日志这七晚里最值得反思的不是“为什么没看账单”而是“为什么监控显示一切正常”。因为我给监控面板设的核心指标是“处理中的任务数”和“已处理图片总数”。可讽刺的是失败任务在无限重试时每重试一次系统就会重新打印一条“processing”这会让“处理中的任务数”一直显示非零看起来就像系统在持续干活。真正能暴露问题的两个指标——接口调用量、单次请求成功率——我没有配置。后来我算了一笔账云 GPU 实例按小时计费这个属于固定成本冤枉钱有限真正的大头是那个图像修复接口每次调用按次计费而且我用的接口并发规格较高单次价格不低。坏样本的失败重试里没有任何退避机制失败后立刻重试我开了几十路并发光是循环调接口的钱就已经吓死人了更别提每次重试还同步产生存储写入请求。2. 拆开代码找真凶那个“很自然”补上的两行重试逻辑2.1 从账单反推向代码的排查链路当账单数字摆到面前时我做的第一件事不是看代码而是先弄清楚钱到底是从哪个环节流出去的。账单控制台按产品维度一拆答案立刻浮现出来图像生成 API 调用费用占了大头GPU 实例时长费用排第二存储费用排第三。API 调用量按天看下来正好在第 7 天前后开始异常飙升和日志里反复报错的时间段吻合。顺着这条线索我把出问题的那几个任务 ID 从队列里捞出来。它们都指向同一批输入文件。再打开日志发现同一段报错信息以秒级频率疯狂重复内容都指向同一个错误码。我随手通过日志反查代码看到了一段让我瞬间头皮发麻的循环逻辑。那段代码虽然不是我自己写的但一眼就能看出它的问题所在重试循环里既没有最大次数限制也没有任何退避等待而且每次重试都会重新调用一次商业化计费 API。2.2 根因还原三段式 Bug 的组合拳整段有问题的核心逻辑简化之后大致长这样def process_batch(item): processed False while not processed: try: resp fix_api.generate(item.input_url) upload(resp.file_url, item.output_bucket) processed True except Exception as exc: # AI 很自然地“贴心”补了一句失败了就继续 log.error(fgenerate failed: {exc}, retrying...)单独看每一行好像都没什么大毛病。但组合起来就是一个完美的烧钱机器第一while not processed这个循环没有重试次数上限。只要processed一直是 False循环就永远不会停。第二except Exception把所有异常都当成“可重试错误”。实际上接口返回的错误码里有一部分是永久性的——比如输入文件本身已损坏、参数非法、鉴权不通过。这些错误你重试一万次结果也一样只会白白调用接口烧钱。真正应该重试的是网络超时、服务端 5xx 这类的瞬时错误。第三坏样本被卡在队列头部。由于这个批处理程序读取输入的顺序是固定的同一张坏图每次被取出后处理失败然后重新入队或继续循环导致后续任务全部被它堵死。如果队列是“先入先出”的这张坏图挡在第一个整个消费进程就只会在它身上打转。三类问题单独出现时都不致命组合起来就成了一个没有任何终端、持续按次计费的空转循环。2.3 火烧得有多快成本放大效应的量化很多人好奇“一个循环而已怎么能烧到一万美金”。我们用具体数字推演一遍你就知道这类空烧有多恐怖。假设我的消费进程开了 20 路并发图像接口单价折算下来约每调用一次 0.3 美元每次失败重试间隔大约 0.5 秒。那么每个进程每小时能产生 7200 次调用20 路并发就是 14.4 万次调用单日光是接口费用就超过 4 万美元。当然实际调用会因为网络延迟等因素放慢但即便如此一周时间烧掉一台小汽车的首付也绰绰有余。我还犯了一个典型的经典错误监控只看“任务在跑”不看“任务在往哪个方向跑”。正确的做法是围绕着“成本”设置独立的预警体系。具体来说要将接口调用量、GPU 利用率、每张图的平均处理成本和单次请求失败率这四类指标做成一张基础看板。如果一个任务处理失败的同时接口调用量还在持续高位那你离及时止损就不远了。3. 为什么 VibeCoding 生成的代码特别容易埋这类 Bug3.1 从“写代码”到“描述代码”审查重点被转移了VibeCoding 的核心变化是人类从“代码的作者”变成了“需求的描述者和结果的验收者”。这本身没有错但你得清楚一件事AI 生成代码时最薄弱的恰恰是异常路径、失败恢复、资源耗尽这类分支。因为这些场景在模型的训练数据里往往不是示例代码展示的主流程模型的“默认习惯”是直接跳过它们或者用一眼看上去合理、实际却完全没有防御的写法补上去。我拿这次事故中的代码和一个真实工程师写的代码对比过。如果让我自己写我在写while not processed的瞬间就会本能地问自己什么时候退出这个循环失败多少次应该放弃放弃之后是不是应该把任务标记为失败而不是重新入队这些“本能问题”来自我被线上故障教育的惨痛经历。AI 没有这些经历它只会根据我们给的需求片段生成一个“看起来能完成主流程”的最优解。3.2 模型会“一本正经地补全”边界条件这是 VibeCoding 最容易迷惑人的地方。AI 补全逻辑的能力太强了它不会因为缺失边界条件就停下来问你而是会自行“补全”一个看似合理的边界。拿这次来说AI 看到“处理图片”这个需求就自己写出了一个while not processed的循环来保证任务最终完成——这种写法在顺序流水线里没问题在计费接口的重试里就是定时炸弹。可以这样类比你让实习生处理“用户登录失败时重新输入密码”他会马上写出一个while True的循环然后除非用户强制退出程序否则永远无法离开登录页。你不会信任这样的实习生直接上生产但当 AI 用同样逻辑写代码时因为代码本身看起来非常流利、变量命名也规范你会很容易放松警惕。这种“流利的表面”恰恰是最危险的。3.3 最容易出现“空烧类 Bug”的场景清单经历这次事故后我把平时容易踩到的“空烧类 Bug”场景整理了一份清单。只要你用 AI 生成代码并把代码部署到按量付费的云环境以下几个场景建议重点排查无限重试 / 死循环里包含按次计费的 API 调用或付费模型推理循环里没有 sleep、退避或最大重试次数失败后立即再试异常处理把所有错误一视同仁地当成“可重试错误”日志无限制打印短短几小时把云日志存储和流量费用烧高结果写入未加版本号或唯一 ID重复处理导致对象存储请求量暴增消费进程没有任务级超时单个坏任务把整个并发池占满这些场景的共同点是代码的主流程都没问题问题都藏在“异常发生后会发生什么”这个分支里。而 AI 生成代码时最敷衍的偏偏就是这些分支。4. 止损之后我把防护体系从零重新搭了一遍4.1 当场止损先掐掉流水再谈修 Bug当我确认问题就出在批处理队列的那一刻我的第一反应不是立刻去改代码而是先把所有消费进程全部停掉。这个操作五秒钟就完成了但效果立竿见影账单不再增长。很多人遇到故障时会急着“边跑边修”但在这种每秒钟都在烧钱的场景里先停掉生产链路永远是最优先的操作。停下来之后我做了三件事把日志完整归档把出问题的输入文件单独隔离再检查是否需要把已产生的费用报告提交给云平台申请减免。最后一项结果不一定理想但值得试一试。4.2 成本护栏让每一笔消耗都变成看得见的心跳修复代码前我先重新设计了监控体系。过去我只看业务成功率的指标这套监控在故障时完全是失明的。现在我强制要求所有按量计费的项目必须挂上四个基础指标单日接口调用量、按任务维度的平均处理成本、运行时实例数量、调用失败率。其中“失败率”和“调用量”组合起来能看到一个非常重要的现象失败率升高而调用量没有下降说明系统正在对坏数据反复重试这是空烧的典型前兆。我把这些指标接入了云平台的告警规则阈值设得比较保守单日费用超过预先估算上限、接口调用量在 10 分钟内翻倍、或者单个任务失败超过 5 次都会立刻通过多渠道通知到我。这套规则可能偶尔误报但我宁可被误报警告吵醒也绝不想再经历一次账单爆炸。4.3 代码层的防呆设计给 AI 生成的代码装上护栏修复核心逻辑时我把原来那段“无限重试”的循环换成了有明确退出条件、有退避策略、且能区分错误类型的版本MAX_RETRIES 3 BACKOFF_BASE 5 for attempt in range(MAX_RETRIES): try: resp fix_api.generate(item.input_url) upload(resp.file_url, item.output_bucket) mark_success(item.id) break except PermanentInputError: # 输入文件本身有问题重试多少次都没用 mark_failed(item.id, reasonpermanent_input_error) break except TransientApiError: if attempt MAX_RETRIES - 1: mark_failed(item.id, reasonmax_retries_exceeded) break time.sleep(BACKOFF_BASE * (2 ** attempt))这段逻辑的要点有三个失败次数有一个硬上限永久性错误被立即拦截并标记失败不会进入重试瞬时错误在重试前等待时间逐次翻倍。每次失败都必须有一条明确的“出路”——要么成功要么被标记为失败绝不允许无限循环。我还在任务入口加了一层前置校验读取图片后先做格式探测和完整性校验只有通过校验的图片才会被送入修复接口。这样一来坏图片根本走不到主流程也就不会卡住队列。经过这次事故我的个人准则是任何涉及计费调用的代码路径都必须有人工审查代码AI 生成的代码可以省去框架搭建的时间但计费点附近的防御逻辑一定要一个字一个字地人工抠清楚。5. 现在我是怎么用 VibeCoding 的一套更清醒的工作流5.1 给 AI 划定“可以放飞”的区域经历过这次事故后我不会再让 AI 完全自由地处理所有代码。现在我会把项目拆成两个区域可以“放飞”的区域和必须“人工审查”的区域。UI 组件、数据结构转换、正则表达式、文档注释、单元测试的生成这些部分即使出了问题后果也相对温和放心地交给 AI 没有太大问题。但计费 API 的调用、队列消费逻辑、存储写入、权限控制以及任何包含while、for循环批量处理的地方我会在需求描述里明确标注“必须经过逐行人工审查”。你完全可以把这个思路应用到自己的项目里在给 AI 下指令时明确告诉它“这段代码会调用按次计费接口请把所有可能的异常路径列出来并给每个异常路径定义明确的行为”。这类约束会让 AI 生成代码时主动避开最危险的写法比事后排查省力得多。5.2 每次合入生产前的五件事清单现在不论代码是不是 AI 写的我在把任何代码合入生产前都会强制过一遍五件检查所有循环是否都有明确的退出条件最大循环次数被设置成多少所有重试逻辑是否区分了永久错误与瞬时错误永久错误是否会被直接标记失败失败时代价高昂的计费调用是否会被跳过而不是继续叠加任务是否有超时控制异常持续一段时间后系统是否可以熔断监控面板是否覆盖了费用、调用量、失败率这些成本指标而不只是业务成功数这五件事每一条都有血的教训。上了这套检查清单之后我后续用 AI 生成代码再也没出过类似的烧钱事故。5.3 让 AI 先生成“失败剧本”再生成主流程最后分享一个我现在经常使用的 VibeCoding 小技巧先不让 AI 直接写主流程而是让它先写“失败剧本”。我会在提示词里这样描述请先不要写具体实现代码。假设这个模块会上线到生产环境请列出它可能遇到的所有异常场景比如上游接口连续失败、输入文件损坏、队列积压、存储写入超限等。对每一种异常场景给出你计划采取的应对策略。等你列完这份清单并确认覆盖完整再开始写主流程。你可能会惊讶这个简单的约束能让 AI 生成代码的健壮性提升一个档次。因为它先被要求思考边界条件之后生成主流程时会更倾向于为刚才列出的异常留出处理逻辑。实际操作中你也可以反过来让 AI 做“压力测试”写完代码后对它说“请假设现在 API 连续失败、队列被挤爆、文件存储写入超限指出这段代码哪一行会先出问题”。大多数情况下它都能指出几个值得警惕的位置。这段流程走完之后我再回头看看那个让我损失惨重的项目其实最贵的不是那一万美金而是让我重新建立起对 AI 生成代码的敬畏心。代码工具再强大最终承担责任的还是把它部署到生产环境里的人。至少涉及花钱的部分哪怕再省时间我也会自己多看一眼。
阅读完成 · 觉得有帮助?