背景


TsFileResource 会随着数据写入不断生成,当 TsFileResource 中的时间索引粒度较细,单个 TsFileResource 可能包含数十万设备的时间索引条目,大小可到几十M,在有限内存管理海量设备时,将 TsFileResource 全部驻留内存有 OOM 风险。

此外,在系统重启时,所有的TsFileResource都需要load到内存中一遍。


因此,TsFileResource 不能全部驻留在内存中,需要设计以下内容:

结构设计:

  • TsFileResource 和 TsFile 的对应关系?(1对1,1对多)
  • TsFileResource 的时间索引粒度?

缓存机制设计:

  • 将哪些 TsFileResource 放在内存里?
  • 内存中的 TsFileResource 何时踢出?
  • 查询时如何将未缓存在内存的 TsFileResource 加载进内存?

前提要求

现有的TsFileResource中有过多的冗余字段,有些字段只对于未封口的TsFileResource起作用

将 TsFileResource 抽为接口,两个实现类,ClosedTsFileResource,UnclosedTsFileResource


下面的方案只考虑对于ClosedTsFileResource的缓存管理,默认内存中保留所有的UnclosedTsFileResource

方案1 & 方案2


总体目标

  • 引入一个全局的TsFileResourceManager,管理TsFileResource,将最近的 TsFileResource 缓存在内存,踢出老文件


当前TsFileResource的层级关系

         存储组 → 虚拟存储组 → 分区号 → List<TsFileResource> 


方案1


每关闭一个 TsFileResource 时,计算其内存,内存达到设置的阈值后,将最不活跃的分区的整个 List<TsFileResource> 清空


当需要一个分区的文件时,到磁盘上 listFiles,将该分区加载进来



优点:以分区作为缓存管理的粒度,缓存管理的开销小,分区被驱逐后,内存中不需要保留过多信息,只需要把原有的Map<PartitionID, List<TsFileResource>>中所驱逐分区对应的value置成null即可,若该分区再次被使用,只需根据所处存储组名、分区id得到分区文件夹路径,从文件夹中获得所有tsfile resource文件,加载进内存即可

缺点:必须开启分区,当分区数为1时,无法奏效


方案2


主要思想:假设使用 FILE_TIME_INDEX 没有内存问题。根据内存动态切换 FILE_TIME_INDEX 和 DEVICE_TIME_INDEX。


踢出一个 TsFileResource 的操作为:将 TsFileResource 中的 ITimeIndex 改为 FILE_TIME_INDEX,重新加载一个TsFileResource的操作为将 TsFileResource 中的 ITimeIndex 改为 DEVICE_TIME_INDEX

注意,当前的FILE_TIME_INDEX的实现有点问题,不应该存Set<String> devices



优点:不开时间分区依旧能够奏效

缺点:以单个TsFileResource作为缓存管理的粒度,缓存的开销比方案1要大


可以考虑实现这种方案,TsFileResourceManager需要实现的接口为

  1. 标记某一批TsFileResource被读取了
  2. 添加新的TsFileResource,例如创建和merge
  3. 升级一批TsFileResource.(什么条件升级)
    1. 如果因为索引问题导致的误读而访问tsfile,那还是要升级,因为老是误读,因此升级是合理的。
    2. 为了防止全表扫描污染缓存,则需要加点逻辑来防止这种情况。例如如果一个文件如果是文件级的索引,在一段时间内被访问次数超过一个阈值就升级它。
  4. 移除旧的TsFileResource,例如删除和merge

TsFileResourceManager的主要逻辑:

  1. 初始化,从磁盘中按时间戳逆序来构建TsFileResource缓存
  2. 升级TsFileResource,从磁盘中读取TsFile构建设备级索引
  3. 降级TsFileResource,将内存中的TsFileResource的索引改成文件级
  4. 筛选TsFileResource进行升级或降级
    1. 升级,上层调用标记TsFileResource被访问后,检查这些TsFileResource如果是文件级的索引,并且在一段时间内被访问次数超过一个阈值就升级它。
    2. 降级,添加新的TsFileResource或者标记某些TsFileResource被访问后,替换掉最近最少使用的设备级的TsFileResource

TsFileResourceManager的主要辅助成员:

  1. LRU cache,不对外公开,存放的都是设备级的TsFileResource,主要用来判断哪些最近被访问,
  2. Map<TsFileResource, Pair<lastCheckTime, queryCount>> waitUpgradeTsFileResource, 不对外公开,key是文件级索引的TsFileResource, value是一个数据结构,该结构主要有两个字段,lastCheckTime是该TsFileResource上次被访问的时间,超过某个阈值(例如1min)就重新设置为当前时间,queryCount也清零。queryCount是自上次被访问的时间以来被访问的次数。筛选时,如果某个文件级的TsFileResource自上次被访问时间以来,queryCount累计超过阈值(例如5),就升级它。
    1. 有可能某段时间所有的TsFileResouce都访问得比较频繁,设备级的的TsFileResource比文件级的TsFileResource更频繁,但是依然会被替换掉。


方案1 & 方案2有个共同的优点,可以复用MTree那边缓存的实现逻辑

方案3


保持现有的 TIME_INDEX 不变,每个存储组保留固定个 TsFileResource,当总内存超过阈值,就将 TsFileResource 合并。可以应用多种合并策略。


例:

File1

root.sg.d1.s1, 1, 10

root.sg.d1.s2, 1, 10


File2

root.sg.d2.s1, 1, 10

root.sg.d1.s2, 11, 20


合并后:

File1, File2

root.sg.d1.s1, 1, 10

root.sg.d2.s1, 1, 10

root.sg.d1.s2, 1, 20




  • No labels

1 Comment

  1. Current TsFileResource.java 有内存overhead,首先要精简、共用(File,factory,version, random,List),besides,方案1、2需要内存和disk的替换,方案3也会需要,而且改变了tsFileResource和tsFile一对一的关系,小心设计