性能大揭秘(之一)!CirroData-TimeS时序数据库的文件存储格式
时序数据的特征
时序数据具有如下特征:
数据是时序的,即一定带有时间戳。
由于时序数据是某个测点,或是某个事件源持续不断的产生的,产生数据的时间点对于追溯、分析这个时间下测点,事件的状态非常重要,所以时序数据一般都带有时间戳,这个是时序数据大的特征。
按时间范围读取
通常来说,一般不会关心某个特定点的数据,而且关注一段时间的数据,关注这段时间之内的数据的走势。
聚合查询价值高
用户会对某段时间之内某些测点的一些聚合值感兴趣,比如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
相关文章