吞吐与延迟:一个厨房比喻讲透性能压测

写于 2026-06-26。背景: MCP 服务 跑在 3 台 ClickHouse(每台 16 核 / 64G,1 分片 3 副本)上。
我们花了一整轮做公网压测,把这套系统的极限、天花板和杠杆全摸清了。这篇把"吞吐 / 延迟 / 排队"这三个最容易混的概念讲透,配我们自己的真实实测数据。


一句话结论

我们这套系统的吞吐天花板 ≈ 76 req/s。 往里塞再多并发(100、200、300、500),每秒"做完"的还是大约 76 个,多出来的全在排队。系统不会崩,只会让每个人等得更久。

天花板能不能抬?能,但不是靠多塞请求,而是靠三个硬杠杆(文末)。


一、两个最容易混的指标:吞吐 vs 延迟

指标 是什么 像什么
吞吐(req/s) 每秒完成多少个请求,是个速率 厨房每分钟出多少盘菜
延迟(p50/p95) 你这一个请求从发出到拿到结果花多久 你这一盘菜从下单到上桌花多久

这俩是两个独立的旋钮。最经典的误解就是把"76 req/s"当成"每个请求 1 秒内返回"——

76 req/s 的准确意思是:系统平均每秒能做完 76 个请求。它本身不保证每个都在 1 秒内返回。

返回快不快,是延迟决定的,跟吞吐是两回事。


二、厨房比喻(整套模型一次讲完)

把整个集群想成一个最快每分钟出 76 盘菜的厨房:

  • 人少时(稀稀拉拉来): 厨房随到随做,你的菜 0.4 秒上桌——又快、又没满负荷。这时吞吐低(只来几个),但延迟极好。
  • 人多猛灌时(200 个同时不停下单): 厨房还是拼命出菜、速率顶到 76 盘/分不变,但你的单子排在前面 190 单后头,你的菜要等 2.5 秒。厨房一秒没歇,可你就是等得久。

关键洞察:

厨房的最大出菜速率是固定的(=吞吐天花板)。你越往里塞人,塞进去的都只是变长的队,不是更多的出菜。

延迟 = 排队 + 做菜

延迟可以精确拆成两块:

延迟 = 排队时间 + 做菜时间
       (queue wait)  (service time)
  • 做菜时间 ≈ 恒定:CPU 实际算这一个查询,大约 0.3~0.4 秒,基本不变。
  • 排队时间 = 随你超过天花板的程度暴涨:没挤时 ≈ 0,猛灌时堆到 2 秒。

所以:

  • 没挤 → 排队≈0 → 延迟 ≈ 只剩做菜(0.37s)
  • 猛灌 → 排队吃掉大头 → 延迟 2.5s(其中 2 秒多都在排队,真正做菜还是那 0.4 秒)

三、真实实测数据(公网压测,2 个 Go 实例)

场景 吞吐(出菜速率) 你的延迟 p50 p95 CH CPU
100 真实 agent(带 1.5s 思考停顿) 54 req/s 0.37s 0.73s ~44%
100 猛灌(一个完立马下一个) 81 req/s 1.15s 2.72s ~84%
200 真实 agent 76 req/s 0.74s 1.8s ~44%
200 猛灌 73 req/s 2.54s 6.17s ~84%
300 猛灌 73 req/s ~85%

读这张表的正确姿势:

  1. 吞吐都在 73~81 这个带子里,无论并发是 100、200 还是 300。这就是那堵 ~76 的墙。
  2. 同样的吞吐,延迟天差地别:100 真实 = 0.37s,200 猛灌 = 2.54s。差别全在"排队多深",不在厨房快慢。
  3. 真实节奏的用户极抗压:200 个带思考停顿的真 agent,吞吐 76、延迟还在亚秒、CPU 才 44%。系统对"真实流量"非常从容——所以监控看着一直很平静,不是没压上,是真实流量根本压不满。

四、一条小学公式把它们拴在一起:Little 定律

同时在排队 / 在做的请求数 = 吞吐 × 延迟

代入我们的数:

  • 76/s × 0.4s ≈ 30 个在飞 → 队浅,人人亚秒。
  • 76/s × 2.5s ≈ 190 个在飞 → 队里堆了 190 个,所以每人等 2.5s。

吞吐是固定的天花板,延迟随队深线性涨。 你往系统里塞的并发,超过天花板的部分,百分百变成队列深度,而队列深度直接等比例变成延迟。


五、"请求"不等于"用户"——别被 76 这个数吓到

76 req/s 是请求数,不是用户数。一个用户一次会话会打很多个请求,而且中间有大量停顿(等模型想、等人看结果)。

  • 如果一个真实用户平均每 5 秒才打一下,那 76 req/s 能同时托住约 380 个这样的用户(76 × 5)。
  • 所以 "76" 听起来小,但因为真实用户是断续打的,实际能服务的人远不止 76

六、为什么要做限流层

现在系统一旦总量超过 ~76,反应是所有人一起排队变慢(p95 飙到 6 秒)——这是最差的体验,因为受害的是全体。

限流层干两件事:

  1. 公平(多租户): 不让某一个用户 / 失控 agent 把 76 这块蛋糕独吞,每人分一片 → 同样天花板下服务更多用户。
  2. 保命(更关键): 把超出的部分挡回去 / 限速,保住其余人的亚秒体验。从"大家一起烂"变成"只限住打猛的那个,其他人不受影响"。

诚实交底:限流不增加总容量(还是 ~76),它是把容量公平分配 + 守住低延迟区。它管的就是"每个用户每秒能打几下"——一限,76 这块蛋糕就能分给更多人,而且保证大家都待在"亚秒、没排队"的舒服区。

形态(两层):

  • 全局准入闸(保命): 限总在飞请求数(比如 ≤ 120),超了直接 429 + Retry-After,让规矩的客户端退避。守住"绝不进入 p95 6s 的烂区"。
  • 每 key 配额(公平 + 变现): 按 API key 做 token bucket,免费档每秒几下、付费档放宽。商业产品的限流层本质就是计费层。

七、天花板能不能抬?三个硬杠杆

76 是当前硬件 + 当前查询组合的天花板,不是宇宙常数。能挪它的只有三个:

杠杆 效果 代价
升 CPU(16→32 核 ×3) 48→96 核 ≈ +100% → ~140-150 req/s 最值,零拓扑改动
加第 4 副本(16 核) 48→64 核 ≈ +33% → ~100 req/s,近线性 顺带 HA 更强,不用重分片
结果缓存(Redis) 有效吞吐 = 76 / (1 − 命中率),命中率 80% → ~380 "流氓层",省下的是 CPU

注意:加内存不在这张表里。 我们 3 台内存一直很空(每台只占 2-3G,剩 ~55G 是 page cache 在兜热数据)——但那是"假空闲":卡的是 CPU 算力,不是内存容量。空闲内存换不出 QPS,唯一途径是上面那个结果缓存(把结果存内存里、直接跳过 CPU 计算)。要花钱,花在 CPU 上。

也别用分片(sharding)。 分片是把数据切开、每个查询扇出到多个分片——它降的是"单条大扫描"的延迟。我们不是扫描瓶颈(数据全在内存、CPU:disk = 20:1),分片只会加扇出协调开销 + 巨大的重分片运维成本。要并发就加副本,不要加分片。


附:压测方法论(怎么测出这些数)

  • 闭环 N 个常驻 worker:每个 worker 打一下 → 等(think)→ 再打,循环。think=1.5 = 模拟真人 agent(看完结果再问);think=0 = "一个完立马下一个" = 极限猛灌。
  • 从服务器端双 IP 打公网(data + ops-110 各 100 路),比从本机可信。
  • 同步密采 3 台 CH 的 /proc/stat CPU,确认真实 CPU%(不是 loadavg)。
  • 踩过的坑:worker 循环里漏了 think-sleep → 死 socket 上紧循环重连 → 单 IP 撞 per-IP 连接上限 → 造出 1000 万次假失败。修法 = think-sleep + 失败重连 + 多 IP。压测器自己的 bug 会造出假信号,数据要交叉验真。

Read more

Mac 上 Skill CLI 无法执行的坑:最后其实一条命令就够了

我在做 Amazon skills 的过程中,逐步把本地 CLI 从 Python 脚本切到 Go 二进制。这样做的好处很明显:用户不用装 Python、不用配依赖,解压 skill 后直接运行。但在 macOS 上,我们反复遇到一个看起来很玄的问题:同一个二进制,在 Linux/Windows 上正常,在 Mac 上就是执行不了。 当时遇到的现象 常见报错大概有几类: * 双击或 agent 调用 CLI 时,系统提示文件来自未知开发者,无法打开。 * 终端里执行时提示 Permission denied。 * 已经 chmod +x 了,仍然被 macOS 拦截。 * Apple

By ladydd

当我把全世界人群的基因 PCA 跑出来后,看见了一个倒 L 型

最近我把之前学的一些分子人类学知识,终于真正落地了。 不是停留在看论文、看别人画图、看别人解释“人群结构”这些概念,而是自己把数据处理完,自己跑 PCA,自己把全世界不同人群放到一张图上。 然后那一刻,我真的被击中了。 图上出现了一个非常漂亮的倒 L 型。 一端是非洲,另一端逐渐拉向东亚,中间有中东、欧洲、南亚、欧亚大陆上的各种过渡人群。它不是那种随机散点图,而是有方向、有骨架、有历史感的结构。 我第一眼看到的时候,脑子里直接冒出一句话: 这不像是一张普通统计图,这像是人类迁徙史在二维空间里留下的影子。 当然,后来我也提醒自己,PCA 不能被过度浪漫化。它不是地图,也不是时间轴,更不是“谁从哪里走到哪里”的直接证据。PCA 本质上是把高维基因差异压缩到几个主成分上,用最大方差方向把样本摊开。它可以帮助我们观察人群结构、相似性、分化和混合,但不能单独承担全部历史解释。PCA 在群体遗传学里常用于观察 population structure

By ladydd

四卡 3090 本地模型部署复盘:Ollama 跑通 35B,以及 GPU0 掉卡问题

这次做的是一轮真实的本地模型部署摸底。 目标不是搭一个临时 Demo,而是把一台四卡 3090 GPU 机器接进自己的日常 AI 使用环境:本机跑 Open WebUI,负责账号、会话和前端配置;GPU 机器只负责模型推理。这样以后换模型、换推理框架、重启服务,都尽量不影响本机的使用入口。 最后结论比较清楚:qwen3.5:35b 的 GGUF Q4_K_M 量化版已经通过 Ollama 跑通,本机 Open WebUI 可以接入,热加载后的聊天速度也能用;但 GPU0 存在明显稳定性问题,重启后能短暂恢复,跑过负载后又会掉到 NVML 异常状态。 状态快照 当前能用的部分: * 本机 Open WebUI 已部署,

By ladydd

面向 AI Agent 的 ClickHouse 集群调优实战:从病根定位到"近乎白捡"

我们最近把一套面向 AI agent 的分析型数据服务,从单机迁到了 ClickHouse 集群(1 分片 × 3 副本 + HAProxy 入口),并做了一轮系统的性能调优。 这篇不止于"我们改了哪些参数",更想讲清楚背后的思路:怎么用数据定位病根、怎么在"空间/复杂度/收益"之间取舍、怎么做到改完能验证、出事能秒回滚、上线不断服务。如果你也在为高重复、只读、模板化的负载(AI agent、看板、报表 API)调 ClickHouse,这套方法可以直接借鉴。 一条主线贯穿全文:先吃透流量特征,再分层优化,每一步都可验证、可回滚。 一、起点:先吃透你的流量长什么样 任何优化的第一步不是动手,

By ladydd
陕公网安备61011302002223号 | 陕ICP备2025083092号