type
Post
status
Published
date
Nov 19, 2025
slug
gateway005
summary
tags
gateway
category
icon
password
摘要
这篇要先立一条很基础的判断:压网关,先要测对层次。后面再看 fallback、
/readyz 波动、stream 中断、provider timeout 和 500,这些数字才有解释力。这篇先给后面的故障分析立一条健康基线。我这次只保留两条对照线:
- 一条更接近
gateway internal path
- 一条更接近
gateway + HTTP adapter的本地真实代码路径
后面无论写 failure-path benchmark,还是解释故障成本,都会拿这两条线做参照。
一、先把测量对象分开
“网关性能”这四个字很容易把几层开销混在一起。
我一开始也容易把“网关性能”当成一个总数字。再往下拆才发现,这里面至少混着
auth、governance、routing、JSON shaping、HTTP request construction、socket I/O、response parsing、JSON encode / decode 这些东西。不先分层,后面的数字基本没法解释。在这个项目里,我先把测量对象收成两条路径:
in-process mock path:更接近测gateway internal path
local HTTP adapter path:更接近测gateway internal path + real adapter cost
这篇只回答一句话:先把
gateway internal path 和 gateway + adapter path 分开,后面的性能数字才有解释力。这张图只想说明一件事:同样叫“压网关”,你可能测到的是两种完全不同的成本。
二、这组 benchmark 先拿来干什么
因为基线是为了后面解释故障成本。
这组 benchmark 先干三件事:
- 给后面的故障路径压测提供对照线
- 帮我区分“网关内部开销”和“adapter 路径开销”
- 避免后面把 fallback、timeout、stream interruption 的成本误判成网关本体突然变差
这篇当前要建立的,就是健康状态下两条路径各自的正常时间成本。
为了把这两条路径稳定跑出来,我做了一个只服务本网关的轻量 benchmark driver:
cmd/loadtest。它现在能压 POST /v1/chat/completions,支持 non-stream 和 stream,能输出 success / failure / P50 / P95 / max,stream 还能额外输出 TTFT 和 chunk 总数。这个工具现在先服务“分层测清楚”,不追求通用压测平台那种大而全。三、先看 non-stream:adapter 多出来的成本在哪
这组 non-stream benchmark 回答的问题很直接:同样一条 chat completion,请求还没进真实 HTTP adapter 之前,和已经进了之后,成本差多少。这一组先拿来拆 internal path 和 adapter path。
运行参数:
- requests =
100
- concurrency =
10
结果如下:
路径 | Success | Failure | Approx QPS | P50 | P95 | Max |
In-process mock | 100 | 0 | 740.7 req/s | 11 ms | 22 ms | 24 ms |
Local HTTP adapter | 100 | 0 | 609.8 req/s | 14 ms | 34 ms | 37 ms |
我这里只先收一个判断:adapter path 比 mock path 慢一些,而且这个差异是稳定可见的。
mock path 主要覆盖的是 auth / governance / routing / JSON shaping,adapter path 在这上面又加进了 HTTP request construction / socket I/O / response parsing / JSON encode / decode。这里看到的额外成本,先理解成 adapter 自己带进来的本地代码路径成本。这也是这组数据最有价值的地方。它把
gateway internal path 和 gateway + adapter path 拆开了。后面如果 failure path 的数字偏离很多,我才知道是在偏离哪条健康基线。non-stream 结果图
这张图只想让人一眼看到:mock path 更接近 internal baseline,adapter path 额外多了一层本地真实 adapter 成本。后面再看故障路径时,默认对照的就是这里这条 adapter baseline。
四、再看 stream:TTFT 和整条流不是一回事
stream 不能只看总时延,至少还要把 TTFT 单独拎出来。
stream benchmark 跟 non-stream 不一样。它不只是在问“整条请求慢了多少”,还在问“第一个 chunk 多久出来”和“整条流多久结束”。这两个数字如果混在一起看,stream 的结论会很容易偏。stream 至少要拆成 TTFT 和 full completion 两段。
运行参数:
- requests =
50
- concurrency =
5
结果如下:
路径 | Success | Failure | Throughput | Latency P95 | TTFT P95 | Chunks |
In-process mock | 50 | 0 | 549.5 req/s | 19 ms | 12 ms | 200 |
Local HTTP adapter | 50 | 0 | 61.4 req/s | 98 ms | 28 ms | 150 |
这里最该先看的不是“adapter path 更慢”,而是 TTFT 和总时延的差距。adapter path 的
TTFT P95 还是 28 ms,但 Latency P95 已经到了 98 ms。这说明首个 chunk 出来得不算慢,整条流真正被拉长的是首 chunk 之后那一段。stream 的大头不一定在开头,很可能在后半段。这背后的原因也比较直接。当前 synthetic upstream 会通过多次写出,把整条流式过程刻意拉长。所以 adapter path 的主要增量,不是在“首 token 出来之前”,而是在“首 token 之后整条流被拉长”。这组数据先告诉我:stream 不能只拿总时延说话。
stream 结果图:TTFT vs 总时延
这张图最想说明的是:首个 chunk 很快出来,不代表整条流完成得也快。后面分析 stream 故障路径时,TTFT 和 full completion 必须分开看。
五、为什么 benchmark 前先把 tenant 限额调高
因为不先把 limiter 影响隔开,测出来的就不是 healthy path,而是治理层拒绝行为。
本地 seed tenant 默认是:
60 req/min
这个值适合 smoke test,不适合 benchmark。如果 benchmark 前不先把限额调高,请求会先被 limiter 挡住。那时你测到的是 quota 策略,不是
gateway internal path,也不是 adapter path。调高 tenant 是为了把治理层噪声先隔离出去。这一步本身就是 benchmark 方法的一部分。当前这一篇要看的,是健康状态下网关路径的正常成本,不是 rate limiter 的拒绝成本。基线要先干净,后面的故障成本才有地方落。
六、资源快照先补一眼:本地依赖里谁最重
前面的请求太短,单次 benchmark 看不出本地 stack 的资源主导项是谁。
前面的
100 请求 benchmark 结束得很快,一次性进程采样意义不大。所以我又补了一次更长的观测:观测期间的资源快照如下:
Component | Approx Memory |
Gateway | 28 MiB |
MySQL | 587.8 MiB |
Redis | 9.953 MiB |
这组快照我先不把它讲成“生产结论”。它当前只回答一个很实际的问题:本地 benchmark 环境里的资源 footprint 主导项是谁。结果很明确,当前主导项是 MySQL,不是 Redis,也不是网关进程本身。
七、这组结果先收到哪里
本地 adapter path 不等于 hosted provider benchmark,这个边界先说清楚,后面的结论才不会飘。
这里的 adapter path 对比,不是 hosted provider benchmark。当前用的是本地 synthetic OpenAI-compatible upstream,所以这里测到的是:
- 网关路径
- adapter 路径
- 本地 upstream 交互开销
它没有覆盖:
- WAN 抖动
- 真实 provider 侧波动
- 公网环境下更复杂的时延分布
所以这组 benchmark 的定位很明确:它建立了
gateway healthy path 的分层基线,但它不能替代真实 hosted-provider latency study。八、怎么复现实验
这篇的价值之一就在于这些基线可以稳定复现,不是一次性的截图数字。
当前复现实验分两条路径:
in-process mock path:更接近测gateway internal path
local HTTP adapter path:更接近测gateway + adapter的本地真实代码路径
这里最重要的不是命令本身,而是复现路径本身是稳定的。后面再写 failure-path benchmark 时,我可以回到这里重新对照。这条基线后面是可以反复拿来用的。
总结
压网关先要测对层次。只有把
gateway internal path 和 gateway + adapter path 的健康基线先分开,后面的故障成本才有解释力。现在已经能先收住这几条数字:- non-stream 的
in-process mock path是740.7 req/s,P95 22 ms
- non-stream 的
local HTTP adapter path是609.8 req/s,P95 34 ms
- stream 场景下,adapter path 的
TTFT P95 = 28 ms
- 同一条路径的
Latency P95会到98 ms
本文确定了一条后面能继续拿来解释 failure path 的健康对照线。
分享
