现状描述
目前分布式原始数据不带值过滤的查询延迟相比单机增加了几十上百倍,因此需要分析查询延迟大幅度增加的具体原因并给出解决方案。
原因分析
现在分布式模块在处理不带值过滤条件的原始数据查询时,会通过迭代消费batchData
的方式在协调者节点做整合迭代返回查询结果给客户端,此外,当前获取batchData
都是针对单条时间序列获取的。
在单机场景下,目前的实现是对于查询到的每个时间序列都会创建一个 reader,该 reader 会保存所有相关数据的 tsfile,并在其中不断 seek 和 read 来读取该时间序列的数据,接着做一些处理。庆幸的是,单机版针对不带值过滤的原始数据查询做了并行的优化,即不同时间序列的 reader 可以并行的去磁盘上读取文件,这一定程度上能够显著提升性能。当然,目前每个时间序列一个 reader 的方式不可避免的是会产生较多的随机读,这也是我们的查询性能在 SSD 上比在 HDD 上性能好的重要原因之一。
在分布式场景下,一次查询涉及到的多条时间序列的数据可能部分在协调者节点也可能部分在远程节点。对于前者,其查询逻辑和单机一样,可以并行的去本地 tsfile 中查,对于后者,目前的实现是会为每个远端的时间序列创建一个 RemoteSimpleSeriesReader
,其维护了一个 batchData
,由协调者节点这边不断查询消费,一旦发现被消费完了,就向远端的数据节点发送一次 rpc 请求让其生产一个batchData
过来。当然,RemoteSimpleSeriesReader
获取batchData
时复用了单机的并行逻辑,使得协调者节点可以并行的发送 rpc 来获取不同时间序列的 batchData。
目前查询并行线程池的 coreSize 和 maxSize 都是 cpu 核心数个,即最大并行度为 cpu 核心数个。假设从磁盘上单线程查询单条时间序列产生一批 batchData 的耗时平均为 t0,如果在一个 4 核的机器上查询 100 个时间序列的数据,要想为所有的时间序列都拿到一批 batchData,由于线程池并行度最大为 4,则至少需要 25* t0 的时间才能完整生产一批 batchData 出来。在分布式场景下,由于 reader 每拿到一批 batchData 就需要发送一次 rpc 而且也需要对应的数据节点执行一次磁盘 IO 后才能返回,即发送一次 rpc 即走一轮 RTT 的代价为 t1,则需要 25 * (t0 + t1) 的时间才能走完一轮。此外,由于单机目前只对不带值过滤条件的原始数据做了并行优化,在面对其他查询时,不同时间序列的执行都是串行执行的,所以单机的延时会是 100*t0,分布式的查询会是 100*(t0+t1)。
可以看到,在分布式只复用单机并发逻辑的情况下,其理论延迟至少都是单机版延迟的 (1 + t1/t0) 倍,其实这一定程度上没有对网络资源进行有效的利用。对于单机的 reader 来说,由于其既要做磁盘 IO 又要对数据做处理,并行度不能太高,然而对于网络请求来说,其并不很耗费 cpu 资源,完全可以为发送 rpc 的操作另外使用一个并行度更高的线程池,比如如果将并行度调高到 100,则就可以同时发送 100 个时间序列的 rpc 请求,则对于可并行的查询耗时理论就减少为了 t1 + 25*t0,对于不可并行的查询则查询耗时理论就减少为了 t1+100*t0。因此,我们可以为 rpc 请求抽象出一个并行度更高的解决方案,比如使用并行度更高的线程池,当然如果觉得线程资源太昂贵可能也能够使用 thrift 的异步客户端模式,也能够一定程度上增大并行度,不过总体来说,这种不优化 rpc 个数的优化最终会有一定瓶颈上限,比如如果一次查询 30 个时间序列一起做了并行那可能能够总耗时为 t1,但如果一次查询几十万个序列则做了并行总耗时也不会是 t1,可能压力都会落在 rpc 框架这里而导致性能无法线性提升。
因此也可以考虑将不同时间序列的 batchData 通过一次 rpc 来共同维护。这样做的好处有三点:
- 将原本并行发送的 rpc 合并成了一条 rpc,能够少同步一些 thrift 和 tcp 连接等协议方面的头尾字节等等,传输相同的业务数据但在链路层需要传输的真实数据更少。
- 对于单机做了并行优化的查询,可以最大程度利用网络资源,即所有相关的数据只需要一次 rpc 即一个 RTT 既可以得到,不再像之前一样会被线程池的并发数所限制。即对于上例,其延时应该在 t1 + 25*t0 附近。
- 对于单机尚未做并行优化的查询,可以隐形的达到并行的效果。对于上例,延时应该在 t1 + 100*t0 左右,当然如果之后单机做了并行优化,那可能会降低到 t1 + 25*t0 左右。
因此就目前而言,可以考虑先做批量优化将分布式对网络的利用率提升起来,然后单机再做并行优化降低延迟,此时分布式的延时也会随着单机的延时降低而降低。
当然,如果分布式开启了时间分区,则能够将不同节点上的 IO 操作并行起来,理论上分片较多时性能可能还会超过单机。
此外,如果对于查询负载较多的场景,如果单机的 IO 成为了瓶颈,则在分布式场景下,由于可以并行利用多块磁盘的 IO,性能相比繁忙的单机可能还有一定上升。
解决方案
对于同一时间分区的数据,其分布都存在于一个数据组(Raft 中的 Data Group)中。因此对于一个查询,可以将其涉及到的时间序列根据时间分区进行分组,然后对于一个数据组内时间序列的数据,使用批量创建批量 fetch 的方式,这样即可以更大程度的利用网络资源,减低网络 IO 对延迟的影响。
定一个阈值,比如 80%。记录每个 seriesReader 的 batchData 消费进度,下一次有时间序列触发拉取 batchData 时,统计同一数据组的所有时间序列的消费进度,消费进度小于 80% 的seriesReader 不触发拉取,消费进度大于 80% 的 seriesReader 触发拉取,即建一个 bitmap,每次发送请求时传输。