Dayward AI
Week 2 · D12About 6 hours

Cost and Model Routing: Choosing a Model per Stage, Caching, Degradation, and a Budget Circuit Breaker

Get a clear tally of the whole line's costs, tier models per stage, bring cost down with caching and degradation, and fit every run with a budget circuit breaker that actually pulls the brake.

Today's goals 0/3

Sign in to tick these off and save your progress.

今日目标

  1. 能按环节把成本拆开,指出钱主要花在哪一步以及为什么
  2. 能给每个环节配一条模型路由策略,在质量与成本之间做出可解释的取舍
  3. 能实现预算熔断,让一次运行在超预算时安全停下而不是烧到底

前十一天你一直在让这条线跑得更稳:能重跑、能并发、能审核、能过质检。今天换一个维度——让它跑得起。读完回到页面顶部把上面三条勾掉。

小白版讲解

制片主任上任第一天,先把账摊在桌上

剧组里有个岗位叫制片主任。他只管一件事:钱花在哪儿了。新人最常犯的错是一上来就抠盒饭——盒饭一天几百块,那台摇臂一天几千块。抠错地方,累死也省不下钱。

你的生产线也一样。前十一天里 run.json 一直在默默记账(D7 打印过账单,D8 把台账条目落了盘),但没人认真看过那张表。今天先把它摊开,因为这条线上的四类调用,价格差了三个数量级

环节计价单位公开单价一集用量小计
镜头视频(768P)每秒¥0.503 镜共 18 秒¥9.00
角色定妆图与每镜首帧每张¥0.0255 张¥0.125
台词配音每计费字符约 ¥0.00035(折算)27 字¥0.0095
剧本与评审文本每 token以官方控制台为准不计价

三条注意事项必须先说清,不然后面全是错的:

第一,语音那一档不是官方标价。官方只公开资源包价格(HD 套餐一 ¥630 对应 200 万字符),按量单价是折算出来的,约每万字符三元多。所以本课涉及语音的金额都写「折算」两个字——把折算值当官方价报给老板,是要出事的。

第二,文本环节干脆不计价。这一档的按量单价以官方控制台为准,拿不到就不写数字,台账照样统计 token,只是金额那列空着。一个不确定的数字被写进表里,比空着危险得多——因为再没人会去核对它。

第三——这条最反直觉——视频那一行的单价不能挂到任何一个具体模型 id 上。官方按量付费页给出的是按秒档位(768P 每秒 ¥0.50、2K 每秒 ¥0.80、480P 每秒 ¥0.33),而本课代码用的 Hailuo 系列走的是套餐里的点数扣减,官方没有公开它的按秒单价;按秒计价的那几档属于另一套单独付费的模型。两套计价口径根本不通用。所以实验里那张表按「档位加规格」计价,视频那几行是台账演示用的折算常量,代码注释和面板上都标着。

这本身就是个好教学点:做成本台账的第一件事不是写表,是搞清楚每一项到底按什么计价。 同一家厂商的不同产品线、按量与包月、点数与现金,口径经常不统一;你要么核清楚,要么老实标成折算值。最糟的是把几种口径混进同一张表,再拿它去报预算。

把这四行加起来:一集 ¥9.13 出头,其中视频占了 98.5%。这就是本章、乃至整门课的那个判断的来源:

这条线上最贵、最慢、最容易失败的是视频生成,所以整条工程设计都是围着「怎么少调一次视频接口」转的。

回头看前面十一天会有新的感觉:幂等键(D8)是为了不重复调视频,参考图复用(D3)是为了少试几次视频,并发闸门(D9)是为了别把视频配额撞穿,人工审核卡在成片之前(D10)是为了不让一整集白拍。它们不是互不相干的最佳实践,是同一条判断的推论。

怎么把这张表变成代码?关键是台账要在调用发生时记,而不是事后猜。每条记录带上环节、类型、模型、用量、是否命中缓存、是否真的计费,最后按环节一聚合,钱花在哪一眼就看出来了。

pricing.js
// 单价表:只放公开单价,拿不到的宁可留空也不编。
const RATES = {
  'video-768p': { unit: 'second', cny: 0.5, approx: false },
  'video-2k': { unit: 'second', cny: 0.8, approx: false },
  'video-480p': { unit: 'second', cny: 0.33, approx: false },
  image: { unit: 'item', cny: 0.025, approx: false },
  // 官方只公开资源包价,这一档是折算值,面板上必须标出来
  tts: { unit: 'character', cny: 0.00035, approx: true },
}
 
const estimate = (rateId, units) =>
  rateId ? Number((RATES[rateId].cny * units).toFixed(4)) : 0
 
function summarize(rows) {
  const byStage = new Map()
  for (const r of rows) {
    // 命中缓存、以及失败未计费的调用,都记账但不计钱
    const cny = r.cached || !r.billed ? 0 : estimate(r.rateId, r.units)
    byStage.set(r.stage, (byStage.get(r.stage) ?? 0) + cny)
  }
  const total = [...byStage.values()].reduce((a, b) => a + b, 0)
  // 同额时按环节名定序,两次运行的报表才逐行一致
  return [...byStage].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
    .map(([stage, cny]) => ({ stage, cny, share: total ? cny / total : 0 }))
}

两个细节值得单独说。一个是 cachedbilled 两个布尔值:命中缓存的调用要记账、不计钱;生成失败或命中安全审核的视频不扣费,同样记账、不计钱。少了这两个标记,报表要么高估账单,要么看不出缓存省了多少。另一个是排序的决胜键——同额时按名字定序,否则两次运行的报表行序会飘,你会以为哪里出了 bug。

账摊开之后,问题就变得很具体:视频占九成以上,这九成能不能少花一点? 答案是能,四条互不重叠的路——分档、缓存、降级、熔断,后面挨个讲。

分档路由:草稿档和成片档不是同一件事

你写代码时会每改一行就发一次生产环境吗?当然不会——本地跑一遍、CI 跑一遍,最后才上线。生成内容也一样,但很多人偏偏每次试错都用最贵的档位跑。

分档路由(tiered routing)的意思是:同一份业务代码,按「这一次跑是为了什么」选择不同的模型与规格。这条线上至少有两个档——草稿档是你在改剧本、调分镜、试运镜,看的是叙事顺不顺,画质糊一点不影响判断;成片档是要发出去的那一版,画质、时长、定妆图一个都不能省。

关键是切换的判据要写在代码里,而不是留在人的脑子里:有没有通过剧本评审(D2)、有没有过人工审核(D10)、有没有指定成片输出,三条全满足才走成片档。

档位具体差在哪?实验里的两套配置是这样的:

维度草稿档成片档
视频清晰度512P768P
每镜时长上限3 秒6 秒
角色定妆图不生成,直接用文字描述生成
文本模型档位提示MiniMax-M2.7-highspeedMiniMax-M3
语音模型档位提示speech-2.8-turbospeech-2.8-hd

跑出来的对照是:草稿档 ¥4.58,成片档 ¥9.13,贵了 99%。也就是说草稿阶段每多试一版,省下的是一半的钱;一集试五版才定稿的话,光分档就省二十多块,一季五集就是一百多。

这里有个必须诚实交代的坑,而且它恰恰是最好的教学素材。官方按量付费页给出的档位是 768P 每秒 ¥0.50、2K 每秒 ¥0.80、480P 每秒 ¥0.33,但我们草稿档请求的是 512P——这一档没有对应的单价。怎么办?

往贵了估。 实验里 512P 按 768P 的单价计费,台账那一行打上 rateApprox 标记并在面板上原样打印。这条规矩听着保守,但估算偏高的最坏结果是你少跑了几版,估算偏低的最坏结果是月底账单翻倍而你不知道为什么。

「往贵了估」还带来一个后果:512P 和 768P 在账面上同价,于是降清晰度这个动作一分钱都省不出来。草稿档真正省钱的是另外两项——时长砍到 3 秒、定妆图不生成。这提醒你:降级要看账,不能凭感觉

缓存:这条线上最划算的一行代码

第二条路是缓存。改一个镜头的画面描述后重跑,账单从 ¥9.13 掉到 ¥3.03——省 67%,代价只是几十行代码。

为什么这条线特别适合缓存?因为产物贵,而且确定性强:同样的提示词、参考图、时长和清晰度,重新生成一次在观感上没有区别,却要再付一次全价。聊天机器人那边每次回答不同算特性,这边每次重生成不同基本是麻烦。

缓存键怎么设计是 D8 的题目(工作流引擎的幂等键,一套键同时管住「跳过已完成节点」与「命中缓存」)。今天只讲两件 D8 没讲的事。

第一件:缓存的省钱效果是分层的。 重跑同一集时,实验会打印一行「缓存命中 11 / 11 条,本次新花费 ¥0.0000」;只改一句台词或一个镜头的画面描述时,打印的是「缓存命中 9 / 11 条」——只有被改动的那一镜的首帧和视频需要重算,其余全部复用。这就是为什么缓存的粒度要落在单镜而不是整集:粒度粗一档,改一个字就要重跑一整集,缓存等于白做。

第二件:命中缓存不等于免费。 缓存文件占磁盘,视频尤其占;缓存被清空后第一次跑会一次性把钱全花出去。所以面板把命中的行也打印出来、金额记 0,另外单独给一行「缓存省下(估算)¥9.13」——要同时看到「这次花了多少」和「本来会花多少」

降级不是失败:三个可降级的维度

第三条路是降级。它和失败的差别很清楚:失败是拿不到东西,降级是拿到一个次一等但仍然可用的东西。 视频通话信号差时自动降清晰度而不是直接挂断,就是这个道理。

这条线上有三个可降级的维度,按「观众最不容易察觉」到「最容易察觉」排序:

  1. 清晰度:768P 降到 512P。观众在手机上刷竖屏短剧,多数情况下察觉不到。
  2. 时长:每镜 6 秒砍到 4 秒。节奏会变快,但故事还在。
  3. 镜头数:五镜砍到三镜。这一档已经在动叙事了,是最后手段。

降级的正确形态不是「跑到一半发现钱不够,砍掉后面几镜」,而是开跑之前先算一遍,算不过就先降,降完再跑:前者留下半集废片,后者产出一集完整的、只是次一等的成片。

degrade.js
// 三个可降级维度,按观众察觉难度从低到高排
const STEPS = [
  { label: '降清晰度 768P → 512P', apply: (p) => ({ ...p, resolution: '512P' }) },
  { label: '砍时长 每镜 6 → 4 秒', apply: (p) => ({ ...p, maxShotSeconds: 4 }) },
  { label: '砍镜头数 只留前 2 镜', apply: (p) => ({ ...p, maxShots: 2 }) },
]
 
// project 是纯函数:给一份计划,算出预估花费,一个请求都不发
function degradeToFit(plan, project, limitCny) {
  let cur = plan
  const applied = []
  const skipped = []
  let projected = project(cur)
  for (const step of STEPS) {
    if (projected <= limitCny) break
    const next = step.apply(cur)
    if (project(next) >= projected) {
      // 这一步在账面上省不出钱,降了只有损失
      skipped.push(step.label)
      continue
    }
    cur = next
    applied.push(step.label)
    projected = project(cur)
  }
  return { plan: cur, applied, skipped, projected, fits: projected <= limitCny }
}

注意中间那段「省不出钱就跳过」的逻辑。它不是防御性代码,是正经判据:512P 在我们的单价表上和 768P 同价,于是降清晰度这一步会被自动跳过——降级的每一步都要拿账本验证,验证不过就别降。日志里会打印「采纳」和「跳过」两类步骤,理由写在旁边。

预算熔断:软上限提醒,硬上限停机

前三条路都是「省」,最后这条是「兜底」——省得再好,也挡不住一次失控的重试风暴把账单打上天。

熔断(circuit breaker)的核心语义只有一句话:在花钱之前判断,不是花完之后统计。 写成事后统计,你能得到的最好结果是「这次超支了 40%」——钱已经花出去了,这行日志除了让你难受没有任何作用。正确的形态是「预留」:调用付费接口之前先把这笔预估的钱从预算里扣掉,扣得动才发请求,扣不动就抛错。

budget.js
class BudgetExceededError extends Error {}
 
class Budget {
  #spent = 0
  #softFired = false
  constructor({ softCny, hardCny, onSoft }) {
    Object.assign(this, { softCny, hardCny, onSoft })
  }
 
  // 花钱之前调用。扣不动就抛错,调用方负责安全停机
  reserve(stage, amountCny) {
    if (this.#spent + amountCny > this.hardCny) {
      throw new BudgetExceededError(
        `硬上限熔断:已花 ${this.#spent.toFixed(4)},${stage} 还要 ${amountCny.toFixed(4)}`
      )
    }
    this.#spent += amountCny
    if (!this.#softFired && this.#spent > this.softCny) {
      this.#softFired = true
      this.onSoft?.(this.#spent) // 只提醒一次,别刷屏
    }
  }
 
  // 失败或命中安全审核的视频不扣费,预扣的钱要退回来
  refund(amountCny) {
    this.#spent = Math.max(0, this.#spent - amountCny)
  }
}

两级上限分工不同。软上限是提醒,不改变行为:越过一次打一条告警就够,作用是让你在还有余地时做决定——继续跑,还是手动降档。硬上限是停机,必须真的停

而「停机」里藏着本章最重要的判断:什么叫安全停机?

不是 process.exit(1)。那样会丢掉账本和进度文件,下次只能从头再来——把已经花掉的钱再花一遍,熔断反而成了浪费的放大器。安全停机的定义是三条:

  1. 已完成的产物留在磁盘上,一个都不删;
  2. 账本写完,本次花了多少、停在哪一步、还差什么,全部落盘;
  3. 缓存也写进去,下次带更高的预算跑,已经做完的部分不用重做。

实验里跑到这一步时,日志是这样的:

TextText
⑤ 把硬上限压到 ¥3,清空缓存后再跑成片档(INJECT=overbudget)
  ✋ 硬上限熔断:已花 ¥0.1250,clips 这一步还要 ¥3.0000,合计超过硬上限 ¥3.0000,本次运行到此为止
  已完成产物 5 个仍在磁盘上,缓存也已写入:下次带更高预算跑,这些都不用重做。

熔断和安全审核拦截这两个场景都是故障注入,默认不跑,靠 INJECT 环境变量打开——本课所有需要人为制造失败的地方都用这一个开关,值是逗号分隔的场景名。D12 支持两个:overbudget 打开硬上限熔断,blocked 让某一镜的第一次视频生成命中安全审核。

还有一个容易漏的口径:生成失败或命中安全审核的视频不扣费。所以熔断器预扣的那笔钱必须退回来,否则一次内容安全拦截会白占掉三块钱额度,而账单上根本没有这笔支出。实验里对应 refund 方法与台账里那条 billed: false 的记录,面板上单独打一行「失败/被拦截未计费」。

成本面板:让人一眼看出这次跑贵在哪

面板不是报表。报表给月底看,面板给这一次运行看,只回答一个问题:这次跑,钱花在哪儿了,跟上次比多了还是少了。

面板出两个维度:按环节(assets / frames / clips / voice)和按 provider 与类型。前者告诉你该优化流程的哪一段,后者告诉你该跟哪一家谈价、或者把哪一类调用换一家。

一张跑完的面板长这样:

TextText
── 成本面板:成片档(首次) ──
  按环节
    clips      ¥  9.0000   98.5%  ████████████████████████  调用 4 次(缓存命中 0)
    frames     ¥  0.0750    0.8%  ························  调用 3 次(缓存命中 0)
    assets     ¥  0.0500    0.5%  ························  调用 2 次(缓存命中 0)
    voice      ¥  0.0096    0.1%  ························  调用 3 次(缓存命中 0)
  合计(估算)¥9.1346
  失败/被拦截未计费(估算)¥3.0000:失败或命中安全审核的视频不扣费

三个设计要点:每行都带调用次数与缓存命中数,因为「花了多少」和「调了几次」是两件事,一次失败重试会让次数涨而金额不涨;百分比和条形图一起给,人对条形图的敏感度远高于数字;底部固定打两行免责说明,讲清金额是估算、不能对账,并点名哪几档单价是折算值、哪一档是官方直接标价。

最后一句:面板是拿来做决定的,不是拿来存档的。 看完一张面板不知道下一步该改什么,这张面板就白做了。这条线上的面板永远指向同一个结论——九成的钱在视频,下一步永远是「怎么少调一次视频接口」。

源码导读

动手实验

🧪 D12 实验:按环节分档的模型路由与带预算熔断的成本面板

Code location: labs/ai-drama-pipeline/day-12-cost-router

验收标准:

  1. 跑完六个阶段,草稿档与成片档各打印一张成本面板,两张面板里 clips 那一行都超过总额的 95%
  2. 原样重跑时打印「缓存命中 11 / 11 条」,本次新花费为 ¥0.0000
  3. 只改一个镜头的画面描述后重跑,打印「缓存命中 9 / 11 条」,新花费只有被改动那一镜的首帧与视频
  4. INJECT=overbudget 再跑一次,日志里出现「✋ 硬上限熔断」,随后打印「已完成产物 5 个仍在磁盘上」
  5. 预算 ¥5 时先打印采纳与跳过的降级步骤,再按降级后的计划跑完,总额落在 ¥5 以内

验收要跑两条命令:MOCK=1 pnpm start 走正常路径,INJECT=overbudget,blocked MOCK=1 pnpm start 把熔断与安全审核拦截两个故障注入进去。动手之前先确认 ffmpeg 装好了(缺了会在启动时报可读错误),并且记住面板上的金额全部是估算值——离线模式下 provider 返回的花费恒为 0。卡住的话先看日志里「缓存命中 x / y」那一行,它几乎能定位本实验九成的问题。

  1. 先原样跑一遍 starter,把六个阶段的输出从头看到尾,记下哪几处现象明显不对(面板全 0、缓存永不命中、熔断没触发、降级一步没降)。
  2. 补上预估函数与打分口径:让 project 能在不发任何请求的前提下算出一集要花多少,跑完看阶段 ⑥ 是否开始打印降级步骤。
  3. 实现缓存命中,重跑阶段 ③ ④,确认命中数从 0 / 11 变成 11 / 11 和 9 / 11,新花费跟着掉下来。
  4. 把预算改成事前预留,用 INJECT=overbudget 跑阶段 ⑤,看到「✋ 硬上限熔断」以及「已完成产物 5 个仍在磁盘上」两行同时出现。
  5. 加上 INJECT=blocked,补上被安全审核拦下的那次调用的退款与不计费口径,确认面板底部出现「失败/被拦截未计费」一行。

面试题

今天 3 道题在下方题库区,侧重成本结构分析、分档路由的判据、以及熔断与降级的安全停机语义。展开后先看「分析过程」再看要点——照着推导练,比背要点管用。标注「国内高频 / 海外高频」方便按目标市场取舍。

检查清单与明日预告

  • 能按环节把成本拆开,指出钱主要花在哪一步以及为什么
  • 能给每个环节配一条模型路由策略,在质量与成本之间做出可解释的取舍
  • 能实现预算熔断,让一次运行在超预算时安全停下而不是烧到底
  • 能说清「安全停机」的三条判据,并解释为什么直接退出进程是错的
  • 能说出降级的三个维度,以及为什么每一步降级都要先拿账本验证
  • 实验的 5 条验收标准全部通过
  • 3 道面试题不看要点也能答出至少 2 道

明天(D13)我们把成片发出去:同一集按三个平台的规格各导出一版,让模型批量出标题与封面文案再打分挑前三,最后把播放数据接回来指导下一集怎么拍。为什么先算账再发行?因为发行会让你有动力多做几个版本,而多做版本正是最容易失控的地方——先把刹车装好,再去踩油门。

Interview questions

  • How do you break down the cost of a content-generation pipeline, and which stage would you optimize first?一条内容生成流水线的成本要怎么拆?拆完你会先优化哪一环,为什么?
    Common in ChinaCommon overseasBasic#cost-analysis#observability

    How to reason about it · think before answering

    1. This question checks whether you have actually read a bill. Answering with generic advice like use more caching signals you never ran this in production; naming the breakdown dimensions and rough ratios signals you did.
    2. Establish the dimensions first: by stage (script, image, video, speech), by billing unit (per second, per item, per character, per token), and by billable status (succeeded, cache hit, failed and not charged). Drop any one of them and a whole class of spend becomes invisible.
    3. Then give orders of magnitude. Video is billed per second, so a dozen seconds already costs a few yuan, while images are cents per item, speech is fractions of a cent per character, and text is lower still. Video typically dominates at over ninety percent.
    4. So the priority is driven by what is expensive, not by what is easy to change. Attack video first, cheapest lever to most expensive: caching and idempotency, tiered routing with a cheap draft tier, degradation across resolution, duration and shot count, and only then vendor negotiation.
    5. Add a credibility note: never put an unverified unit price in the table. Mark derived prices as estimates and leave unpublished ones blank while still counting usage. Reporting an estimate as an official price is how these projects lose trust.
    6. Expect the follow-up: how do you prove the optimization worked? Run the same input twice and compare the per-stage panel, not the monthly invoice, which mixes in traffic you did not cause.

    分析过程 · 先想清楚再作答

    1. 这题在考你有没有真的看过账单。凭感觉答「多用缓存、少调模型」的人一听就没做过;能说出「按什么维度拆、拆出来大概什么比例」的才是。
    2. 拆的维度要先立住:按环节(脚本、图像、视频、语音)、按计价单位(按秒、按张、按字符、按 token)、按是否计费(成功、命中缓存、失败未扣费)。三个维度缺一个,报表就会有一类花费永远看不见。
    3. 然后给数量级。多媒体生成这类流水线里视频按秒计价,一集十几秒就是几块钱;图像按张几分钱、语音按字符几厘钱、文本更低。结论是视频通常占九成以上,其余全是零头。
    4. 所以优化顺序不是「哪一环最容易优化」,而是「哪一环最贵」。先优化视频,手段按代价从低到高排:缓存与幂等(不重复调)、分档路由(草稿档用便宜规格)、降级(清晰度、时长、镜头数)、最后才是换厂商谈价。
    5. 补一句可信度:不确定的单价不要写进表。官方只给资源包价的档位要标明是折算值,官方没公开的档位就留空只统计用量——把估算值当官方价报上去,是这类项目最常见的翻车点。
    6. 可预期的追问是「那怎么证明优化生效了」。答案是同一份输入跑两遍,对照面板上按环节的金额与调用次数,而不是看月账单——月账单里混着别人的流量,归因不到你这次改动。

    Key points

    • Break it down three ways: by stage, by billing unit, and by whether the call was actually charged
    • Lead with the ratio: video is billed per second and usually exceeds ninety percent of per-episode cost
    • Optimize expensive first: caching and idempotency, tiered routing, degradation, vendor negotiation last
    • Leave unknown unit prices blank while still counting usage, and label derived prices as estimates
    • Validate by running the same input twice and diffing the per-stage panel, not the monthly invoice

    答题要点

    • 按三个维度拆:环节、计价单位、是否真的计费(成功 / 缓存命中 / 失败未扣费)
    • 先给比例再给结论:视频按秒计价,通常占单集成本九成以上,其余是零头
    • 优化顺序由贵到便宜:缓存与幂等、分档路由、降级、最后才谈价换厂商
    • 拿不到的单价宁可留空只统计用量,折算出来的要标明是折算值
    • 验证靠同一份输入跑两遍对照面板,不看混杂的月账单
  • When should you degrade instead of retry, and which dimensions can you degrade first?什么情况下该降级而不是重试?如果决定降级,你有哪些维度可以降,怎么排先后?
    Common in ChinaCommon overseasIntermediate#degradation#retry-strategy

    How to reason about it · think before answering

    1. The pivot is the word instead. This tests whether you separate two failure classes: retry addresses bad luck this time, degradation addresses cannot finish under this configuration. Answering retry three times then degrade misses the point.
    2. Give a reusable rule: retry fixes transient, configuration-independent problems such as rate limits, timeouts and server errors. Degradation fixes persistent, constraint-driven ones such as running out of budget, quota or time. Retrying the second class just burns resources faster.
    3. Name the class most people get wrong: a content-safety block should be neither retried nor degraded, it needs a changed input. Conflating the three is the biggest scoring mistake here.
    4. Order degradation dimensions by how noticeable they are, least to most: resolution, duration, then count of items. Touch the one that changes the content itself only as a last resort.
    5. Add an engineering rule: validate every degradation step against the cost model. If a step saves nothing on your rate card, degrading quality buys you nothing and should be skipped.
    6. Expect the follow-up: when do you decide? Project the cost with a pure function before the run starts and degrade up front. Cutting mid-run leaves a half-finished artifact and wastes everything already spent.

    分析过程 · 先想清楚再作答

    1. 题眼在「而不是」三个字。它考的是你能不能区分两类失败:重试针对的是「这次不巧」,降级针对的是「按当前配置根本跑不完」。答成「先重试三次再降级」就落进了套路。
    2. 给一条可复用的判据:重试解决的是**瞬时**且**与配置无关**的问题(限流、超时、服务端 5xx),降级解决的是**持续**且**由约束导致**的问题(预算不够、配额见底、截止时间快到了)。前者重试有效,后者重试只会把资源烧得更快。
    3. 顺带点出最容易被答错的一类:内容安全拦截既不该重试也不该降级,它要改输入。把三类混在一起是这题最大的失分点。
    4. 降级的维度要按「用户察觉难度」排,从低到高:清晰度、时长、数量(镜头数 / 条数)。先降察觉不到的,最后才动会影响内容本身的那一档。
    5. 还有一条工程判据:每一步降级都要拿成本模型验证一遍。如果某一档在你的单价表上省不出钱(比如更低的清晰度和当前档同价),那这一步降了只有损失,应该直接跳过。
    6. 可预期的追问是「降级要在什么时候决定」。答案是开跑之前先用纯函数预估一遍,算不过就降完再跑——跑到一半再砍,会留下半成品,前面花的钱全打水漂。

    Key points

    • Retry transient configuration-independent failures; degrade when the constraint makes completion impossible
    • Content-safety blocks are a third class: change the input rather than retrying or degrading
    • Order degradation by noticeability: resolution, duration, item count, content last
    • Validate each degradation step against the rate card and skip steps that save nothing
    • Decide before the run starts; cutting mid-run leaves a half-finished artifact and wastes prior spend

    答题要点

    • 重试针对瞬时且与配置无关的失败,降级针对持续且由约束导致的不可完成
    • 内容安全拦截是第三类:既不重试也不降级,要改输入
    • 降级维度按察觉难度排:清晰度、时长、数量,最后才动内容本身
    • 每一步降级都要拿成本模型验证,省不出钱的那一步直接跳过
    • 降级要在开跑前决定,跑到一半再砍会留下半成品且前面的钱白花
  • How would you design a budget circuit breaker for a pipeline that calls paid APIs, and what makes the stop safe?给一条会调用付费接口的流水线加预算熔断,你会怎么设计?做到什么程度才算安全停机?
    Common in ChinaCommon overseasDeep dive#budget-control#circuit-breaker

    How to reason about it · think before answering

    1. The discriminator is the word safe. Most candidates can say stop when over budget; what the interviewer wants is the state the system is left in afterwards.
    2. Rule one: the check happens before you spend. Use reservation-style accounting, projecting each paid call with a pure function and deducting it from the budget before issuing the request. After-the-fact accounting only tells you that the money is already gone.
    3. Rule two: the two thresholds do different jobs. A soft limit warns once so a human can decide whether to continue or downgrade; a hard limit must actually stop. Setting both to the same value means you have no soft limit.
    4. Rule three defines a safe stop, and all three parts are required: keep every finished artifact, persist the ledger and the point of interruption, and write the cache. Miss any one and the next run with a higher budget pays again for work already paid for, turning the breaker into a waste amplifier. Calling exit is therefore wrong.
    5. Rule four is refunds: if the vendor does not charge for failed or safety-blocked calls, the reserved amount must be released, otherwise you overstate the bill and silently consume headroom. Mark those ledger rows separately and show them as their own line on the panel.
    6. Two follow-ups to expect. Under concurrency the reservation must be atomic, so a shared counter needs a single owner or an atomic operation or you will oversell. And the limits themselves should be derived from historical usage through the same projection function, not guessed.

    分析过程 · 先想清楚再作答

    1. 这题的区分度全在「安全」两个字。多数人能答出「超预算就停」,但停完之后系统处在什么状态,才是面试官真正想听的。
    2. 先立第一条:判断必须发生在花钱之前。做法是预留式记账——每次调用付费接口前用一个纯函数预估这笔花费,从预算里扣,扣得动才发请求。事后统计只能告诉你已经超了,那时钱已经出去了。
    3. 第二条是两级上限的分工:软上限只提醒且只提醒一次,作用是让人在还有余地时决定继续还是降档;硬上限必须真的停。把两者做成同一个阈值,等于没有软上限。
    4. 第三条才是「安全停机」的定义,三个都要满足:已完成的产物一个不删、账本与停在哪一步落盘、缓存写入。少了任何一条,下一次带更高预算重跑就要把已经花掉的钱再花一遍——熔断反而成了浪费的放大器。所以直接退出进程是错的。
    5. 第四条是退款口径:失败或被内容安全拦下的调用如果厂商不计费,预扣的额度必须退回来,否则你会一边高估账单一边白占预算。台账上这类记录要单独标出来,面板上单独一行。
    6. 可预期的追问有两个。一是「并发下怎么保证不超」——预留必须是原子的,多个 worker 共享一个计数器时要走单点或原子操作,否则会超卖。二是「上限设多少」——用同一个预估函数按历史用量反推,而不是拍脑袋。

    Key points

    • Reserve before you spend: project the cost, deduct it, and skip the call if it does not fit
    • The soft limit warns once for a human decision; the hard limit must actually stop, with different thresholds
    • A safe stop keeps artifacts, persists the ledger and resume point, and writes the cache; never just exit
    • Release reservations for calls the vendor does not charge for, and show them as a separate ledger line
    • Make reservations atomic under concurrency and derive limits from historical usage via the same projector

    答题要点

    • 预留式记账:调用付费接口前先预估并扣减,扣不动就不发请求
    • 软上限只提醒一次供人决策,硬上限必须真的停,两者阈值必须不同
    • 安全停机三条:产物保留、账本与断点落盘、缓存写入,绝不直接退出进程
    • 厂商不计费的失败调用要退回预扣额度,并在台账与面板上单独标出
    • 并发下预留必须原子;上限用同一个预估函数按历史用量反推

Comments

Sign in to join the discussion

No comments yet — be the first.