基准测试工具之你不知道的两三事 | 11. 性能测试与正确性验证有什么区别
基准测试工具之你不知道的两三事 | 11. 性能测试与正确性验证有什么区别
一轮基准测试结束后,我们通常最先看到的是吞吐量、平均延迟、P99 和失败数。如果吞吐足够高、失败数又是 0,能否直接说明测试通过?
不能。性能测试回答的是“系统处理得多快”,正确性验证回答的是“系统处理的结果是否符合预期”。一次请求返回成功,只能说明客户端收到了一次成功响应,并不能自动证明所有数据都以正确的设备、时间戳、类型和值落入数据库。
这一篇结合 IoT Benchmark 的实际实现,讲清性能与正确性应怎样分开测试,又怎样合并为一个可信结论。

1. 两类测试回答的是不同问题
| 维度 | 性能测试 | 正确性验证 |
|---|---|---|
| 核心问题 | 系统能处理多快、多稳? | 写入和查询结果是否符合预期? |
| 常见指标 | 吞吐量、平均延迟、P99、失败率、CPU、内存、磁盘与网络 | 记录数、时间戳、设备与测点、数据类型、具体值、查询结果集合 |
| 主要输入 | 工作负载配置与环境配置 | 可重复读取的预期数据集或可信参考结果 |
| 典型结论 | “在当前并发和批大小下,系统达到某性能区间。” | “预期写入的记录均可查询,且字段和值一致。” |
| 失败表现 | 延迟抬升、吞吐平台、超时、资源饱和 | 少行、多行、错值、错类型、时间戳偏移、重复或覆盖 |
两者不是互相替代的关系。正确但很慢,说明系统能力不满足业务目标;很快但写错,性能数字则失去了意义。
2. 为什么“写入成功”仍可能写错
客户端成功调用写入接口,并不等于业务数据完全正确。实际测试中至少可能出现以下情况:
| 表面现象 | 可能隐藏的问题 |
|---|---|
| 请求返回成功 | 批内部分记录被忽略,客户端只看到整体响应。 |
| 失败数为 0 | 数据写入了错误设备、表、测点或时间分区。 |
| 写入点数符合预期 | 字段顺序、数据类型或具体值发生变化。 |
| 查询能返回数据 | 返回的是旧数据、重复数据,或被相同时间戳覆盖后的结果。 |
| 两个系统吞吐接近 | 两边真正持久化的数据量或写入语义并不相同。 |
因此,写入正确性至少应核对四层:
- 数量正确:预期多少行、多少点,实际是否一致。
- 定位正确:设备、表、测点、标签和时间戳是否正确。
- 内容正确:类型、空值语义和具体值是否一致。
- 查询正确:点查、范围查询和聚合是否返回预期结果。
如果测试还涉及重试、相同时间戳覆盖或乱序写入,则应再增加重复、幂等和最终可见性检查。
3. IoT Benchmark 的三段式验证流程
IoT Benchmark 提供了 generateDataMode、verificationWriteMode 和 verificationQueryMode。从代码看,它们组成了一条“准备预期数据 → 写入数据库 → 查询核对”的流程。
三个模式的职责并不相同:
| 模式 | 主要职责 | 它不能单独证明什么 |
|---|---|---|
generateDataMode |
生成可复用的数据文件和 schema。 | 不会把数据写入待测数据库。 |
verificationWriteMode |
从 FILE_PATH 读取批次、注册 schema 并执行写入,同时统计写入性能。 |
写入结束本身不代表数据已经逐值校验。 |
verificationQueryMode |
再次读取同一数据集,构造验证查询,并将查询结果与预期时间戳和值比较。 | 不能替代高并发性能压测,也不是所有数据库适配器都必然支持。 |
这意味着,若只运行 verificationWriteMode 并看到失败数为 0,最多能说“写入调用没有被工具判定为失败”,不能说“所有数据已经验证正确”。
4. 验证查询在代码中怎样工作
在 verificationQueryMode 下,RealDataSetQueryClient 会从数据文件中取得一个 IBatch,再构造 VerificationQuery。这个对象同时携带:
- 目标设备及其测点 schema;
- 该批记录的时间戳;
- 每个时间戳对应的预期值。
数据库适配器据此生成查询,读取这些时间戳对应的数据,并把返回值与预期值逐项比较。以当前 IoTDB 适配器为例,代码还会对 BLOB、DATE 等类型做格式转换,再进入比较。
这一逻辑比简单的 count(*) 更严格,因为“行数相同”仍可能包含错误值。但它也有明确边界:验证覆盖范围取决于对应数据库适配器的 verificationQuery 实现;接口默认实现会返回“不支持验证查询”。因此,开始测试前应先确认所用 DB_SWITCH 对应的适配器确实实现了该能力。
5. 一个必须检查的实现细节:错误日志不等于失败计数
按本文读取的本地版本 de2f8f3f,IoTDB 的验证查询在发现值不一致或返回行数不一致时会输出 ERROR 日志;但只要查询过程本身没有抛出异常,方法最后仍可能返回 Status(true, point)。
随后,DBWrapper 会把 Status.isOk() 为真的操作计入成功测量。也就是说:
最终报告中的查询失败数为 0,不足以证明逐值比较没有发现差异。
因此,正确性判定至少要同时查看:
| 证据 | 应检查的内容 |
|---|---|
| 验证日志 | 是否出现 Expected ... but was ...、预期行数不一致或查询错误。 |
| 返回点数 | 实际匹配点数是否等于根据数据集计算出的预期点数。 |
| 数据集规模 | 输入文件的设备数、记录数和测点数是否与测试方案一致。 |
| 抽样复核 | 手工或独立脚本查询若干设备与时间范围,确认值和类型。 |
| 服务端状态 | 是否仍存在异步写入、后台处理或尚未可见的数据。 |
这个细节并不意味着验证模式没有价值,而是提醒我们:日志和预期数量也是验证结果的一部分,不能只看最终汇总表。
6. 如何实际运行一轮验证
首先准备一份固定数据集。可以使用 generateDataMode 生成,也可以按 IoT Benchmark 要求准备 FILE_PATH 下的数据文件和 schema。
1 | |
随后清理目标范围并写入同一份数据:
1 | |
写入完成后不要清理目标数据,再切换为查询验证:
1 | |
示例只表达模式切换关系。实际运行时还应完整冻结数据库版本、DB_SWITCH、数据模型、批大小、客户端数、数据文件校验值和测试日志。
7. 性能对比应该设置正确性门禁
当比较两个版本、接口或产品时,建议先定义门禁,再生成性能排名。
| 门禁 | 通过条件示例 |
|---|---|
| 写入调用门禁 | 无未解释的超时和失败,成功写入量达到预期。 |
| 数量门禁 | 设备、记录和数据点数量与固定数据集一致。 |
| 内容门禁 | 时间戳、类型和值的比较无差异。 |
| 查询门禁 | 关键点查、范围查询与聚合结果符合参考结果。 |
| 可见性门禁 | 在约定等待或刷新条件后,数据对新查询可见。 |
只有全部通过,吞吐量和延迟才进入最终结果表。若某个系统更快但未通过正确性门禁,应报告为“结果无效”或“当前配置下验证失败”,而不是继续参与排名。
8. 四个常见误区
第一,把失败数为 0 当作数据完全正确。失败数反映工具如何判定操作状态,不一定覆盖逐值差异。
第二,只比较总行数。相同时间戳覆盖、错设备和错字段都可能让总量看似正常。
第三,性能测试结束后直接清库。如果还没有保存配置、日志并执行查询验证,正确性证据会一并消失。
第四,一边验证,一边随意更换数据集或 schema。预期数据必须固定,否则比较对象本身已经发生变化。
9. 小结
性能测试关注吞吐、延迟和资源,正确性验证关注数量、位置、类型和值。IoT Benchmark 可以通过生成数据、验证写入和验证查询三个模式组织这条流程,但使用者仍需检查适配器支持范围、错误日志和预期点数。
最稳妥的原则是:先证明写对,再讨论写快;正确性门禁未通过的性能数字,不参与比较。 下一篇将继续讨论单机测试与集群测试的区别,以及节点、网络和数据分布如何改变测试结论。
系列文章
- 第一篇:数据库基准测试工具是什么
- 第二篇:从一次时序数据库写入测试开始
- 第三篇:如何模拟线上写入负载
- 第四篇:如何读懂测试结果波动
- 第五篇:什么是读写混合负载
- 第六篇:如何模拟线上读写混合负载
- 第七篇:怎样做一场公平的性能对比
- 第八篇:怎样找到并发与批大小的合理区间
- 第九篇:如何模拟乱序写入
- 第十篇:不同写入接口该怎样比较
- 第十一篇:性能测试与正确性验证有什么区别
- 第十二篇:单机测试与集群测试有什么不同
- 第十三篇:怎样保存和复盘一轮测试
- 第十四篇:一份可复用的基准测试方案模板