本次分享来自社区贡献者田原 。
随着物联网的普及和工业技术的不断发展,高效管理海量时间序列的需求越来越广泛,数据量越来越庞大。时间序列主要分为两种,即单元时间序列和多元时间序列。单元时间序列是指一个具有单个时间相关变量的序列,单元时间序列只包含一列时间戳和一列值。多元时间序列是指一个具有多个时间相关变量的序列,多元时间序列包含多个一元时间序列作为分量,各个一元时间序列的采样时间点相同,所以数据可以用矩阵形式表示,每行为一个时间点,每列为一个一元时间序列。
1
时序数据库分类
Time Series Database
当前主流的时序数据库存储引擎大多数都只支持一种时间序列模型(单元或多元),我们可以按照时序数据库是单元时间序列存储引擎,还是多元时间序列存储引擎对其进行分类
单元时间序列存储引擎
该存储引擎将每个时间序列独立存储,写入物理磁盘的时候,会对应两列数据,一列是时间戳列,一列是值列,两者一一对应。这种存储引擎适用于各个传感器独立采集的场景每个传感器采集的数据具有独立的时间戳。
基于已有的键值数据库构建的时序数据库基本都属于这一类,如 KairosDB 和 OpenTSDB 等。还有部分原生时序数据库的存储引擎也属于这一类,如 InfluxDB 和 Prometheus 等。
0.12及以前版本的 Apache IoTDB 的存储引擎和文件格式也只支持单元序列,无法高效的存储和查询多元时间序列。
多元时间序列存储引擎
该存储引擎将多个时间序列共享存储一列时间戳列,此外,每个时间序列再单独存储一列值列。一列时间戳列对应多个值列。这种存储引擎适用于多个传感器同时采集的场景,如在实际生产环境中,数据的收集粒度是设备级别的,一个设备下的多个传感器的值对应同一个时间戳。
基于已有的关系型数据库构建的时序数据库基本都属于这一类,将一个设备下所有序列建模成一张表,时间列只存在一列,典型的像 TimescaleDB 。少部分原生时序数据库的存储引擎也采用多元时间序列模型构建存储引擎,如 TDengine。
最近也有一款基于 Prometheus 开发的多元时间序列存储引擎 Heracles ,该存储引擎目前只是一个原型系统,并未合入 Prometheus主代码分支。除了减少时间戳冗余存储和查询效率外, Heracles 的论文中还提到, Prometheus 写入时会对每个时间序列挨个加锁,且存在于写入流程的关键代码路径上,当写入的多元时间序列分量很多时,这部分加锁的开销对于写入性能的影响也不可忽略。
2
Apache IoTDB 双存储引擎
Storage Engine
Apache IoTDB 从0.13版本开始,创新定义了时序数据库双存储引擎,内置两款高效的存储引擎:支持单元时间序列的非共享时间戳存储引擎和支持多元时间序列的共享时间戳存储引擎。
双存储引擎定义
从整个数据库管理系统的整体架构来看,存储引擎向上对接查询引擎,为查询引擎提供标准化的数据访问格式,向下对接存储介质,按照文件格式规定的数据组织,以数据页或其他单元为粒度,通过存储介质提供的特定接口,对存储介质中的数据完成读写操作。
单元与多元时间序列业务场景分别对时序数据库的存储引擎提出了不同的要求,所以我们在 Apache IoTDB 中支持了两个存储引擎来分别满足单元与多元时间序列的不同业务诉求。下图是 Apache IoTDB 双存储引擎整体架构的示意图,这里的双存储引擎主要区别在于设备下的序列是否共享时间列,原有的不共享时间戳存储引擎是适合单元序列,而新增的共享时间戳存储引擎为多元序列做了相应优化。因为是否共享时间戳列的差异,两个存储引擎在与查询引擎交互的结果集格式、内存表、持久化的排序阶段和持久化的编码方式都有显著差异。但是得益于良好的抽象,在元数据管理器以及缓存管理器上,两者是共享的。甚至在底层的文件格式上,也是用的一套双存储引擎混合文件结构,实现了在一个 TsFile 中能同时混合存储单元时间序列与多元时间序列。
双存储引擎数据模型设计
将两种存储引擎融合到一个数据库中,首先遇到的问题就是如何兼容原有的数据模型,以及如何让用户指定使用哪种存储引擎。所以我们在创新元数据模型,使得用户可以通过 API 指定某些多元时间序列共享时间戳的同时,依然要保证多元时间序列兼容原有数据模型的语意。
存储引擎的指定粒度可以放在存储组级别,但是这样会导致这个存储组下要么都是多元时间序列,要么都是单元时间序列,用户在使用灵活度上就会有限制。考虑到多元时间序列都是在一个设备下的,所以要么这个设备下的所有序列是共享一列时间戳的,要么是非共享时间戳的。所以我们将设置存储引擎的粒度放在了设备上,如下图所示,这样在同一个存储组中可以同时拥有多元时间序列和单元时间序列,在元数据树的设备节点用布尔变量标识该设备下的序列是否共享时间戳列,即该设备下的序列是否是多元时间序列。
对于多元序列,我们新增了 ALIGNED 关键字,用以标识某个设备下的时间序列是多元时间序列,共享一列时间戳。如下图所示,我们分别使用主动创建和自动创建两种方式,为 root.ln.wf01.GPS 这个设备创建了latitude和longitude组成的多元序列。这两种语法在元数据树上创建出的设备节点,其是否共享时间戳的属性为true。
3
性能对比
Performance Comparison
写入性能与磁盘占用对比
为了测试多元时间序列在具有不同分量数量时,共享时间戳存储引擎的写入持久化性能提升以及磁盘空间的节省程度,我们分别测试了具有1个分量、10个分量、30个分量以及100个分量的多元时间序列。时间序列的分量类型都采用long类型,取值与对应的时间戳相同,任意两个相邻时间戳的间隔为1ms,时间戳的起始值从1646134492000开始。这组实验中,每个分量都写入10,000,000点,并且在每个时间戳下,多元序列的每个分量都有值,即所有多元时间序列的空值比例为0%。
如下图所示,整体上看,在分量数超过1时,多元时间序列的持久化速度平均要比单元时间序列快1.6倍。
在磁盘占用方面,如下图所示,当多元时间序列下只有一个分量时,因为多元时间序列的存储方式会比单元时间序列多存储时间列各种粒度的统计信息,以及分量值列的空值信息,所以单元时间序列的存储方式在只有一个分量的情况下,会比多元时间序列的存储方式少占用1%的磁盘空间。但是当分量数量超过1时,如分量数分别为10、30和100的情况下,因为多元时间序列的存储方式只存储一列时间戳,相比于单元时间序列的存储方式,分别少存储了9、29和99列时间戳,因为实验中所有值列与时间列写入了相同的数值,并且采用相同的编码方式,多元时间序列大约比单元时间序列少占用50%的磁盘空间。
查询性能对比
时序数据库的查询场景很丰富,但是总体上分为两种:第一种是原始数据查询,返回序列写入的原始点,通过where子句中是否含有值过滤条件,又细分为不带值过滤的原始数据查询和带值过滤的原始数据查询;第二种是降采样查询,将一段时间内的原始数据做某种运算后返回。我们固定多元时间序列的分量数为30,在上述三种查询场景下,对比多元时间序列和单元时间序列的查询性能差异。
不带值过滤的原始数据查询
不带值过滤的原始数据查询时长与其查询的序列数相关,序列数越多,从磁盘读取的数据量也就越大,如果是单元时间序列,还需要对多个序列做时间戳的对齐操作。每次查询30个分量中的所有分量,查询sql类似于“select * from root.**”,在查询的分量数进一步增多后,多元时间序列查询性能的优势被进一步放大,因为可以比单元时间序列少从磁盘中读取更多的时间列,并且少做更多值列的按时间戳对齐操作。如下图所示,不带值过滤的全分量原始数据查询场景下,多元时间序列比单元时间序列平均快62.2%。
带值过滤的原始数据查询
带值过滤的原始数据查询效率与查询选择率有关,选择率是指满足该条查询过滤条件的结果集占总数据量的百分比。我们分别在90%、50%和10%这三种选择率下,对包含30分量,且空值比例分别为0%、10%以及50%的数据集上进行实验。
在查询每次涉及30分量中的所有分量时,如下图所示,在选择率为90%时,多元时间序列比单元时间序列平均快34.8%;在选择率为50%时,多元时间序列比单元时间序列平均快30.1%;在选择率为10%时,多元时间序列比单元时间序列平均快4%。当查询的分量数进一步提高到30时,在各种选择率和空值比例组合条件下,多元时间序列的查询性能平均是单元时间序列的1.23倍。与15分量的查询类似,如果我们只看90%和50%选择率以及对应的空值比例为0%和10%的实验结果,多元时间序列在全分量的查询性能提升平均能达到40%。
降采样查询
降采样查询是指使用比数据采集的时间频率更低的频率进行的一种查询方式,是聚合查询的一种特例。例如,数据采集的频率是一秒,想按照1分钟对数据进行展示,则需要使用降采样查询。在 IoTDB 中,可以使用 GROUP BY 子句来进行时间区间分段聚合,支持根据时间间隔和自定义的滑动步长(默认值与时间间隔相同)对结果集进行划分,默认结果按照时间升序排列。本次实验中我们指定聚合窗口为5000ms,使用的聚合算子为count,查询sql类似于“select count(*) from root.** group by([1646134492000, 1646144492001), 5000ms)”。
如下图所示,在查询涉及所有30个分量时,多元时间序列比单元时间序列快约15%;查询涉及15个分量时,多元时间序列比单元时间序列快约10.9%;只查询一个分量时,多元时间序列比单元时间序列慢约6%。
4
总结
Summary
通过上述实验可以看出,Apache IoTDB提出的双存储引擎各有其应用场景:
1
在单分量的场景下,将序列建模成单元时间序列,使用非共享时间戳存储引擎比共享时间戳存储引擎的写入持久化速度要快,磁盘占用量也会少,并且查询性能也要略微优于后者。
2
当分量数大于1且空值比例较低时,将序列建模成多元时间序列,使用共享时间戳存储引擎比非共享时间戳存储引擎的写入持久化速度要平均快1.6倍,磁盘空间占用上也会减少接近一半。
3
将序列建模成多元时间序列,使用共享时间戳存储引擎写入数据后,在各种查询场景下,只要查询涉及的分量数大于1,多元时间序列的查询性能均优于单元时间序列;即使只查询单分量时,多元时间序列也只略微逊色于单元时间序列,平均慢约10%。
关于我们
Apache IoTDB——海量时序数据管理的最佳解决方案,一款高吞吐、高压缩、高可用、物联网原生的开源时序数据库。从0到1自研时序存储方案、物联网数据模型、低流量数据传输方案,使得纳秒级采样数据写入无压力、TB级数据查询毫秒级、数据存储无损压缩数十倍。核心技术源自清华、自主可控。目前已在国家电网、国家气象局、中航成飞、中核集团、长安汽车、金风科技等企业广泛应用。