存储引擎

存储引擎

时序数据库的存储引擎主要需满足以下三个主要场景的性能需求:

  • 大批量的时序数据写入的高性能

  • 直接根据时间线(即 Influxdb 中的 Serieskey)在指定时间戳范围内扫描数据的高性能

  • 间接通过 measurement 和部分 tag 查询指定时间戳范围内所有满足条件的时序数据的高性能

WAL 解析

InfluxDB 写入时序数据时为了确保数据完整性和可用性,与大部分数据库产品一样,都是会先写 WAL,再写入缓存,最后刷盘。对于 InfluxDB 而言,写入时序数据的主要流程如同下图所示:

时序数据写入流程

InfluxDB 对于时间线数据和时序数据本身分开,分别写入不同的 WAL 中。

索引数据的 WAL

由于 InfluxDB 支持对 Measurement,TagKey,TagValue 的删除操作,当然随着时序数据的不断写入,自然也包括 增加新的时间线,因此索引数据的 WAL 会区分当前所做的操作具体是什么,它的 WAL 的结构如下图所示:

索引数据的 WAL

时序数据的 WAL

由于 InfluxDB 对于时序数据的写操作永远只有单纯写入,因此它的 Entry 不需要区分操作种类,直接记录写入的数据即可:

时序数据的 WAL

TSMFile 解析

TSMFile 是 InfluxDB 对于时序数据的存储方案。在文件系统层面,每一个 TSMFile 对应了一个 Shard。TSMFile 的存储结构如下图所示:

TSMFile

其特点是在一个 TSMFile 中将 时序数据(i.e Timestamp + Field value)保存在数据区;将 Serieskey 和 Field Name 的信息保存在索引区,通过一个基于 Serieskey + Fieldkey 构建的形似 B+tree 的文件内索引快速定位时序数据所在的数据块。在当前版本中,单个 TSMFile 的最大长度为 2GB,超过时即使是同一个 Shard,也会继续新开一个 TSMFile 保存数据。本文的介绍出于简单化考虑,以下内容不考虑同一个 Shard 的 TSMFile 分裂的场景。

索引块的构成

索引块的构成

其中索引条目在 InfluxDB 的源码中被称为 directIndex。在 TSMFile 中,索引块是按照 Serieskey + Fieldkey 排序 后组织在一起的。

  1. 根据用户指定的时间线(Serieskey)以及 Field 名 在 索引区 利用二分查找找到指定的 Serieskey+FieldKey 所处的 索引数据块
  2. 根据用户指定的时间戳范围在 索引数据块 中查找数据落在哪个(或哪几个)索引条目
  3. 将找到的索引条目对应的时序数据块加载到内存中进行进一步的 Scan

上述的 1,2,3 只是简单化地介绍了查询机制,实际的实现中还有类似扫描的时间范围跨索引块等一系列复杂场景。

时序数据的存储

同一个 Serieskey + Fieldkey 的 所有时间戳 - Field 值对被拆分开,分成两个区:Timestamps 区和 Value 区分别进行存储。它的目的是:实际存储时可以分别对时间戳和 Field 值按不同的压缩算法进行存储以减少时序数据块的大小。采用的压缩算法如下所示:

  • Timestamp:Delta-of-delta encoding
  • Field Value:由于单个数据块的 Field Value 必然数据类型相同,因此可以集中按数据类型采用不同的压缩算法

做查询时,当利用 TSMFile 的索引找到文件中的时序数据块时,将数据块载入内存并对 Timestamp 以及 Field Value 进行解压缩后以便继续后续的查询操作。

TSIFile 解析

如果查询时用户并没有按预期按照 Serieskey 来指定查询条件,而是指定了更加复杂的条件,该如何确保它的查询性能?通常情况下,这个问题的解决方案是依赖倒排索引(Inverted Index)。InfluxDB 的倒排索引依赖于下述两个数据结构:

  • map<SeriesID, SeriesKey>
  • map<tagkey, map<tagvalue, List<SeriesID>>>

它们在内存中展现如下:

但是在实际生产环境中,由于用户的时间线规模会变得很大,因此会造成倒排索引使用的内存过多,所以后来 InfluxDB 又引入了 TSIFile。TSIFile 的整体存储机制与 TSMFile 相似,也是以 Shard 为单位生成一个 TSIFile。

上一页
下一页