基准测试工具之你不知道的两三事 | 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 后没有写入任何更新数据这批数据虽然迟到对这一序列仍可以是顺序写入。同一序列已写入 10:06后到数据为 10:03乱序写入同一序列最后写入 10:00后到数据依次为 10:01 至 10:05仍是顺序写入所以测试前要先写清楚哪些序列发生乱序、迟到点占多少、以及迟到点相对同序列最新点落后多少。不能只写“开启了乱序”。2. 代码中的一条记录怎样获得时间戳生成写入负载时SingletonWorkDataWorkLoad先用全局递增的insertLoop为设备批次分配位置再为 batch 内每条记录计算原始stepOffsetstepOffset (curLoop / DEVICE_NUMBER) * BATCH_SIZE_PER_WRITE batchOffset随后代码分别做两件事调用getCurrentTimestamp(stepOffset)生成时间戳调用generateOneRow(..., stepOffset)生成测点值。当使用等长时间戳IS_REGULAR_FREQUENCYtrue时时间戳可概括为timestamp START_TIMESTAMP × precision POINT_STEP × stepOffset POINT_STEP其中stepOffset是可能被乱序模式改写后的 offset。若关闭等长时间戳最后一个POINT_STEP会替换为一个随机的[0, POINT_STEP)内偏移。否POISSONBATCH全局 insertLoop原始 stepOffset开启乱序stepOffset 不变按概率减去随机 delta按 base 循环平移生成时间戳按原始 offset 生成测点值组成 Record 并写入这张图揭示了一个容易忽略的事实当前实现只改时间戳不改生成测点值所使用的原始 offset。因而它模拟的是“带有当前生成值的记录以较早时间戳抵达”的写入压力而不是从历史缓存中原样重放一条完整旧记录。若业务特别依赖“值与事件发生时刻”的严格对应关系应采用真实数据集模式或自行构造数据而不能把该生成模式直接当作历史回放。3.POISSON逐点决定是否后移在POISSON模式下每生成一条记录代码先以OUT_OF_ORDER_RATIO为概率抽样if random() OUT_OF_ORDER_RATIO: stepOffset stepOffset - poissonDelta() else: stepOffset stepOffsetpoissonDelta()由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不是“对每一条记录按概率抽一次”。它先计算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_TIMEtrue时SingletonWorkDataWorkLoad会直接以System.currentTimeMillis()作为记录时间戳并不调用getCurrentTimestamp(stepOffset)。因此基于IS_OUT_OF_ORDER的这套时间戳改写逻辑不会生效。要测试工具生成的乱序应该保持该开关为false。第二IS_REGULAR_FREQUENCYfalse会额外给每个时间戳加入随机的子POINT_STEP偏移。它可以用于不规则采样数据但会把“采样不规则”与“乱序”叠加。第一次定位乱序代价时建议使用IS_REGULAR_FREQUENCYtrue先把两类变量分开。6. 一套更可靠的配置与验证流程先固定数据规模、并发、批大小、写入间隔、POINT_STEP和数据库版本仅改变乱序相关配置。# 先做顺序写基线 IS_RECORD_CURRENT_REALLY_TIMEfalse IS_REGULAR_FREQUENCYtrue IS_OUT_OF_ORDERfalse # 再开启乱序其余负载参数保持不变 IS_OUT_OF_ORDERtrue OUT_OF_ORDER_MODEPOISSON OUT_OF_ORDER_RATIO从较低比例开始 LAMBDA按目标迟到跨度设置 MAX_K按目标迟到跨度设置推荐按以下顺序进行跑顺序写基线记录吞吐、P99、失败数和服务端资源。选择POISSON从较低OUT_OF_ORDER_RATIO开始扫描同时抽样确认迟到时间差是否接近预期。在相同负载下切换BATCH检查回绕后的时间戳序列而不是只看配置文件。只有当两种模式的时间戳形态都被验证后再比较吞吐、P99、失败、CPU、内存、磁盘和查询结果。对每轮测试至少保存配置快照、版本号、原始日志、抽样时间戳以及数据核验结果。乱序写入的吞吐高不代表时间戳、数值和写入数量已经正确落库。7. 三个常见误区第一把OUT_OF_ORDER_RATIO当作所有模式下的精确乱序比例。在POISSON中它是逐点抽样概率在BATCH中它参与计算平移基数。两者的代码语义不同。第二把BATCH直接理解为业务补传。它实现的是 batch 式时间戳重排不会描述设备离线时长、缓存积压量或恢复过程。第三开启真实当前时间后还期待乱序生效。IS_RECORD_CURRENT_REALLY_TIMEtrue绕过了乱序时间戳生成路径。8. 小结IoT Benchmark 的乱序逻辑可以概括为先由stepOffset生成记录再在生成时间戳时按POISSON或BATCH改写该 offset。POISSON是逐点概率抽样加随机回退BATCH是按LOOP的确定性循环平移。理解这层实现后测试的重点就很清楚固定其他负载变量验证真实生成的时间戳形态并把“配置参数”与“实际落到数据库的乱序序列”分开记录。这样得到的结果才真正能说明系统面对哪一种乱序压力会付出多大代价。系列文章第一篇数据库基准测试工具是什么第二篇从一次时序数据库写入测试开始第三篇如何模拟线上写入负载第四篇如何读懂测试结果波动第五篇什么是读写混合负载第六篇如何模拟线上读写混合负载第七篇怎样做一场公平的性能对比第八篇怎样找到并发与批大小的合理区间