性能大揭秘(之一)!CirroData-TimeS时序数据库的文件存储格式

2022-01-10 00:00:00 索引 数据 时序 时间 设备


时序数据的特征




时序数据具有如下特征:

  • 数据是时序的,即一定带有时间戳。

    由于时序数据是某个测点,或是某个事件源持续不断的产生的,产生数据的时间点对于追溯、分析这个时间下测点,事件的状态非常重要,所以时序数据一般都带有时间戳,这个是时序数据大的特征。

  • 按时间范围读取

    通常来说,一般不会关心某个特定点的数据,而且关注一段时间的数据,关注这段时间之内的数据的走势。

  • 聚合查询价值高

    用户会对某段时间之内某些测点的一些聚合值感兴趣,比如count、avg、max、last_value、first_value等。

  • 时间序列多大千万级甚至亿级别

    由于入网设备的增多,测点数量增加,时间序列就会越来越多,如何管理和存储这些时间序列元信息,也是将要面临的挑战。

  • 数据量大,存储成本高

    由于测点数据是不间断持续写入的,随着入网设备逐渐增加,数据量将会越来越大,会占用大量的磁盘空间,这就要求时序数据库具有较高的压缩比,能够节省磁盘空间。





文件存储方式




文件存储格式主要有两类:行存和列存。

假设有如下数据。



1、行存

行存如下图所示:

  • 每行数据连续存储

  • 行和行之间顺序存储

  • 典型的代表有Apache Avro


2、列存

列存如下图所示:

  • 每列连续存储

  • 列和列之间顺序存储


3、行列式存储

还有一类是行列式存储,实际上是对列式文件存储格式的改进,如下图所示:

  • 以两行为一个存储单元对数据进行切分

  • 每个存储单元内按列存储

  • 典型的代表有Apache Parquet,Apache ORC





文件存储格式选择





行存储的写入是一次性完成,消耗的时间比列存储少,并且能够保证数据的完整性,缺点是数据读取过程中会产生冗余数据,如果只有少量数据,此影响可以忽略;数量大可能会影响到数据的处理效率。


列存储在写入效率、保证数据完整性上都不如行存储,它的优势是在读取过程,不会产生冗余数据,这对数据完整性要求不高的大数据处理领域,尤为重要;并且压缩效果好,能降低存储成本。


而时序数据的特性是写多读少,量大,对完整性要求不高,读取模式更多的是对某些列某段数据的读取,因此其更适合列式存储格式。





列式时序数据存储格式TsFile




TsFile(Time Series File)是CirroData-TimeS的数据文件存储格式,类似于Parquet的文件结构。分析TsFile文件格式之前,先来看下真实环境中时序数据的格式。  

如下图所示,一般工厂内有多个设备(device),每个设备会有多个传感器(sensor),每个传感器会采集某种指标(measurement)。



1、数据存储方式

  • 同一个设备(device)下的数据好存放在一起,因为我们习惯查询一个设备的一些信息关联分析设备的情况;

  • 同一种指标(measurement)的数据好放在一起,因为数据量会比较大(7x24小时产生),需要考虑压缩效率,同一类数据相似度比较高,有利于压缩;

  • 数据好按时间序来存储,因为指标数据采集是源源不断的,用户查询时一般也会有监控近一段时间,或者某段时间区间数据的需求,按时间序排序能加快查询速度。


因此如下图所示,对数据进行抽象如下:

  • 一个设备(device)下的数据隶属于一个ChunkGroup;

  • 而每个指标(measurement)的数据存储为一个Chunk;

  • 每个Chunk内数据按时间序排序,每段时间数据为一个Page。



这里把一个chunk的数据拆成多个Page是有好处的,提供更细粒度的存储抽象单元可以降低读取冗余数据的数量。


2、索引存储方式

存储完数据后,为了加快查询,一般需要建立索引,而索引越细查询越快。根据上面的数据划分,细粒度的是Page,但是如果以Page为粒度做索引,索引的开销会很大,毕竟数据量过大,会导致大量的Page产生,索引将变得非常大,查询时如果索引无法被加载进内存,那查询效果会大打折扣。


一个比较直观的做法就是以Chunk为粒度来构建索引。因此索引树就有三层。

  • 某个设备(device)的所有数据的索引根;

  • 某个文件多次刷写设备(device)数据的索引,因为一个设备(device)的数据可能会多次刷写到一个文件里,因此其数据会分布在文件的多个位置里,需要给这每个部分加一层索引;

  • 某个指标(measurement)的数据索引,因为一个设备(device)的数据里会包含多个传感器的数据,因此还需要给每个指标的数据加一层索引。


如果将一个设备(device)的某段时间的数据索引及其下的指标(measurement)的索引可以放在一起,查询时可以一并读出,可以减少一层索引的查找。


终索引树如下图所示:

  • 所有设备(device)的索引根按设备id顺序存放,其指向其数据的二级索引起始处;

  • 某个设备(device)的所有数据的索引按时间序存放,每段时间区间的数据实际上是分散在文件的多个位置;

  • 设备(device)数据索引内包含多个指标(measurement)的数据索引,按指标id顺序存放。



3、文件格式

如下图所示,TsFile文件格式分为几大块:

  • 文件头(magic number)

  • 数据部分

  • 元数据部分(索引部分)

  • 文件尾(magic number)



3.1 文件头&文件尾

都是magic number,共12字节TsFilev0.8.0。


3.2 数据部分

  • ChunkGroup

    代表一个设备(device)的一段时间的数据,包含一系列Chunk,末尾还有一个ChunkFooter(用来记录deviceId信息);

  • Chunk

    代表一个传感器一段时间的数据,包含一个ChunkHeader和一系列Page;

  • Page

    代表一个传感器一小段时间的数据,包含一个PageHeader和一系列点数据。其中点数据包含是时间列和数值列,一一对应。


3.3 元数据部分

  • ChunkGroupMetaData

    每个设备(device)某段时间的数据的索引,会有多个,每个下面会包含多个ChunkMetaData。

  • ChunkMetaData:某个传感器某段时间的数据索引。

  • FileMetadata

    整个文件的元数据信息,包括设备(device)数据索引的根,指标(measurement)的元数据信息,以及一些统计数据。

  • DeviceIndexMetadata:某个设备(device)的索引的根。

  • MeasurementSchema

    当前文件的所有指标(measurement)的元数据信息。


更详细的元数据和数据信息可以看参考2。


4、优化

读取的流程如下:

  • 读取文件元数据信息;

  • 二分查找deviceIndexMetadata,找到对应的device的ChunkGroupMetadata;

  • 读取该device下所有的ChunkMetaData到内存中;

  • 根据ChunkMetaData读取对应的数据。


实际生产过程中会遇到一个设备有几十万的传感器的情况,此时会加载大量与当前查询无关的ChunkMetaData信息,对查询性能也会有巨大的性能开销。


如何进行优化?我们会在下篇关于时序数据库CirroTimeS的文章中再继续介绍新的改进。


备注:

CirroData-TimeS时序数据库是基于开源Apache IoTDB改进、东方国信深度参与的国产时序数据库。CirroData-TimeS时序数据库团队有2名Apache IoTDB Committer。  

参考文献

  • [1] 处理海量数据:列式存储综述(存储篇)

    https://zhuanlan.zhihu.com/p/35622907

  • [2] Hierarchy

    http://iotdb.apache.org/UserGuide/V0.8.x/7-TsFile/3-Hierarchy.html

来源:https://mp.weixin.qq.com/s/jfXb3OL1_HWet3QaYd0oVQ

相关文章