InfluxDB的存储机制解析
InfluxDB的存储机制解析
InfluxDB作为一款流行的时序数据库,其独特的存储机制使其在处理大规模时序数据时表现出色。本文将深入解析InfluxDB的存储引擎演进历程、数据模型以及核心存储组件(WAL、TSMFile、TSIFile)的工作原理,帮助读者全面理解InfluxDB的存储架构。
InfluxDB的存储引擎演进
InfluxDB自发布以来,其存储引擎经历了多次重大变革。以下是其存储引擎的演进历程:
- 版本0.9.0之前:基于LevelDB的LSMTree方案
- 版本0.9.0~0.9.4:基于BoltDB的mmap COW B+tree方案
- 版本0.9.5~1.2:基于自研的WAL + TSMFile方案(TSMFile方案在0.9.6版本正式启用,0.9.5只是提供了原型)
- 版本1.3~至今:基于自研的WAL + TSMFile + TSIFile方案
InfluxDB在选择存储引擎时主要考虑以下因素:
- 时序数据在降采样后会存在大批量的数据删除,而LevelDB的LSMTree删除代价过高
- 单机环境存放大量数据时不能占用过多文件句柄,而LevelDB会随着时间增长产生大量小文件
- 数据存储需要热备份,而LevelDB只能冷备
- 大数据场景下写吞吐量要跟得上,而BoltDB的B+tree写操作吞吐量成瓶颈
- 存储需具备良好的压缩性能,而BoltDB不支持压缩
此外,出于技术栈一致性以及部署简易性的考虑,InfluxDB团队决定自研存储引擎。
InfluxDB的数据模型
在深入解析InfluxDB的存储引擎之前,先回顾一下InfluxDB中的数据模型:
- measurement:指标对象,也即一个数据源对象。每个measurement可以拥有一个或多个指标值,也即下文所述的field。在实际运用中,可以把一个现实中被检测的对象(如:“cpu”)定义为一个measurement
- tags:概念等同于大多数时序数据库中的tags, 通常通过tags可以唯一标示数据源。每个tag的key和value必须都是字符串。
- field:数据源记录的具体指标值。每一种指标被称作一个“field”,指标值就是 “field”对应的“value”
- timestamp:数据的时间戳。在InfluxDB中,理论上时间戳可以精确到纳秒(ns)级别
此外,在InfluxDB中,measurement的概念之上还有一个对标传统DBMS的Database的概念,逻辑上每个Database下面可以有多个measurement。在单机版的InfluxDB实现中,每个Database实际对应了一个文件系统的目录。
Serieskey的概念
InfluxDB中的SeriesKey的概念就是通常在时序数据库领域被称为时间线的概念, 一个SeriesKey在内存中的表示即为下述字符串(逗号和空格被转义)的字节数组:
{measurement名}{tagK1}={tagV1},{tagK2}={tagV2},...
其中,SeriesKey的长度不能超过65535字节。
支持的Field类型
InfluxDB的Field值支持以下数据类型:
Datatype | Size in Mem | Value Range |
---|---|---|
Float | 8 bytes | 1.797693134862315708145274237317043567981e+308 ~ 4.940656458412465441765687928682213723651e-324 |
Integer | 8 bytes | -9223372036854775808 ~ 9223372036854775807 |
String | 0~64KB | String with length less than 64KB |
Boolean | 1 byte | true 或 false |
在InfluxDB中,Field的数据类型在以下范围内必须保持不变,否则写数据时会报错类型冲突:
- 同一Serieskey + 同一field + 同一shard
Shard的概念
在InfluxDB中,能且只能对一个Database指定一个Retention Policy(简称:RP)。通过RP可以对指定的Database中保存的时序数据的留存时间(duration)进行设置。而Shard的概念就是由duration衍生而来。一旦一个Database的duration确定后, 那么在该Database的时序数据将会在这个duration范围内进一步按时间进行分片从而时数据分成以一个一个的shard为单位进行保存。
shard分片的时间与duration之间的关系如下:
Duration of RP | Shard Duration |
---|---|
< 2 Hours | 1 Hour |
= 2 Hours 且 <= 6 Months | 1 Day |
6 Months | 7 Days |
新建的Database在未显式指定RC的情况下,默认的RC为数据的Duration为永久,Shard分片时间为7天。
InfluxDB的存储引擎分析
时序数据库的存储引擎主要需满足以下三个主要场景的性能需求:
- 大批量的时序数据写入的高性能
- 直接根据时间线(即Influxdb中的Serieskey)在指定时间戳范围内扫描数据的高性能
- 间接通过measurement和部分tag查询指定时间戳范围内所有满足条件的时序数据的高性能
InfluxDB在结合了上述考量的基础上推出了他们的解决方案,即下面要介绍的WAL + TSMFile + TSIFile的方案。
WAL解析
InfluxDB写入时序数据时为了确保数据完整性和可用性,与大部分数据库产品一样,都是会先写WAL,再写入缓存,最后刷盘。对于InfluxDB而言,写入时序数据的主要流程如下:
InfluxDB对于时间线数据和时序数据本身分开,分别写入不同的WAL中,其结构如下所示:
- 索引数据的WAL:由于InfluxDB支持对Measurement,TagKey,TagValue的删除操作,因此索引数据的WAL会区分当前所做的操作具体是什么,它的WAL的结构如下图所示:
- 时序数据的WAL:由于InfluxDB对于时序数据的写操作永远只有单纯写入,因此它的Entry不需要区分操作种类,直接记录写入的数据即可
TSMFile解析
TSMFile是InfluxDB对于时序数据的存储方案。在文件系统层面,每一个TSMFile对应了一个Shard。
TSMFile的存储结构如下图所示:
其特点是在一个TSMFile中将时序数据(i.e Timestamp + Field value)保存在数据区;将Serieskey和Field Name的信息保存在索引区,通过一个基于Serieskey + Fieldkey构建的形似B+tree的文件内索引快速定位时序数据所在的 数据块
注:在当前版本中,单个TSMFile的最大长度为2GB,超过时即使是同一个Shard,也会继续新开一个TSMFile保存数据。本文的介绍出于简单化考虑,以下内容不考虑同一个Shard的TSMFile分裂的场景
- 索引块的构成:在TSMFile的索引区的构成,如下所示:
其中索引条目在InfluxDB的源码中被称为directIndex
。在TSMFile中,索引块是按照Serieskey + Fieldkey排序后组织在一起的。明白了TSMFile的索引区的构成,就可以很自然地理解InfluxDB如何高性能地在TSMFile扫描时序数据了:
- 根据用户指定的时间线(Serieskey)以及Field名在索引区利用二分查找找到指定的Serieskey+FieldKey所处的索引数据块
- 根据用户指定的时间戳范围在索引数据块中查找数据落在哪个(或哪几个)索引条目
- 将找到的索引条目对应的时序数据块加载到内存中进行进一步的Scan
注:上述的1,2,3只是简单化地介绍了查询机制,实际的实现中还有类似扫描的时间范围跨索引块等一系列复杂场景
- 时序数据的存储:在TSMFile中,时序数据块的结构如下:
其中同一个Serieskey + Fieldkey的所有时间戳 - Field值对被拆分开,分成两个区:Timestamps区和Value区分别进行存储。它的目的是:实际存储时可以分别对时间戳和Field值按不同的压缩算法进行存储以减少时序数据块的大小
采用的压缩算法如下所示:
Timestamp:Delta-of-delta encoding
Field Value:由于单个数据块的Field Value必然数据类型相同,因此可以集中按数据类型采用不同的压缩算法
- Float类: Gorrila's Float Commpression
- Integer类型: Delta Encoding + Zigzag Conversion + RLE / Simple8b / None
- String类型: Snappy Compression
- Boolean类型: Bit packing
做查询时,当利用TSMFile的索引找到文件中的时序数据块时,将数据块载入内存并对Timestamp以及Field Value进行解压缩后以便继续后续的查询操作。
TSIFile解析
有了TSMFile,第3章开头所说的三个主要场景中的场景1和场景2都可以得到很好的解决。但是如果查询时用户并没有按预期按照Serieskey来指定查询条件,而是指定了更加复杂的条件,该如何确保它的查询性能?通常情况下,这个问题的解决方案是依赖倒排索引( Inverted Index )。
InfluxDB的倒排索引依赖于下述两个数据结构:
- map
- map>>
它们在内存中展现如下:
但是在实际生产环境中,由于用户的时间线规模会变得很大,因此会造成倒排索引使用的内存过多,所以后来InfluxDB又引入了TSIFile
TSIFile的整体存储机制与TSMFile相似,也是以Shard为单位生成一个TSIFile。具体的存储格式就在此不赘述了。
总结
以上就是对InfluxDB的存储机制的粗浅解析,由于目前所见的只有单机版的InfluxDB,所以尚不知道集群版的InfluxDB在存储方面有哪些不同。但是,即便是这单机版的存储机制,也对我们设计时序数据库有着重要的参考意义。