基准测试工具之你不知道的两三事 | 9.如何模拟乱序写入

基准测试工具之你不知道的两三事 | 9.如何模拟乱序写入

顺序写入通常是时序数据库最容易处理的路径:同一条序列持续写入递增的时间戳。网络抖动、异步链路或边缘缓存则可能让较早的时间戳晚于新数据抵达。

IoT Benchmark 的“乱序”并不是把一批记录随机打散后直接发送。阅读它的实现会发现:工具先根据一个 stepOffset 生成记录,再在生成时间戳时按模式改写这个 offset。理解这条链路,才能知道 OUT_OF_ORDER_RATIO、POISSON 和 BATCH 实际改变了什么。

在这里插入图片描述

1. 什么才算乱序

以一条设备序列为例:数据库已经收到 10:06 的数据,随后又收到 10:03 的数据;后到数据的时间戳更早,这才是乱序。

反过来,设备在 10:06 才上传自己在 10:01 至 10:05 产生的数据,但该序列自 10:00 后没有写入任何更新数据,这批数据虽然迟到,对这一序列仍可以是顺序写入。

所以,测试前要先写清楚:哪些序列发生乱序、迟到点占多少、以及迟到点相对同序列最新点落后多少。不能只写“开启了乱序”。

2. 代码中的一条记录怎样获得时间戳

生成写入负载时,SingletonWorkDataWorkLoad 先用全局递增的 insertLoop 为设备批次分配位置,再为 batch 内每条记录计算原始 stepOffset:

1
stepOffset = (curLoop / DEVICE_NUMBER) * BATCH_SIZE_PER_WRITE + batchOffset

随后,代码分别做两件事:

  1. 调用 getCurrentTimestamp(stepOffset) 生成时间戳;
  2. 调用 generateOneRow(..., stepOffset) 生成测点值。

当使用等长时间戳(IS_REGULAR_FREQUENCY=true)时,时间戳可概括为:

1
timestamp = START_TIMESTAMP × precision + POINT_STEP × stepOffset' + POINT_STEP

其中 stepOffset' 是可能被乱序模式改写后的 offset。若关闭等长时间戳,最后一个 POINT_STEP 会替换为一个随机的 [0, POINT_STEP) 内偏移。

这张图揭示了一个容易忽略的事实:当前实现只改时间戳,不改生成测点值所使用的原始 offset。 因而它模拟的是“带有当前生成值的记录,以较早时间戳抵达”的写入压力,而不是从历史缓存中原样重放一条完整旧记录。若业务特别依赖“值与事件发生时刻”的严格对应关系,应采用真实数据集模式或自行构造数据,而不能把该生成模式直接当作历史回放。

3. POISSON:逐点决定是否后移

在 POISSON 模式下,每生成一条记录,代码先以 OUT_OF_ORDER_RATIO 为概率抽样:

1
2
3
4
if random() < OUT_OF_ORDER_RATIO:
stepOffset' = stepOffset - poissonDelta()
else:
stepOffset' = stepOffset

poissonDelta() 由 PoissonDistribution 产生。LAMBDA 和 MAX_K 用来控制该离散分布的取值;delta 的单位是 step,转换成时间差后约为 delta × POINT_STEP。

因此,POISSON 适合表达“多数数据按原节奏到达,少数点随机变旧”的形态。这里的 OUT_OF_ORDER_RATIO 是每条记录被选中改写 offset 的概率,实际采样比例会随运行而波动;它不是服务端最终统计到的、精确不变的乱序事件比例。

还应注意两个边界:

边界 对测试的影响
delta 大于当前 stepOffset 代码不会把 offset 截断到 0,早期记录可能被推到基准开始时间之前。
多客户端并发 工具决定的是记录的时间戳;同一序列请求的实际抵达顺序仍会受线程调度和网络影响。

所以,POISSON 的结果应通过抽样写入数据或服务端记录验证,而不应只凭配置值认定“已经产生了 x% 的乱序”。

4. BATCH:对 offset 做循环平移

BATCH 不是“对每一条记录按概率抽一次”。它先计算:

1
2
OUT_OF_ORDER_BASE = floor(LOOP × OUT_OF_ORDER_RATIO)
stepOffset' = (stepOffset + OUT_OF_ORDER_BASE) mod LOOP

同时,SingletonWorkDataWorkLoad 会把全局 insertLoop 的起点设置为同一个 OUT_OF_ORDER_BASE。也就是说,这一模式通过确定性的索引平移与回绕,生成一段成批出现的时间戳重排;它不是随机地从 batch 中挑出若干点。

模式 代码层面的机制 更适合观察的压力形态
POISSON 每条记录独立地按概率回退若干 step 零散、持续出现的迟到点。
BATCH 使用 floor(LOOP × ratio) 计算平移基数,再按 LOOP 取模 成段时间戳回绕带来的批式重排。

这里有一个很重要的细节:在 BATCH 模式中,OUT_OF_ORDER_RATIO 是计算平移基数的输入,而不像 POISSON 那样直接作为逐条抽样概率。再加上取模的模数是 LOOP,实际写入的时间戳序列与“乱序点恰好占 ratio”并不应简单画等号。使用 BATCH 时尤其要导出或抽样检查时间戳序列。

5. 两个容易让乱序失效或变复杂的开关

第一,IS_RECORD_CURRENT_REALLY_TIME=true 时,SingletonWorkDataWorkLoad 会直接以 System.currentTimeMillis() 作为记录时间戳,并不调用 getCurrentTimestamp(stepOffset)。因此,基于 IS_OUT_OF_ORDER 的这套时间戳改写逻辑不会生效。要测试工具生成的乱序,应该保持该开关为 false。

第二,IS_REGULAR_FREQUENCY=false 会额外给每个时间戳加入随机的子 POINT_STEP 偏移。它可以用于不规则采样数据,但会把“采样不规则”与“乱序”叠加。第一次定位乱序代价时,建议使用 IS_REGULAR_FREQUENCY=true,先把两类变量分开。

6. 一套更可靠的配置与验证流程

先固定数据规模、并发、批大小、写入间隔、POINT_STEP 和数据库版本,仅改变乱序相关配置。

1
2
3
4
5
6
7
8
9
10
11
# 先做顺序写基线
IS_RECORD_CURRENT_REALLY_TIME=false
IS_REGULAR_FREQUENCY=true
IS_OUT_OF_ORDER=false

# 再开启乱序;其余负载参数保持不变
IS_OUT_OF_ORDER=true
OUT_OF_ORDER_MODE=POISSON
OUT_OF_ORDER_RATIO=<从较低比例开始>
LAMBDA=<按目标迟到跨度设置>
MAX_K=<按目标迟到跨度设置>

推荐按以下顺序进行:

  1. 跑顺序写基线,记录吞吐、P99、失败数和服务端资源。
  2. 选择 POISSON,从较低 OUT_OF_ORDER_RATIO 开始扫描;同时抽样确认迟到时间差是否接近预期。
  3. 在相同负载下切换 BATCH,检查回绕后的时间戳序列,而不是只看配置文件。
  4. 只有当两种模式的时间戳形态都被验证后,再比较吞吐、P99、失败、CPU、内存、磁盘和查询结果。

对每轮测试,至少保存配置快照、版本号、原始日志、抽样时间戳以及数据核验结果。乱序写入的吞吐高,不代表时间戳、数值和写入数量已经正确落库。

7. 三个常见误区

第一,把 OUT_OF_ORDER_RATIO 当作所有模式下的精确乱序比例。在 POISSON 中它是逐点抽样概率;在 BATCH 中它参与计算平移基数。两者的代码语义不同。

第二,把 BATCH 直接理解为业务补传。它实现的是 batch 式时间戳重排,不会描述设备离线时长、缓存积压量或恢复过程。

第三,开启真实当前时间后还期待乱序生效。IS_RECORD_CURRENT_REALLY_TIME=true 绕过了乱序时间戳生成路径。

8. 小结

IoT Benchmark 的乱序逻辑可以概括为:先由 stepOffset 生成记录,再在生成时间戳时按 POISSON 或 BATCH 改写该 offset。POISSON 是逐点概率抽样加随机回退;BATCH 是按 LOOP 的确定性循环平移。

理解这层实现后,测试的重点就很清楚:固定其他负载变量,验证真实生成的时间戳形态,并把“配置参数”与“实际落到数据库的乱序序列”分开记录。这样得到的结果,才真正能说明系统面对哪一种乱序压力会付出多大代价。

系列文章

  1. 第一篇:数据库基准测试工具是什么
  2. 第二篇:从一次时序数据库写入测试开始
  3. 第三篇:如何模拟线上写入负载
  4. 第四篇:如何读懂测试结果波动
  5. 第五篇:什么是读写混合负载
  6. 第六篇:如何模拟线上读写混合负载
  7. 第七篇:怎样做一场公平的性能对比
  8. 第八篇:怎样找到并发与批大小的合理区间
  9. 第九篇:如何模拟乱序写入
  10. 第十篇:不同写入接口该怎样比较
  11. 第十一篇:性能测试与正确性验证有什么区别
  12. 第十二篇:单机测试与集群测试有什么不同
  13. 第十三篇:怎样保存和复盘一轮测试
  14. 第十四篇:一份可复用的基准测试方案模板

基准测试工具之你不知道的两三事 | 9.如何模拟乱序写入
https://spricoder.github.io/benchmarking/benchmarking-09/
作者
SpriCoder
发布于
2026年7月18日
许可协议