S3-架构方法论篇-05-性能与上下文效率
⚡ 性能与上下文效率:从 P99 到 P999,从尾延迟到 token 成本
本文是《从零吃透企业级 AI 平台》第三季·架构方法论的第 5 篇
⚠️ 事实锚点速记:本篇沿用第三季事实锚点——以「万悟(Apache-2.0 开源,源码可查)↔ WorkBuddy(闭源产品,仅公开方法论)」双平台作方法论对照;完整声明、信源与"为何不把两者都当开源"见 《00 · 第三季导读》。
🎯 本文目标
读完本文,你将:
- 理解"平均延迟 2s"为什么是一个危险的数字,以及 P99 / P999 背后的资源竞争逻辑
- 看清万悟如何用模型路由 / 语义缓存 / GPU 调度 / 熔断做"传统后端性能"治理
- 看清 WorkBuddy 如何用 Prompt Cache / 上下文压缩 / 模型路由做"LLM 上下文效率"治理
- 理解 AI 平台里"性能"其实有两套含义:吞吐 / 尾延迟 vs token 成本 / 上下文窗口
前置知识:了解基本的并发 / 协程概念;了解尾延迟(tail latency)基本概念更佳,但非必须
1. "平均 2 秒"是一个谎言
某 AI 文档分析平台的运营周报上曾经长期印着这样一行:
文档分析平均延迟:2.1s ✅
直到有一天,一位企业客户投诉:"我上传了一份 47 页的并购文档,等了 43 秒才出结果。你们的 SLA 写的是 10 秒以内。"
工程师查了监控。平均延迟确实是 2.1s。P50 是 1.8s。P95 是 4.2s。
P99 是 11.3s。
P999 是 38.7s。
那位客户,恰好落在了 P999 里。
这不是偶发。在日均 5000 次分析中,P999 意味着每天大约有 5 次请求超过 38 秒。这 5 个用户不会看你的运营周报,他们只会打电话给客户经理说"你们系统太慢了"。
💡 关键洞察:平均延迟是一个统计学幻觉。它把 4999 次 1.8s 和 1 次 43s 搅拌在一起,告诉你"一切正常"。但对于那 1 个等了 43 秒的用户来说,你的系统就是慢。在 B2B 场景中,一个企业决策者的耐心阈值大约是 15 秒——超过这个时间,他不会刷新页面,他会换一家供应商。
拿出菜打比方:餐厅说"平均出菜时间 8 分钟"。但如果你点的那道菜恰好赶上厨师换班、灶台被占、食材临时解冻,你等了 35 分钟。你不会想"嗯,平均 8 分钟,我应该理解"。你只会想"再也不来了"。
更糟糕的是,长尾延迟不是随机噪声。它往往有结构性原因,意味着它会反复发生,且随着负载增长而恶化。
2. 四种性能优化思路横向对比

2.1 加机器(垂直 / 水平扩容)
# 伪代码:最直觉的"优化"
# 慢?加 CPU。还慢?加实例。
def handle_analysis(doc):
# 单线程处理,CPU 打满
result = heavy_compute(doc) # 4s
return result
# "优化"后:
# kubectl scale deployment doc-parser --replicas=8
# 问题:如果瓶颈是 LLM API 的 rate limit(60 RPM),
# 加 8 个实例只是让 8 个实例同时被 429 拒绝
2.2 缓存(Cache Everything)
# 伪代码:把能缓存的都缓存
@cache(ttl=3600)
def match_rules(clause_text: str) -> list[RuleMatch]:
return rule_engine.match(clause_text)
# 问题:文档条款几乎不重复(每份文档都是独特的)
# 缓存命中率 < 3%,反而增加了内存压力和 GC 负担
# 缓存适合"读多写少、重复率高"的场景,不适合"每次输入都不同"的场景
2.3 异步化 + 背压(Backpressure)
# 伪代码:不追求"每个请求都快",追求"系统不崩溃"
class BackpressurePipeline:
def __init__(self, capacity=50):
self.queue = asyncio.Queue(maxsize=capacity)
self.semaphore = asyncio.Semaphore(10) # 并发上限
async def submit(self, task):
if self.queue.full():
# 不拒绝,而是告诉调用方"慢一点"
raise BackpressureSignal(retry_after=2.0)
await self.queue.put(task)
async def worker(self):
while True:
task = await self.queue.get()
async with self.semaphore:
await self.execute(task)
self.queue.task_done()
2.4 尾延迟对冲(Tail Latency Hedging)
# 伪代码:同一个请求发两份,取先返回的
async def hedged_llm_call(prompt: str) -> str:
primary = asyncio.create_task(llm_call(prompt, instance="A"))
await asyncio.sleep(2.0)
if not primary.done():
hedge = asyncio.create_task(llm_call(prompt, instance="B"))
done, pending = await asyncio.wait(
[primary, hedge], return_when=asyncio.FIRST_COMPLETED
)
for p in pending:
p.cancel()
return done.pop().result()
return primary.result()
# 代价:资源消耗翻倍(但只在慢请求时触发)
# 收益:P99 从 11s 降到 4s
横向对比
| 加机器 | 缓存 | 背压 | 尾延迟对冲 | |
|---|---|---|---|---|
| 解决平均延迟 | ✅ | ✅(高命中时) | ❌(不加速) | ❌(不加速) |
| 解决长尾延迟 | ⚠️(如果瓶颈是 CPU) | ❌ | ✅(防雪崩) | ✅(直接砍尾) |
| 防系统崩溃 | ❌(可能加速崩溃) | ❌ | ✅ | ❌ |
| 资源效率 | 低(常态浪费) | 高(命中时) | 高 | 中(仅慢时翻倍) |
| 实现复杂度 | 低 | 中 | 中 | 高 |
| 适合文档分析场景 | ⚠️(LLM 是外部瓶颈) | ❌(命中率极低) | ✅ | ✅(LLM 调用) |
📌 小结:AI 文档分析的性能瓶颈往往不在 CPU(解析和规则匹配很快),而在外部 IO(LLM API 调用)和资源竞争(批量任务抢同一个 LLM rate limit)。因此,"加机器"和"缓存"都不是主要手段,重点是"背压 + 尾延迟对冲"。再往深一层,AI 平台的"性能"到底指什么? 万悟与 WorkBuddy 恰恰在这里给出不同的答案。
3. 两个真实平台怎么选
"性能"在 AI 平台里其实有两套含义:万悟关心的是传统后端性能(吞吐 / 延迟 / 尾延迟),WorkBuddy 关心的是 LLM 上下文效率(token 成本 / 上下文窗口 / 缓存命中)。两套性能观并存,因为两者的瓶颈不同。

3.1 万悟:传统后端性能工程(吞吐 / 尾延迟 / 熔断)
万悟是开源企业级平台,性能治理建立在已核实的能力上:
- 模型路由:按任务类型把请求路由到合适的模型,避免"杀鸡用牛刀"。
- 语义缓存:相似 query 命中缓存,减少重复推理。
- GPU 调度 + circuit breaker:推理资源统一调度;微服务基础设施层的熔断(circuit breaker)防止单点慢依赖拖垮整体(即第三季第 3 篇提到的"断路解决可用性")。
- 跨 11 微服务链路优化:OTel 链路定位瓶颈(呼应第 3 篇),对象池化(如 Go 的
sync.Pool)减少 GC 压力。
万悟的路径是"工程化性能"——把性能当成一个需要测量、定位、优化的后端工程问题。对应汪晟杰 Agent OS 基础设施层"监控运维"与智能服务层 MaaS(模型服务)。
3.2 WorkBuddy:LLM 上下文效率工程(token 成本 / 窗口)
WorkBuddy(闭源)的性能观更偏"上下文效率"(Anne M-C-H-L + 汪晟杰 Agent OS):
- Prompt Cache 命中率:把稳定前缀(系统提示、复用上下文)缓存,降低重复 token 成本。
- 上下文压缩:对超长上下文做压缩 / 摘要,控制窗口占用。
- 渐进加载:按需加载上下文,而非一次塞满窗口。
- Billing / Credits(智能服务层):把"成本"变成可度量、可治理的一等公民——每次调用计费、配额可控,性能直接挂钩"花了多少"。
- 模型无关路由(Auto 模式):在混元 / DeepSeek / GLM / Kimi / MiniMax 间按任务自动选模型,把"性能"表达为"选对模型"。
WorkBuddy 的路径是"上下文效率"——它不直接优化"单次调用的延迟"(那取决于模型服务商),而是优化"花了多少 token / 占了多少窗口 / 选了哪个模型"。对应汪晟杰 Agent OS 智能服务层 Billing/Credits + MaaS 模型无关路由。
3.3 对照表:吞吐延迟 vs 上下文效率
| 维度 | 万悟(传统后端性能) | WorkBuddy(LLM 上下文效率) |
|---|---|---|
| 性能对象 | 微服务链路吞吐 / 尾延迟 | token 成本 / 上下文窗口 |
| 核心手段 | 模型路由 / 语义缓存 / GPU 调度 / 熔断 | Prompt Cache / 上下文压缩 / 渐进加载 |
| 成本观 | 算力 / 延迟 | Billing / Credits(按调用计费) |
| 模型选择 | 按任务路由 | 模型无关路由(Auto,按任务选模型) |
| 瓶颈定位 | OTel 链路 Trace | 上下文压缩率 / 缓存命中率 |
| 哲学对应 | Agent OS 基础设施"监控运维" | Agent OS 智能服务层 Billing/Credits + MaaS |
3.4 同一问题,两种答案:P99 飙升
- 万悟:打开 Trace,定位是哪个微服务 Span 慢——是模型推理排队,还是某次外部调用阻塞?用熔断 + 路由 + 缓存把尾延迟压下来。
- WorkBuddy:看 Prompt Cache 命中率与上下文压缩率——是不是每次都把超长原文塞进窗口导致 token 暴涨、成本高且慢?用压缩 + 缓存 + 选对模型把效率提上来。
两者都追求"更快更省",但万悟优化的是机器时间(延迟),WorkBuddy 优化的是token 经济(成本/窗口)。诚实标注:WorkBuddy 公开材料偏"token 成本 / 上下文效率",对"传统尾延迟工程"(如对象池化、背压、尾延迟对冲)覆盖较浅——这部分恰恰是万悟作为后端工程平台的强项。读者不必强求 WorkBuddy 也讲 sync.Pool,那本就不是它的战场。
4. 业界怎么做的
4.1 Google:尾延迟对冲的原始论文
Jeff Dean 和 Luiz Barroso 在 2013 年发表的 The Tail at Scale(CACM)是尾延迟工程的奠基之作。核心论点:
"在大规模分布式系统中,一个用户请求往往扇出到数十个后端服务。即使每个服务的 P99 只有 1s,扇出 100 个服务后,整体 P99 接近 100% 的请求会超过 1s。"
他们提出的对冲请求(hedged requests)是尾延迟对冲的理论基础。万悟用 OTel 链路 + 熔断把这套思想落在后端工程上;WorkBuddy 则用"模型无关路由 + 上下文压缩"在 token 维度达成类似的"避免最慢路径"效果。
4.2 Netflix:自适应并发限制
Netflix 在 2024 年开源了 concurrency-limits 库,核心思想是:不要硬编码并发上限,让系统自己发现最优并发数(类似 TCP 拥塞控制的 AIMD 算法)。这对"批量任务抢 LLM rate limit"的场景尤其相关——背压不该是写死的常量,而应随下游健康度自适应。
4.3 Cloudflare:P99 作为 SLO 而非 P50
Cloudflare 在 2023 年的性能工程博文中提出:
"如果你的 SLO 是'平均延迟 < 100ms',你的工程师会优化 P50 从 80ms 到 60ms——这对 99% 的用户没有感知。把 SLO 改成'P99 < 200ms',工程师才会去追那 1% 的长尾。"
这对两个平台都成立:万悟该把 SLO 钉在链路 P99 上,WorkBuddy 该把"上下文压缩率 / 缓存命中率"当成可度量的效率 SLO——毕竟对 LLM 产品而言,"窗口占用失控"就是另一种形式的"慢"。
5. Trade-off 与常见误区
5.1 这个选择的代价
| 代价 | 具体表现 | 通用应对 |
|---|---|---|
| 对冲增加 LLM 成本 | +7% token 消耗 | 仅在 P95 以上触发,正常请求无开销 |
| 背压增加用户等待时间 | 批量任务从"并行 20s"变成"串行 60s" | 用户预期管理:前端显示"排队中 47/200" |
| 超时预算增加复杂度 | 每个模块需要从 header 中读取预算 | 封装为中间件,业务代码无感知 |
| 对象池有并发陷阱 | 如果 Put 回去的对象被修改,下一个 Get 会拿到脏数据 | 每次 Get 后 Reset(),CI 有 lint 检查 |
5.2 三个常见误区
误区 1:"P99 高是因为服务器配置不够。"
通用实践中,解析(Go)跑在 2 核 4G 的容器里,CPU 使用率常态 < 30%。P99 高的原因不是"算力不够",而是"200 份文档抢同一个 LLM rate limit"。加 CPU 对 LLM 的外部延迟没有任何帮助。在优化之前,先搞清楚瓶颈在哪里(Trace 数据),再决定加什么资源——万悟用 OTel 链路做这件事,WorkBuddy 用缓存命中率 / 压缩率做这件事。
误区 2:"背压就是限流。"
限流(Rate Limiting)是"超过阈值就拒绝"。背压(Backpressure)是"超过阈值就减速,但不拒绝"。对于批量文档分析,用户上传了 200 份,期望是"全部完成"。如果限流拒绝了 50 份,用户需要重新上传——这是极差的体验。背压让系统慢下来,但承诺"全部做完"。
误区 3:"优化应该从最慢的模块开始。"
LLM 调用可能占 78% 的时间,但它是外部服务——你无法优化模型服务商的内部调度。你能做的是:减少不必要的调用(合并相似内容)、对冲慢调用、在等待时做其他事(流水线化)。真正"可优化"的往往是那些"看起来不慢但可以被并行化"的内部环节。这也呼应 3.4:万悟优化内部链路,WorkBuddy 优化 token 经济,都不去硬刚外部模型延迟。
5.3 一个开放问题
尾延迟对冲目前多在 LLM 调用层实施。但如果瓶颈转移到内部规则引擎(比如规则数量从 200 条增长到 2000 条),是否需要在内部层也做对冲?内部计算是本地执行(Go),不像 LLM 是外部服务——"对冲"一个本地计算意味着什么?是开两个 goroutine 跑同一份规则然后取先完成的?还是把规则集分片到两个实例并行跑?万悟作为后端工程平台,这类"内部计算尾延迟"正是它的主场;而 WorkBuddy 的上下文效率视角,则会先问"能不能少传点上下文,让规则少跑几遍"。两种思路,对症不同时期的瓶颈。
6. 动手练习
场景:你的 AI 文档分析系统日均处理 2000 份文档。最近一周,客户投诉"系统变慢了"。你的监控显示:
| 指标 | 上周 | 本周 |
|---|---|---|
| P50 | 2.0s | 2.1s |
| P95 | 4.5s | 4.8s |
| P99 | 8.0s | 19.3s |
| 日均请求量 | 1800 | 2400 |
| LLM API 429 错误率 | 0.1% | 4.7% |
| 批量分析(>50 份)占比 | 12% | 31% |
任务:
- 诊断:根据以上数据,判断 P99 飙升的最可能根因。写出你的推理链(不要直接跳到"加机器")。
- 短期止血(1 天内):设计一个不需要改代码的临时方案,让 P99 回到 12s 以内。
- 中期方案(2 周内):设计背压 + 对冲的实施方案。画出信号流:从 LLM 429 → 到用户感知,经过哪些模块、每个模块做什么。
- 双平台视角:如果成本暴涨(token 消耗翻倍),万悟式(模型路由 + 语义缓存)和 WorkBuddy 式(Prompt Cache + 上下文压缩 + 选对模型)分别会从哪一头下手降本?两者思路的根本差异是什么?
- 长期预防:设计 3 个性能 SLO 和对应的告警规则。要求:告警触发时,工程师能在 5 分钟内判断"是 LLM 慢了"还是"是内部模块慢了"。
| 评估维度 | 好的答案应该覆盖 |
|---|---|
| 根因分析 | 不是"系统慢了",而是"哪个环节、在什么条件下、因为什么资源竞争而慢了" |
| 分层思维 | 区分"可优化的"(内部模块)和"只能适应的"(外部 LLM) |
| 用户视角 | P99 不是数字,是"每天 N 个用户等了超过 X 秒" |
| 成本意识 | 对冲有成本、加机器有成本、换模型有准确率成本——trade-off 要量化 |
| 可逆性 | 短期方案是否容易回滚?长期方案是否引入了新的复杂度? |
⏱️ 30 秒速览
这篇你只需要记住 3 件事:
- "平均延迟"是统计学幻觉——P99 和 P999 才是用户真实体验的度量
- 万悟做"传统后端性能"(吞吐/尾延迟/熔断);WorkBuddy 做"LLM 上下文效率"(token 成本/窗口/缓存)
- AI 平台有两套性能观:机器时间 vs token 经济,选型时要先想清楚瓶颈在哪一头
📌 本文小结
- "平均延迟"是统计学幻觉——P99 和 P999 才是用户真实体验的度量
- 性能优化的第一步永远是定位瓶颈(Trace / 缓存命中率),而不是"加机器"
- 背压是"减速不拒绝",限流是"超了就拒绝"——批量场景需要前者
- 万悟用模型路由 / 语义缓存 / GPU 调度 / 熔断做后端性能工程
- WorkBuddy用 Prompt Cache / 上下文压缩 / 模型无关路由做 LLM 上下文效率工程
- 诚实标注:WorkBuddy 偏 token 成本 / 上下文效率,传统尾延迟工程覆盖较浅——那是万悟的强项
📚 参考资料
- Jeff Dean & Luiz Barroso, The Tail at Scale, CACM 2013 — research.google/pubs(尾延迟工程奠基论文)
- Netflix Technology Blog, Adaptive Concurrency Limits, 2024 — netflixtechblog.com(AIMD 算法在并发控制中的应用)
- Cloudflare Blog, How We Measure Performance, 2023 — blog.cloudflare.com(P99 作为 SLO 的实践)
- Go Wiki, sync.Pool — pkg.go.dev/sync#Pool(对象池化的标准用法和陷阱)
- Brendan Gregg, Systems Performance, 2nd Edition, Addison-Wesley, 2020(USE / RED 方法、如何判断瓶颈是 CPU 还是 IO)
- Anne《从模型到 Harness:WorkBuddy 如何把 Agent 做成可用产品》(腾讯技术工程,2026-07-24)— Prompt Cache / 上下文压缩 / 渐进加载
- 汪晟杰《从一个人到一支队伍,AI 如何重写生产力》(腾讯新闻,2026-07-22,WAIC)— Agent OS 智能服务层 Billing/Credits/MaaS 模型无关路由
📖 下一篇预告
第三季·第 6 篇:多语言与契约边界——何时该上多语言
两种语言怎么共享类型定义?protobuf 的
oneof和 Go 的interface{}怎么对齐?CI 里 Go 测试通过了但 Python 测试挂了,是谁的问题?当 Go 侧改了一个 struct 字段但忘了更新.proto文件,Python 侧会在什么时候爆炸——编译时、启动时、还是凌晨三点的生产环境?下一篇,我们聊聊"双语言不是两倍效率,是两倍的纪律要求",以及:为什么有的平台根本不需要多语言。
📱 关注公众号,追更不迷路
本系列文章首发于微信公众号「农夫三拳有点癫」,每周更新源码拆解与架构实战。
在微信扫描下方二维码即可关注:
本文来自博客园,作者:老羅,转载请注明原文链接:https://www.cnblogs.com/laoluo2025/p/22854930


浙公网安备 33010602011771号