时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入
http://hbasefly.com/2018/03/27/timeseries-database-6/
InfluxDB写入总体框架
InfluxDB提供了多种接口协议供外部应用写入,比如可以使用collected采集数据上传,可以使用opentsdb作为输入,也可以使用http协议以及udp协议批量写入数据。批量数据进入到InfluxDB之后总体会经过三个步骤的处理,如下图所示:
- 批量时序数据shard路由:InfluxDB首先会将这些数据根据shard的不同分成不同的分组,每个分组的时序数据会发送到对应的shard。每个shard相当于HBase中region的概念,是InfluxDB中处理用户读写请求的单机引擎。
- 倒排索引引擎构建倒排索引:InfluxDB中shard由两个LSM引擎构成 – 倒排索引引擎和TSM引擎。时序数据首先会经过倒排索引引擎构建倒排索引,倒排索引用来实现InfluxDB的多维查询。
- TSM引擎持久化时序数据:倒排索引构建成功之后时序数据会进入TSM Engine处理。TMS Engine处理流程和通用LSM Engine基本一样,先将写入请求追加写入WAL日志,再写入cache,一旦满足特定条件会将cache中的时序数据执行flush操作落盘形成TSM File。
批量时序数据Shard路由
倒排索引引擎构建倒排索引
既然是LSM引擎,工作机制必然是这样的:首先将数据追加写入WAL再写入Cache就可以返回给用户写入成功,WAL可以保证即使发生异常宕机也可以恢复出来Cache中丢失的数据。一旦满足特定条件系统会将Cache中的时序数据执行flush操作落盘形成文件。文件数量超过一定阈值系统会将这些文件合并形成一个大文件。那具体到倒排索引引擎整个流程是什么样的,简单来看一下:
1.WAL追加写入:Inverted Index WAL格式很简单,由一个一个LogEntry构成,如下图所示:
每个LogEntry由Flag、Measurement、一系列Key\Value以及Checksum组成。其中Flag表示更新类型,包括写入、删除等,Measurement表示数据表,Key\Value表示写入的Tag Set以及Checksum,其中Checksum用于根据WAL回放数据时验证LogEntry的完整性。注意,LogEntry中并没有时序数据列,只有维度列(Tag Set)。
2. Inverted Index在内存中构建
(3)如果seriesKey在文件中不存在,需要将其写入内存。倒排索引内存结构主要包含两个Map:<measurement, List<tagKey>> 和 <tagKey, <tagValue, List<SeriesKey>>>,前者表示时序表与对应维度集合的映射,即这个表中有多少维度列。后者表示每个维度列都有哪些可枚举的值,以及这些值都对应哪些SeriesKey。InfluxDB中SeriesKey就是一把钥匙,只有拿到这把钥匙才能找到这个SeriesKey对应的数据。而倒排索引就是根据一些线索去找这把钥匙。
3. Inverted Index Cache Flush流程
- 缓存Map排序:<measurement, List<tagKey>>以及<tagKey, <tagValue, List<SeriesKey>>都需要经过排序处理,排序的意义在于有序数据可以结合Hash Index实现范围查询,另外Series Block中B+树的构建也需要SeriesKey排序。
- 构建并持久化Series Block:在排序的基础上首先持久化<tagKey, tagValue, List<SeriesKey>>结构中所有的SeriesKey,也就是先构建Series Block。依次持久化SeriesKey到SeriesKeyChunk,当Chunk满了之后,根据Chunk中最小的SeriesKey构建B+树中的Index Entry节点。当然,Hash Index以及Bloom Filter是需要实时构建的。需要注意的是,Series Block在构建的同时需要记录下SeriesKey与该Key在文件中偏移量的对应关系,即<SeriesKey, SeriesKeyOffset>,这一点至关重要。
- 内存中将SeriesKey映射为SeriesId:将<tagKey, <tagValue, List<SeriesKey>>结构中所有的SeriesKey由上一步中得到的<SeriesKey, SeriesKeyOffset >中的SeriesKeyOffset代替。形成新的结构:<tagKey, <tagValue, List<SeriesKeyOffset>>,即<tagKey, <tagValue, List<SeriesKeyId>>>,其中SeriesKeyId就是SeriesKeyOffset。
- 构建并持久化Tag Block:在新结构<tagKey, <tagValue, List<SeriesKeyId>>>的基础上首先持久化tagValue,将同一个tagKey下的所有tagValue持久化在一起并生成对应Hash Index写入文件,接着持久化下一个tagKey的所有tagValue。所有tagValue都持久话完成之后再依次持久化所有的tagKey,形成Tag Block。
- 构建并持久化Measurement Block:最后持久化measurement形成Measurement Block。
时序数据写入流程
2. 时序数据写入内存结构
- 内存中构建Series Data Block:顺序遍历内存Map中的时序数据,分别对时序数据的时间列和数值列进行相应的编码,按照Series Data Block的格式进行组织,当Block大小超过一定阈值就构建成功。并记录这个Block内时间列的最小时间MinTime以及最大时间MaxTime。
- 将构建好的Series Data Block写入文件:使用输出流将内存中数据输出到文件,并返回该Block在文件中的偏移量Offset以及总大小Size。
- 构建文件级别B+索引:在内存中为该Series Data Block构建一个索引节点Index Entry,使用数据Block在文件中的偏移量Offset、总大小Size以及MinTime、MaxTime构建一个Index Entry对象,写入到内存Series Index Block对象。
InfluxDB数据删除操作(DropMeasurement,DropTagKey)
InfluxDB比较奇葩,对于删除操作处理的比较异类,通常InfluxDB不会删除一条记录,而是会删除某段时间内或者某个维度下的所有记录,甚至一张表的所有记录,这和通常的数据库有所不同。比如:
DROP SERIES FROM h2o_feet WHERE location = ‘santa_monica' DELETE FROM "cpu" DELETE FROM "cpu" WHERE time < '2000-01-01T00:00:00Z' DELETE WHERE time < '2000-01-01T00:00:00Z'
上文我们知道InfluxDB中一个shard有两个LSM引擎,一个是倒排索引引擎(存储维度列到SeriesKey的映射关系,方便多维查找),一个是TSM Engine,用来存储实际的时序数据。如果是删除一条记录,通常只需要TSM Engine执行删除就可以,倒排索引引擎是不需要执行删除的。而如果是Drop Measurement这样的操作,那么两个LSM引擎都需要执行相应的删除。问题是,这两个引擎的删除策略完全不同,TSM Engine采用了一种同步删除策略,Inverted Index Engine采用了标记删除策略。如下图所示:
- TSM File Index相关处理:在内存中删除满足条件的Index Entry,通常删除会带有Time Range以及Key Range,而且TSM File Index会在引擎启动之后加载到内存。因此删除操作会将满足条件的Index Entry从内存中删除。
- 生成tombstoner文件:tombstoner文件会记录当前TSM File中所有被删除的时序数据,时序数据用[key, min, max]三个字段表示,其中key即SeriesKey+FieldKey,[min, max]表示要删除的时间段。如下图所示:
(2)删除Cache中满足条件的series
(3)在WAL中生成一条删除series的记录并持久化到硬盘