实验背景

在cache满的情况下,会造成查询“假死”的现象,查询会变的很卡。关掉cache后,查询会变慢,但是并不会出现“假死”的现象,总可以得到查询结果,速度要优于开cache。

实验配置

写100个文件,每个文件中有10个device和,每个device有1000个sensor,每个sensor写一个点flush一次,所以,第i个文件内所有sensor的时间范围是[i, i +1)。

一个timeseriesmetadata大约360B,所以对于某一个device而言,总的timeseries metadata的大小为 文件数 * sensor数 * 360B,即100 * 1000 * 360B,约为36MB。

执行如下sql:

select sum(*) from root.sg1.d0


实验分支

TimeSeriesMetadataCache


实验分析

现在的聚合查询逻辑是按照sensor进行迭代查询,一个sensor查询完之后,才会开始另一个sensor的查询,但是在读一个sensor在某个文件中的TimeSeriesMetadata时,为了减少磁盘IO以及反序列化的次数,我们会将该device下此次查询涉及的所有sensor的TimeSeriesMetadata都预读出来,存进cache中。当cache大小并不能存储下所有文件的所有sensor的TimeSeriesMetadata时,这些预读操作都是浪费,因为即使预读存进cache后,还是会因为cache满了之后,被踢出,反而多了很多次put操作。

我们可以再深入分析一下:

  1. 在cache不满时,如果进行预读操作,对于一个sensor来讲,因为会预读出另外的999个sensor的TimeSeriesMetadata,并且当遍历到下一个sensor时,之前预读的TimeSeriesMetadata早已被踢出去了,所以cache的命中率一直是0%,所以每个文件的每个sesnor会进行1000次map的put操作,所以总计进行了100,000,000次put操作(这put操作耗时也包含了,某次put操作后,cache满了,踢出部分缓存的耗时),以及100,000次反序列化TimeSeriesMetadata操作
  2. 如果我们不进行预读操作,每次读取只将对应的sensor的TimeSeriesMetadata放进cache中,那每个文件的每个sesnor会进行1次map的put操作,所以总计只进行了100,000次put操作,是上面的1/1000,以及100,000次反序列化TimeSeriesMetadata操作
  3. 而当我们关闭cache后,这100,000,000次的put操作耗时就都省去了,但是依然有100,000次反序列化TimeSeriesMetadata操作
  4. 若cache大于36MB,即能容纳该device下所有文件的所有sensor的timeseriesMetadata,遍历第一个sensor时,会进行100,000次put操作,将之后的999个sensor的timeseriesMetadata都缓存起来,遍历之后的sensor时,cache命中,所以不需要反序列化操作,即只进行了100次反序列化TimeSeriesMetadata操作。


实验场景一

开cache,分配给IoTDB总的内存100MB,读内存占3/10,即30MB,timeseriesMetadata占读内存的1/15,即2MB,也就是足够容纳5个文件的一个device的所有TimeSeriesMetadata。
耗时:36046ms


实验场景二

不进行预读操作,其余配置同实验一

耗时:21,619ms


实验场景三

关cache,其余配置同实验一
耗时:20,220ms


实验场景四

开cache,分配给IoTDB总的内存1000MB,读内存占3/10,即300MB,timeseriesMetadata占读内存的2/15,即40MB,也就是足够容纳100个文件的一个device的所有TimeSeriesMetadata。
耗时:6,177ms


实验验证

上面的四个场景,对应实验分析中的四个点,并且实验结果均符合实验分析,为了进一步验证这个结论,我们使用JProfiler对实际耗时进行统计,方便起见,我们选取场景一与场景二进行抽样

                                                               

                               场景一                                                                                                                                                    场景二

可以看到,场景一的耗时比场景二就是多在forEach那边,也就是多出来的99,900,000次put操作


验证GroupBy的执行流程

对于GroupBy查询,我们并不是迭代完一个sensor的所有区间,再去迭代下一个sensor,我们是对于一个groupby的时间窗口,迭代所有的sensor,所以如果是对于groupby查询而言,只要cache能够容纳一个时间窗口的所有sensor的TimeSeriesMetadata,就能保证cache命中率,不会出现上面的cache抖动现象。


同样的对于下面这样一个GroupBy查询

select last_value(*) from root.sg1.d0 groupby([0,100),10ms)

一个时间窗口包含了10个文件,每个文件1000个sensor,因为还需要再读一个文件,才能判断时间窗口的结束,所以按照分析,只要cache能够容纳11,000个TimeSeriesMetadata即可,即需要cache > 4.04MB,我们设置cache为4.1MB,执行上面这条查询只需要3,266ms,cache命中率99.9%.

验证了我们对于GroupBy查询流程的分析。


验证cache满后对后续查询的影响

当前一个查询将cache占满后,cache本身的最大size能满足下一个查询的需求,此时不应该对下一个查询产生影响。

设定cache为4.1MB

先执行select last_value(*) from root.sg1.d0 groupby([0,100),10ms)

耗时为2,293ms

此时cache已经占满,再执行select last_value(*) from root.sg1.d1 groupby([0,100),10ms),查询另一个device的

耗时为2,313ms

可以看到,确实没有影响。


  • No labels