基准测试工具之你不知道的两三事 | 7.怎样做一场公平的性能对比
前面的文章中,我们已经能够构造纯写和读写混合负载,也知道单次结果不能轻易代表系统能力。接下来就到了基准测试最容易产生误解、也最有实际价值的一步:比较。
比较两个数据库版本、两种写入接口,甚至两款数据库时,真正的问题不是“谁的吞吐量更大”,而是“这两个结果是否真的在回答同一个问题”。如果工作负载、硬件、配置或运行过程不同,数字再漂亮也无法构成公平结论。
这一篇讨论怎样把一场性能对比设计成可复现、可解释的实验,而不是一张只有排名的表格。
1. 先确定:这轮到底要比较什么?
一次对比只能有一个清晰的研究对象。常见的对象包括:
| 想回答的问题 | 对比对象 | 应保持不变的内容 |
|---|---|---|
| 新版本是否带来写入退化 | 同一数据库的两个版本 | 硬件、数据规模、连接方式、写入负载与配置。 |
| 哪种写入接口更适合当前场景 | 同一数据库的不同接口 | 数据库版本、服务端配置、数据规模、客户端并发和负载。 |
| 两款数据库对同一场景的表现如何 | 不同数据库产品 | 硬件、逻辑数据、写入节奏、查询语义和运行次数。 |
| 批大小是否影响性能 | 同一系统的两组参数 | 除BATCH_SIZE_PER_WRITE外的所有配置和环境。 |
“数据库 A 和数据库 B 谁更快”并不是一个足够完整的问题。至少还要说明:快的是哪一种负载,是纯写还是读写混合,写入是否允许乱序,查询是什么类型,以及用什么指标判断“更快”。
2. 建立一份对比实验方案
在运行之前,先把每轮测试要固定的东西写下来。它可以很短,但必须足够让后来的自己或其他人复现。
这份“实验契约”至少应包含以下几部分:
| 类别 | 需要记录的内容 | 为什么不能省略 |
|---|---|---|
| 被测对象 | 数据库名称、版本、写入接口、关键服务端设置 | 这是唯一允许主动变化的部分。 |
| 机器环境 | CPU、内存、磁盘、网络、部署方式 | 资源差异可能比软件差异更大。 |
| 数据负载 | 设备数、测点数、数据类型、时间间隔、乱序比例 | 它决定测试的数据形态。 |
| 操作负载 | 写入/查询比例、查询范围、客户端数、批大小 | 它决定系统实际面对的请求。 |
| 运行记录 | 运行编号、完整配置、日志位置、开始时间 | 它让异常结果可以被追溯。 |
| 结果口径 | 写入与查询吞吐量、P99、成功与失败数 | 它防止只挑选对自己有利的指标。 |
IoT Benchmark 的主配置、结果矩阵和延迟矩阵正好可以承载这份契约的大部分信息,但机器环境、服务端版本与部署方式仍需要额外记录。
3. 不要把“默认配置”当成公平配置
很多不公平比较并非出于主观选择,而是来自看似无害的默认差异。例如,两个数据库的连接方式不同、一个启用了压缩而另一个没有、一个测试使用本地 SSD 而另一个使用共享存储,或者一个系统测的是纯写,另一个测的是读写混合。
下面几种比较不能直接成立:
| 不可直接比较的情况 | 为什么不成立 | 正确做法 |
|---|---|---|
| 两组设备数、测点数或总点数不同 | 处理的数据规模不同 | 固定逻辑数据规模后再比较。 |
两组DB_SWITCH或接口不同 | 请求路径和客户端开销不同 | 要么固定接口,要么把“接口差异”明确设为唯一变量。 |
| 一组有乱序、一组无乱序 | 写入顺序不同,存储代价也不同 | 分别做顺序写和乱序写两组对比。 |
| 一组纯写、一组读写混合 | 系统承受的负载类型不同 | 为两组系统执行同一份操作比例。 |
| 一组出现失败、另一组全部成功 | 成功处理的数据并不相同 | 先解释失败并恢复可用性,再讨论性能。 |
这并不要求不同数据库的所有内部设置完全一致。内部实现本来就不同,不能强行把所有选项对齐。关键在于对外可见的工作负载和可用资源应相同,而每个系统自身的必要配置都应被记录。
4. 用基线和多轮结果替代“最好的一次”
任何一个被测对象都应该先获得自己的稳定基线,再进入比较。做法是:在同一环境、同一负载下连续运行多轮,记录写入与查询各自的吞吐量、P99 和失败数,观察它们通常落在哪个范围。
单次最高吞吐量不应成为结论,因为它可能只是暂时的缓存状态或偶然波动。更可靠的表述是:在某个固定负载和环境下,系统 A 的多轮结果通常落在某个范围,系统 B 落在另一个范围;同时,两者的尾延迟和失败数分别如何。
若两个范围高度重叠,就不应强行给出“谁更快”的结论。此时可以诚实地说明:在当前负载和环境下,尚未观察到稳定差异;再根据业务需要扩大数据规模、调整负载或补充更多轮次。
5. 结果要按操作类型分别比较
当负载中既有写入又有查询时,不能用一个总吞吐量给系统排名。IoT Benchmark 会按操作类型给出成功数、失败数、吞吐量和延迟统计,比较时也应逐行阅读。
| 结果表现 | 可以作出的判断 | 还不能作出的判断 |
|---|---|---|
| A 的写入吞吐更高、P99 更低,且两者均无失败 | 当前纯写场景下 A 的写入表现更好 | A 在所有查询场景下都更好。 |
| A 的写入更快,但 Q2 范围查询 P99 更高 | A 的写优化可能伴随该查询形态的代价 | A 整体体验一定更差。 |
| A 与 B 吞吐接近,但 A 的失败数更高 | A 当前配置下的可用性或兼容性存在问题 | A 的性能更高。 |
| 一方只在某类聚合查询中落后 | 差异与该查询路径有关 | 所有读请求都会同样落后。 |
这张表背后的原则很简单:结论必须与负载和指标的粒度相同。测的是 Q2,就只对 Q2 下结论;测的是顺序写,就不能延伸到乱序写;测的是单机,就不能延伸到集群部署。
6. 写结论时,保留边界比写排名更重要
一份可信的对比结论通常包含四句话:
在什么环境、什么版本和什么负载下; 比较了哪些对象,唯一变化是什么; 多轮运行中观察到了哪些吞吐量、P99 和失败数范围; 这个结论不覆盖哪些场景。例如,好的结论会说“在固定的顺序写入负载下,接口 A 的写入吞吐范围高于接口 B,且两者没有失败;本结果不代表乱序写入、范围查询或集群部署下的表现”。
这样的表述看似保守,实际更有价值:它让读者知道结论可以被复现,也知道什么时候不能套用。
7. 小结
公平的性能对比,不是把两组数字放在一起,而是让两组数字来自同一份问题、同一份负载和同一套可追溯条件。先明确唯一变量,冻结环境与 workload,再执行多轮测试,最后分别比较写入、查询、P99 和失败数。
在开始对比前,可以用下面的清单自查:
- 本轮比较的唯一对象是什么?
- 两组测试的机器环境与逻辑工作负载是否一致?
- 两组是否都成功完成,没有被失败数掩盖?
- 是否使用多轮范围,而非最好的一次?
- 结论是否明确了适用场景与不覆盖的边界?
当这些问题都有答案时,性能比较才不只是一次“跑分”,而能成为版本回归、接口选择和数据库选型的可靠依据。下一篇将继续讨论:怎样通过参数扫描找到并发与批大小的合理区间。
系列文章
- 第一篇:数据库基准测试工具是什么
- 第二篇:从一次时序数据库写入测试开始
- 第三篇:如何模拟线上写入负载
- 第四篇:如何读懂测试结果波动
- 第五篇:什么是读写混合负载
- 第六篇:如何模拟线上读写混合负载