InfluxDB的存储引擎演化过程

简介: InfluxDB的存储引擎从LSM Tree,到mmap B+ Tree,再到TSM Tree。

时序数据的来源很多,比如DevOps,目前公司的的机房里面成百上千台的服务器和虚机已经很常见了,这些服务器的运行状态、内部服务的log这些都需要监控。还有IoT,5G技术逐渐成熟,IoT将直接受益,终端设备每时每刻对会将其探测到数据以及其自身的运行状态数据传回服务器。时序数据数据以下特点:

  • 数据量非常大
  • 高并发读/写,取决于产生数据的终端数目、频率、数据维度,以及监控分析系统的需求
  • 数据持续稳定生成,系统设计时就可以基本确定其IOPS
  • 与时间维度完全相关

    • 时间越久的数据其价值越来越低,这也会导致大量过期数据的删除操作
    • 单点的数据没有价值,价值在连续的数据分布,所以就算单点数据出错了也没有必要修正

目前有一些公司开始关注时序数据系统,比如Facebook的beringei数据库,阿里云上实现的HiTSDB服务。InfluxDB是目前最为活跃的开源时序数据库(TSDB: Time Series Database)。InfluxDB尝试了不同的数据存储引擎,最终还是设计了针对时间序列进行优化的TSM Tree(Time-Structured Merge Tree)。

数据组织形式

以下是批量写入InfluxDB的数据Line Protocol。数据中包含了服务器运行参数和空气质量监测数据。

Labs,location=SH,host=server1 CUP=73,Mem=16.067 1574179200s
Labs,location=SH,host=server1 CUP=74,Mem=15.123 1574179210s
Labs,location=SH,host=server1 CUP=71,Mem=15.450 1574179220s
Labs,location=SH,host=server2 CUP=31,Mem=32.087 1574179200s
Labs,location=SH,host=server2 CUP=20,Mem=32.167 1574179210s
Labs,location=SH,host=server2 CUP=25,Mem=32.257 1574179220s
Labs,location=SZ,host=server1 CUP=11,Mem=8.021 1574179200s
Labs,location=SZ,host=server1 CUP=17,Mem=7.530 1574179210s
Labs,location=SZ,host=server1 CUP=37,Mem=7.214 1574179220s
Labs,location=SZ,host=server2 CUP=43,Mem=16.779 1574179200s
Labs,location=SZ,host=server2 CUP=22,Mem=16.326 1574179210s
Labs,location=SZ,host=server2 CUP=21,Mem=16.117 1574179220s
Air,city=SH,sensor=e6b58be8 PM25=62,O3=16,CO=5 1574179200s
Air,city=SH,sensor=e6b58be8 PM25=62,O3=19,CO=6 1574179210s
Air,city=SH,sensor=e6b58be8 PM25=62,O3=23,CO=5 1574179220s

measurement:是逻辑上的数据容器,类似于MySQL里面的table,数据样本中的Labs和Air。

tag set:以第一行数据为例,location和host就是tag key,SH和server1就是对应的tag value。组合在一起location=SH,host=server1就是tag set,还有city=SH,sensor=e6b58be8等。

field set:第一行数据中的CUP=73,Mem=16.067

timestamp:第一行数据中的1574179200s

point:数据点,样本数据中每一行就是一个数据点,它包括了以上列出来的几个部分。

series:给定一组tag set,所包含的所有数据点组成的相关数据序列,基于timestamp有序的。比如在Labs这个measurement中,location=SH,host=server1这个tag set对应的就是一个series,这个series包含了3个point。这里measurement + tag set就可以唯一标识series,称为SeriesKey。这是对InfluxDB里面最基本的时序数据组织形式。

entry:每个series中包含了field set,每个field对应的一列数据以及相对的timestamp就组成一个entry,这里SeriesKey + FiledName就是EntryKey 。比如Labs + location=SH,host=server1 + CUP对应的entry[1574179200s|73, 1574179210s|74, 1574179220s|71]。所以entry就是一个值序列。

时序数据中,单个point是没有意义的,意义在series或者entry上面进行聚合计算,也就是给定tags、fields以及一个时间区间,进行查询计算。

LSM Tree

一直到0.8版本以前,InfluxDB使用LSM Tree(Log-Structured Merge Tree)作为存储引擎,底层直接使用LevelDB,用timestamp作为key进行排序存储。关于LSM Tree可以参考序列文章:

使用LSM Tree遇到了一些问题:

  1. LevelDB其实是一个库,不是服务,没有实现热备份。
  2. LevelDB的删除操作的实现方式是写入一条tombstone记录,由后台的compaction进行删除,所以删除等效于写入。但是如上文所说,时序数据会涉及到大量的过期数据需要删除,这会严重影响数据写入效率。为了解决这个问题,InfluxDB根据时间段将数据分为多个shard,每个shard起一个LevelDB存储。这样删除过期数据就可以直接根据shard的时间区间进行,没必要通过写入操作。
  3. 使用shard又造成了另一个问题,随着数据量的增大,开了越来越多的LevelDB,每个LevelDB都会产生很多SSTable文件,机器上的文件句柄不久就被用完了。

mmap B+Tree

0.9版本使用BoltDB来做底层存储,其存储引擎用的是 mmap B+Tree(memory mapped B+Tree)。这带来了3个好处:

  1. InfluxDB和BoltDB都是以Go编写的,BoltDB可以直接嵌入到InfluxDB中。
  2. BoltDB支持热备份。
  3. BoltDB中每个数据库存储为一个文件,节省文件句柄。

InfluxDB还在BoltDB前面加了一层WAL。但是BoltDB由于数据写入时需要更新索引,造成随机写,其性能满足不了高IOPS的场景。

TSM Tree

从0.9.5版本开始,到目前最新1.7版本,InfluxDB使用新设计实现的存储引擎TSM Tree,这次又回到了LSM Tree,针对时序数据进行优化。对于其实现细节,已经有很多很专业的文章,但是不太易于理解。

LSM Tree对于写入效率的优化已经达到了极致,所以TSM的优化最主要的是对于数据压缩、数据清除和查询效率。以上文给出的数据为例,我们主要的需求场景有2种:

  1. location=SH,host=server1这一台服务器最近24小时的运行状态,CUP和Mem使用情况。这是给定了SeriesKey查找对应的series。这个其实是比较直接的,因为每一个entry都可以通过其EntryKey定位出来。
  2. location=SH这个机房所有机子最近24小时的总体运行状态,比如最大CPU,平均Mem。这里没有给定完全的SeriesKey,而是通过某些tag需要查询出所有相关的series。这个情况稍微麻烦一下,我们放到下面说。

下面我们来看InfluxDB数据存储的实现。

Shard

Shard是InfluxDB中真正完成数据存储的部件。延用了前面版本基于LevelDB存储的思想,每个shard等价于一个LevelDB实例,包含独立的wal,cache(LevelDB中的MemTable),分层级的tsm文件(LevelDB中的SSTable)以及compaction。新数据进来会根据timestamp写入对应的shard,如果是分布式环境,还可以通过hash放到不同的机器上面。

Cache

放在内存中,设计相对简单,就是一个字典,key是EntryKey,value是Entry。新数据写入时,找到相对的Entry追加进去。当大小达到阈值,再写到TSM文件。

TSM文件

TSM文件的设计类似于SSTable,cache中的一个Entry会持久化到tsm文件中一个或者多个block,并为每一个block建立index,在index中记录该block的EntryKey和timestamp区间,用于查询。上面的数据写入tsm文件会生成以下key-value条目:

Labs,location=SH,host=server1___CUP ===> (1574179200s,1574179210s,1574179220s), (73,74,71)
Labs,location=SH,host=server1___Mem ===> (1574179200s,1574179210s,1574179220s), (16.067,15.123,15.450)
Labs,location=SH,host=server2___CUP ===> (1574179200s,1574179210s,1574179220s), (31,20,25)
Labs,location=SH,host=server2___Mem ===> (1574179200s,1574179210s,1574179220s), (32.087,32.167,32.257)
Labs,location=SZ,host=server1___CUP ===> (1574179200s,1574179210s,1574179220s), (11,17,37)
Labs,location=SZ,host=server1___Mem ===> (1574179200s,1574179210s,1574179220s), (8.021,7.530,7.214)
......

到这里,解决上面提出来的第一个问题就比较清楚了,因为对EntryKey和timestamp都已经建了索引。还有一点,tsm文件中的每一个block只会Entry的数据,这个设计其实是列式存储,有两个好处:

  1. 根据timestamp进行区间检查找时,只需要拼接相关Entry,可以提高查询效率
  2. 列式存储,相同数据类型,更好的压缩率

TSI(Time Series Index)

这里就来看一下第二个问题。首先对数据中的series进行编号,并维护一个字典从series id映射到SeriesKey

{
    0: "location=SH,host=server1",
    1: "location=SH,host=server2",
    2: "location=SZ,host=server1",
    3: "location=SZ,host=server2"
}

然后对所有tag建立反向索引(就是看跟每个tag相关的所有series):

{
    "location": {
        "SH": [0, 1],
        "SZ": [2, 3]
    },
    "host": {
        "server1": [0, 2],
        "server2": [1, 3],
    }
}

现在第二个问题的解决方法就比较清楚了,只要在反向索引中找出所有跟给定的tag相关的series,通过series的SeriesKey就可以回到第一个问题,所以最终还是基于SeriesKey来定位到相应的数据。另外一点,InfluxDB对tag是加了索引的,给定某一个tag进行查询的效率很高,但是field没有,也就是说类似于SELECT * FROM Labs WHERE CUP=73这样对field进行检索是比较慢的,因为会对tsm文件中的数据进行遍历。

这又带来另外一个问题,如果series太多,反向索引在内存中放不了怎么办?这是在1.3版本中解决的问题,也是使用LSM Tree持久化到磁盘,同理也是每一个shard都会有一个独立的LSM Tree,磁盘文件名为fields.idx。

数据库文件结构

<root>/
├── data/
|    └── <db name>/
|        └── autogen/
|            └── 123/ # shard name
|                ├── 000000009-0000000001.tsm
|                └── fields.idx
├── meta/
|    └── meta.db
└── wal/
    └── <db name>/
        └── autogen/
            └── 123/ # shard name
                └── _00085.wal
目录
相关文章
|
1天前
|
存储 关系型数据库 分布式数据库
PolarDB的PolarStore存储引擎以其高效的索引结构、优化的数据压缩算法、出色的事务处理能力著称
PolarDB的PolarStore存储引擎以其高效的索引结构、优化的数据压缩算法、出色的事务处理能力著称。本文深入解析PolarStore的内部机制及优化策略,包括合理调整索引、优化数据分布、控制事务规模等,旨在最大化其性能优势,提升数据存储与访问效率。
11 5
|
存储 NoSQL 数据库
时序数据库连载系列: 时序数据库一哥InfluxDB之存储机制解析
InfluxDB 的存储机制解析 本文介绍了InfluxDB对于时序数据的存储/索引的设计。由于InfluxDB的集群版已在0.12版就不再开源,因此如无特殊说明,本文的介绍对象都是指 InfluxDB 单机版 1. InfluxDB 的存储引擎演进 尽管InfluxDB自发布以来历时三年多,其存储引擎的技术架构已经做过几次重大的改动, 以下将简要介绍一下InfluxDB的存储引擎演进的过程。
6599 0
|
6月前
|
存储 监控 负载均衡
InfluxDB最佳实践:数据模型设计与查询优化
【4月更文挑战第30天】本文探讨了InfluxDB的最佳实践,重点在于数据模型设计和查询优化。合理选择字段类型,根据业务逻辑划分Measurement,利用Tags进行索引优化,以及适时数据归档和清理,能有效提升性能。查询优化包括使用索引、精简查询语句、应用聚合函数及限制返回结果。分布式查询和分片适用于大规模数据集,以实现并行查询和负载均衡。这些策略旨在帮助用户优化InfluxDB的性能,进行高效时序数据分析。
|
6月前
|
存储 缓存 算法
InfluxDB高级特性:数据压缩与存储优化技术详解
【4月更文挑战第30天】InfluxDB,流行的开源时序数据库,采用LSM Tree存储引擎,利用WAL和TSM文件高效存储数据。其高级特性包括数据压缩(Snappy、Gorilla、Delta编码)和存储优化(时间序列分区、数据块合并、删除与归档)。通过选择合适的压缩算法、设置分区策略、定期合并数据块及制定保留策略,可优化InfluxDB性能和存储效率。
1170 0
|
6月前
|
存储 数据采集 缓存
TDengine 企业级功能:存储引擎对多表低频场景优化工作分享
在本文中,TDengine 的资深研发将对多表低频场景写入性能的大幅优化工作进行深入分析介绍,并从实践层面剖析本次功能升级的具体作用。
123 2
|
6月前
|
存储 NoSQL 关系型数据库
LSM设计一个数据库引擎
LSM设计一个数据库引擎
89 0
|
存储 数据库 时序数据库
InfluxDB是一个开源的时间序列数据库
InfluxDB是一个开源的时间序列数据库
169 2
|
存储 传感器 分布式计算
「时序数据库」时序数据库和MongoDB第二部分-模式设计最佳实践
「时序数据库」时序数据库和MongoDB第二部分-模式设计最佳实践
|
存储 NoSQL Oracle
「时序数据库」使用cassandra进行时间序列数据扫描
「时序数据库」使用cassandra进行时间序列数据扫描
|
存储 Prometheus Cloud Native
Prometheus 存储引擎分析
Prometheus 存储引擎分析