面试知识库

综合模拟面试 7 · 延迟治理与性能优化#

面试官画像: 搜索/推荐团队 Staff Engineer,关注端到端性能与用户体验 时长: 40-45 分钟 · 难度: ★★★★★ 覆盖方向: 延迟定位方法论 → LLM 解码瓶颈 → 四刀减法 → prompt cache → 追问轮优化


第一阶段 · 定位(10 分钟)#

面试官: 你们系统端到端延迟多少?怎么定位的瓶颈?

候选人: 病态场景 133.9 秒,治理后 39.7 秒。定位靠 Langfuse trace——每个 LLM 调用、工具调用都有独立 span。拆出来发现 ~96% 是 LLM 解码时间,后端代码(Qdrant 召回、reranker、运费计算)加起来不到 2 秒。

面试官: 96% 这个数字怎么得出来的?

候选人: trace 里每个 span 有 start/end 时间戳。把所有 LLM span 的总耗时除以端到端,就是 96%。搜索工具 item_search 单次才 0.3 秒,reranker 30 条约 335 毫秒。纯后端优化天花板极低。

面试官: 那你们怎么省 LLM 时间?

候选人: 核心洞察:省 token = 省延迟。LLM 解码是逐 token 的,输出 token 越多越慢。四刀减法:第一,子 Agent 关 reasoning——fork 出去的子 Agent 只做执行不做推理,省 reasoning token。第二,planner 确定性预置——第一轮必经 planner,把它挪到 harness 层确定性注入,省一轮 LLM 往返。第三,收尾不逐件重写——shopping_summary 原来把每件商品的推荐理由让 LLM 重写一遍,改成内部 LLM 只产 reason 字段,前端拼渲染。第四,追问轮不开 reasoning。


第二阶段 · Prompt Cache 保全(12 分钟)#

面试官: 你提到 prompt cache,具体怎么保证命中?

候选人: 关键约束——system prompt + 工具表构成前缀,Anthropic cache 要求前缀逐 byte 精确匹配。任何改动(摘工具、改 system prompt)= cache miss,多付 ~11000 token 输入费。所以我们做了一个反直觉的选择:工具权限管控不摘工具,而是用执行层哨兵拦截。工具表恒定不变,前缀稳定。

面试官: 哨兵拦截不是浪费了一次 tool_call 的 token 吗?

候选人: 是的,一次无效 tool_call 约 50-100 token。但 cache miss 是 ~11000 token。两个数量级的差距,选择很明确。而且被拦截后模型收到错误信息,下一轮就知道不该调了,几乎不会重复犯。

面试官: 上下文压缩不会破坏 cache 吗?

候选人: 这是 Cache Breakpoint 的核心设计。不是盲目压缩——先算缓存断点,断点前是已被缓存的前缀区(打 cache_control),断点后才是可压缩区。压缩只动后半段,前缀不碰。盲目压缩命中率从 85% 跌到 15%,综合成本反涨 297%。

面试官: 断点怎么算?

候选人: 遍历 messages,从尾部留 keep_recent 轮不动(保留最近上下文),再往前找最近一个已被 provider 确认 cached 的边界。这个边界之前的内容已经在 provider 侧缓存了,不能改动;边界之后到 keep_recent 之间的才做摘要压缩。


第三阶段 · 追问轮优化(10 分钟)#

面试官: 追问轮延迟怎么处理?

候选人: 追问轮的关键是——有候选就不重搜。planner 输出 retrieval 字段,判断当前候选池能否满足追问需求。能满足 → 直接拿候选做 picker,省掉整个 item_search + rerank。不能满足才补搜。

面试官: 怎么判断”能满足”?

候选人: planner 是 LLM,看追问内容与当前候选的关系。比如用户说”便宜点的有没有”,候选池里按价格排就行,不需要重搜。但如果说”换个品牌看看”,那就得补搜。

面试官: 走过弯路没有?

候选人: 走过。最初想用”有候选 = 是追问”做判断——直接看候选池非空就跳过搜索。但”有候选”不等于”是追问”——用户可能完全换了个需求,候选池是上一轮的残留。正确做法是让 planner 判意图,而不是用机制替代语义理解。


第四阶段 · 前缀缓存塌方实战(8 分钟)#

面试官: 上线后遇到过 cache 相关的 bug 吗?

候选人: 遇到过前缀缓存塌方。一次性注入(oneshot injection)把整条 ModelResponse 的 result 塞进 state,但 result 里的前缀和 schema 不是随 state 更新的。下一轮 agent 看到的 tool 输出和实际缓存的前缀不一致 → cache miss → 每轮多付 11000 token。

修法两刀:第一,注入随 ModelResponse 的 result 落 state,保住前缀连续性。第二,schema 断言改成按渲染契约验证——不再断言 raw 字段,改断言前端真正消费的 platform、raw_decode 等字段。

修后实测:注入成功率从 31.6% 涨到 67.5%,只断一轮后就恢复。