文档目录

3.8 Lab 3:同一个接口的两层测试

上一节:3.7 层级选择与成本 | 下一节:第 4 章 工具链 配套代码:03-test-pyramid/08-lab3 预计时长:90 分钟


一、这个 Lab 的目标

亲手验证本章的核心论断:不同层级会发现不同的问题,而且有些问题只有某一层能发现。

做完之后你应该能对任何性能问题回答:「这个该在哪一层测,为什么不是另一层。」


二、任务清单

# 任务 产出 时长
1 搭组件基准(Testcontainers + 采样循环) ComponentBench.kt + 结果表 35 min
2 搭集成基准(Ktor 完整启动) IntegrationBench.kt + 结果表 20 min
3 全链路压测(k6 从另一进程) k6 报告 20 min
4 对比三层结果,写出「只有它能发现」的清单 实验档案 15 min

三、任务 1:组件基准

准备:

  • 用 Testcontainers 起 PostgreSQL(不要用 H2)。
  • 建表并灌 10 万行数据,用幂律分布(前 1% 的用户占 30%–40% 的查询)。
  • 建必要的索引,故意留一个没有索引的列作为对照。

要测的四件事(分开测):

# 测什么 预期发现
1 主键查询 findById 走索引,P99 应该很低
2 二级索引查询 findByUserId 走索引 + 回表
3 无索引列查询 findByStatus ⚠️ 全表扫描,P99 随数据量线性恶化
4 批量查询 vs 循环单查 量化「消除 N+1」的收益

方法要求:

  • 预热 5000 次(让连接池填满、PG 缓存热起来)。
  • 采样 20000–50000 次,每次单独计时。
  • 排序后报告 P50 / P95 / P99(不要只报平均)。
  • 统计查询次数(用来发现 N+1)。

四、任务 2:集成基准

做法:把 Ktor 服务完整启动(真实端口,不加反向代理),用一个简单的并发客户端压。

必须先做:

  1. 独立预热阶段(至少 60 秒或直到 CV 收敛,第 2 章 2.4 节)。
  2. 明确并发模型(闭环还是开环,写进记录)。

要观察:

观察点 为什么
端到端 handler 耗时 与组件基准的差额 = 框架开销(拦截器、事务、序列化)
连接池 active/pending 单服务并发下池子够不够
启动预热曲线 从启动到稳态需要多久

已知局限(写进记录):

  • 闭环模型会低估尾延迟(第 0 章 0.6 节)。
  • 不含网络与网关。
  • 所以它的定位是「与组件基准做相对比较」,不是「验收 SLO」。

五、任务 3:全链路压测

做法:用 k6 从另一个进程(最好另一台机器)压。

必须做:

  1. 用 constant-arrival-rate executor(开环)。
  2. 验证实际 QPS 是否等于设定值——不相等就说明压测机是瓶颈,数据作废。
  3. 记录客户端的 P50/P95/P99 与错误率。

六、任务 4:对比与结论(本 Lab 的核心)

填出这张表:

指标 组件基准 集成基准 全链路 差额归因
P50 (ms)
P95 (ms)
P99 (ms)
错误率
测量点 Repository 方法 服务端 handler 压测客户端 —
并发模型 单线程循环 闭环 开环 —

然后回答这三个问题(这是验收的关键):

问题 1:哪个问题只有组件基准能发现?

(提示:无索引列的 P99、批量 vs 循环的差异)

问题 2:哪个问题只有集成基准能发现?

(提示:连接池 pending、事务开销、框架拦截器成本、启动预热)

问题 3:哪个问题只有全链路能发现?

(提示:网络延迟、排队、网关开销、跨进程的尾延迟放大)

如果三个问题的答案都是「差不多」,说明你的实验设计有问题——最常见的原因是:三层用了不同的数据量或不同的负载,导致不可比。


七、验收标准

  • 组件基准用真实 PostgreSQL(Testcontainers),不是 H2。
  • 数据集是 10 万行 + 幂律分布,并写明在记录里。
  • 组件基准把「SQL 执行 / 映射 / 业务逻辑 / 序列化」至少分开测了两项。
  • 组件基准报告的是 P50/P95/P99,不是平均值。
  • 集成基准有独立预热阶段,并写明了并发模型。
  • 全链路压测验证了实际 QPS == 设定 QPS。
  • 三层结果填在同一张表里,差额有归因。
  • 明确说出三项「只有某一层能发现」的结论。
  • 实验档案有「预期 vs 实际」表和假设台账条目。

八、常见问题

Q:我没有第二台机器做全链路压测怎么办? A:两个降级方案:① 压测客户端与服务分容器部署,用 Docker 的 CPU 限制把两边隔开(比同机好,但仍不理想);② 明确记录「本次全链路数据受同机干扰」,只用于相对比较(比如优化前后),不用于容量规划。不要假装它是可信的端到端数据。

Q:组件基准的 P99 波动很大(±40%)怎么办? A:先排查三件事:① 是否预热不足(用滚动 CV 判定,第 2 章 2.4 节);② 后台任务干扰(autovacuum、日志轮转);③ 采样次数太少(P99 需要足够样本才有意义)。如果排除后仍波动大,说明数据库本身有周期性抖动(checkpoint、autovacuum)——这本身是一个重要发现,应该记进档案。

Q:为什么建议故意留一个没索引的列? A:因为你需要一个「已知的、确凿的瓶颈」作为参照。如果所有查询都很快,你无法判断「组件基准能不能发现瓶颈」——有了这个对照组,你才能确认这个层级真的有效。这在方法论上叫「阳性对照」。

Q:三层测完发现差异很小,是不是白做了? A:差异小本身就是一个有价值的结论——说明你的服务没有显著的框架开销、网络开销和排队。但要检查:① 数据量是否太小(差异会被掩盖);② 负载是否太低(排队还没出现);③ 是否真的用了开环模型。如果这三项都确认了,那么「差异小」就是可信的、值得高兴的结论。