https://www.influxdata.com/blog/influxdb-markedly-outperforms-opentsdb-in-time-series-data-metrics-benchmark/
https://github.com/influxdata/influxdb-comparisons
https://www.influxdata.com/blog/new-storage-engine-time-structured-merge-tree/
https://zhuanlan.zhihu.com/p/28747552
一、OpenTSDB
OpenTSDB是一个分布式、可伸缩的时序数据库,支持高达每秒百万级的写入能力,支持毫秒级精度的数据存储,不需要降精度也可以永久保存数据。其优越的写性能和存储能力,得益于其底层依赖的HBase,HBase采用LSM树结构存储引擎加上分布式的架构,提供了优越的写入能力,底层依赖的完全水平扩展的HDFS提供了优越的存储能力。OpenTSDB对HBase深度依赖,并且根据HBase底层存储结构的特性,做了很多巧妙的优化。关于存储的优化,我在这篇文章中有详细的解析。在最新的版本中,还扩展了对BigTable和Cassandra的支持。
OpenTSDB采用按指标建模的方式,一个数据点会包含以下组成部分:
metric:时序数据指标的名称,例如sys.cpu.user,stock.quote等。
timestamp:秒级或毫秒级的Unix时间戳,代表该时间点的具体时间。
tags:一个或多个标签,也就是描述主体的不同的维度。Tag由TagKey和TagValue组成,TagKey就是维度,TagValue就是该维度的值。
value:该指标的值,目前只支持数值类型的值。
OpenTSDB采用按指标建模的方式,一个数据点会包含以下组成部分:
metric:时序数据指标的名称,例如sys.cpu.user,stock.quote等。
timestamp:秒级或毫秒级的Unix时间戳,代表该时间点的具体时间。
tags:一个或多个标签,也就是描述主体的不同的维度。Tag由TagKey和TagValue组成,TagKey就是维度,TagValue就是该维度的值。
value:该指标的值,目前只支持数值类型的值。
存储模型
OpenTSDB底层存储的优化思想,可以参考这篇文章,简单总结就是以下这几个关键的优化思路:
对数据的优化:为Metric、TagKey和TagValue分配UniqueID,建立原始值与UniqueID的索引,数据表存储Metric、TagKey和TagValue对应的UniqueID而不是原始值。
对KeyValue数的优化:如果对HBase底层存储模型十分了解的话,就知道行中的每一列在存储时对应一个KeyValue,减少行数和列数,能极大的节省存储空间以及提升查询效率。
对查询的优化:利用HBase的Server Side Filter来优化多维查询,利用Pre-aggregation和Rollup来优化GroupBy和降精度查询。
UIDTable
接下来看一下OpenTSDB在HBase上的几个关键的表结构的设计,首先是tsdb-uid表,结构如下:
Metric、TagKey和TagValue都会被分配一个相同的固定长度的UniqueID,默认是三个字节。tsdb-uid表使用两个ColumnFamily,存储了Metric、TagKey和TagValue与UniqueID的映射和反向映射,总共是6个Map的数据。
从图中的例子可以解读出:
TagKey为’host’,对应的UniqueID为’001’
TagValue为’static’,对应的UniqueId为’001’
Metric为’proc.loadavg.1m’,对应的UniqueID为’052’
为每一个Metric、TagKey和TagValue都分配UniqueID的好处,一是大大降低了存储空间和传输数据量,每个值都只需要3个字节就可以表示,这个压缩率是很客观的;二是采用固定长度的字节,可以很方便的从row key中解析出所需要的值,并且能够大大减少Java堆内的内存占用(bytes相比String能节省很多的内存占用),降低GC的压力。
不过采用固定字节的UID编码后,对于UID的个数是有上限要求的,3个字节最多只允许有16777216个不同的值,不过在大部分场景下都是够用的。当然这个长度是可以调整的,不过不支持动态更改。
DataTable
第二张关键的表是数据表,结构如下:
该表中,同一个小时内的数据会存储在同一行,行中的每一列代表一个数据点。如果是秒级精度,那一行最多会有3600个点,如果是毫秒级精度,那一行最多会有3600000个点。
这张表设计的精妙之处在于row key和qualifier(列名)的设计,以及对整行数据的compaction策略。row key格式为:
其中metric、tagk和tagv都是用uid来表示,由于uid固定字节长度的特性,所以在解析row key的时候,可以很方便的通过字节偏移来提取对应的值。Qualifier的取值为数据点的时间戳在这个小时的时间偏差,例如如果你是秒级精度数据,第30秒的数据对应的时间偏差就是30,所以列名取值就是30。列名采用时间偏差值的好处,主要在于能大大节省存储空间,秒级精度的数据只要占用2个字节,毫秒精度的数据只要占用4个字节,而若存储完整时间戳则要6个字节。整行数据写入后,OpenTSDB还会采取compaction的策略,将一行内的所有列合并成一列,这样做的主要目的是减少KeyValue数目。
查询优化
HBase仅提供简单的查询操作,包括单行查询和范围查询。单行查询必须提供完整的RowKey,范围查询必须提供RowKey的范围,扫描获得该范围下的所有数据。通常来说,单行查询的速度是很快的,而范围查询则是取决于扫描范围的大小,扫描个几千几万行问题不大,但是若扫描个十万上百万行,那读取的延迟就会高很多。
OpenTSDB提供丰富的查询功能,支持任意TagKey上的过滤,支持GroupBy以及降精度。TagKey的过滤属于查询的一部分,GroupBy和降精度属于对查询后的结果的计算部分。在查询条件中,主要的参数会包括:metric名称、tag key过滤条件以及时间范围。上面一章中指出,数据表的rowkey的格式为:
从查询的参数上可以看到,metric名称和时间范围确定的话,我们至少能确定row key的一个扫描范围。但是这个扫描范围,会把包含相同metric名称和时间范围内的所有的tag key的组合全部查询出来,如果你的tag key的组合有很多,那你的扫描范围是不可控的,可能会很大,这样查询的效率基本是不能接受的。
我们具体看一下OpenTSDB对查询的优化措施:
Server side filter
HBase提供了丰富和可扩展的filter,filter的工作原理是在server端扫描得到数据后,先经过filter的过滤后再将结果返回给客户端。Server side filter的优化策略无法减少扫描的数据量,但是可以大大减少传输的数据量。OpenTSDB会将某些条件的tag key filter转换为底层HBase的server side filter,不过该优化带来的效果有限,因为影响查询最关键的因素还是底层范围扫描的效率而不是传输的效率。
减少范围查询内扫描的数据量
要想真正提高查询效率,还是得从根本上减少范围扫描的数据量。注意这里不是减小查询的范围,而是减少该范围内扫描的数据量。这里用到了HBase一个很关键的filter,即FuzzyRowFilter,FuzzyRowFilter能够根据指定的条件,在执行范围扫描时,动态的跳过一定数据量。但不是所有OpenTSDB提供的查询条件都能够应用该优化,需要符合一定的条件,具体要符合哪些条件就不在这里说明了,有兴趣的可以去了解下FuzzyRowFilter的原理。
范围查询优化成单行查询
这个优化相比上一条,更加的极端。优化思路非常好理解,如果我能够知道要查询的所有数据对应的row key,那就不需要范围扫描了,而是单行查询就行了。这里也不是所有OpenTSDB提供的查询条件都能够应用该优化,同样需要符合一定的条件。单行查询要求给定确定的row key,而数据表中row key的组成部分包括metric名称、timestamp以及tags,metric名称和timestamp是能够确定的,如果tags也能够确定,那我们就能拼出完整的row key。所以很简单,如果要能够应用此优化,你必须提供所有tag key对应的tag value才行。
以上就是OpenTSDB对HBase查询的一些优化措施,但是除了查询,对查询后的数据还需要进行GroupBy和降精度。GroupBy和降精度的计算开销也是非常可观的,取决于查询后的结果的数量级。对GroupBy和降精度的计算的优化,几乎所有的时序数据库都采用了同样的优化措施,那就是pre-aggregation和auto-rollup。思路就是预先进行计算,而不是查询后计算。不过OpenTSDB在已发布的最新版本中,还未支持pre-aggregation和rollup。而在开发中的2.4版本中,也只提供了半吊子的方案,它只提供了一个新的接口支持将pre-aggregation和rollup的结果进行写入,但是对数据的pre-aggregation和rollup的计算还需要用户自己在外层实现。
总结
OpenTSDB的优势在于数据的写入和存储能力,得益于底层依赖的HBase所提供的能力。劣势在于数据查询和分析的能力上的不足,虽然在查询上已经做了很多的优化,但是不是所有的查询场景都能适用。可以说,OpenTSDB在TagValue过滤查询优化,是这次要对比的几个时序数据库中,优化的最差的。在GroupBy和Downsampling的查询上,也未提供Pre-aggregation和Auto-rollup的支持。不过在功能丰富程度上,OpenTSDB的API是支持最丰富的,这也让OpenTSDB的API成为了一个标杆。
二、KairosDB
KairosDB最初是从OpenTSDB 1.x版本fork出来的一个分支,目的是在OpenTSDB的代码基础上进行二次开发来满足新的功能需求。其改造之一就是支持可插拔式的存储引擎,例如支持H2可以方便本地开发和测试,而不是像OpenTSDB一样与HBase强耦合。在其最初的几个版本中,HBase也是作为其主要的存储引擎。但是在之后的存储优化中,慢慢使用Cassandra替换了HBase,它也是第一个基于Cassandra开发的时序数据库。在最新的几个版本中,已不再支持HBase,因为其存储优化使用了Cassandra所特有而HBase没有的一些特性。
在整体架构上,和OpenTSDB比较类似,都是采用了一个比较成熟的数据库来作为底层存储引擎。自己的主要逻辑仅仅是在存储引擎层之上很薄的一个逻辑层,这层逻辑层的部署架构是一个无状态的组件,可以很容易的水平扩展。
在功能差异性上,它在OpenTSDB 1.x上做二次开发,也是为了对OpenTSDB的一些功能做优化,或做出一些OpenTSDB所没有的功能。我大概罗列下我看到的主要的功能差异:
可插拔式的存储引擎:OpenTSDB在早期与HBase强耦合,为了追求极致的性能,甚至自研了一个异步的HBase Client(现在作为独立的一个开源项目输出:AsyncHBase)。这样也导致其整个代码都是采用异步驱动的模式编写,不光增加了代码的复杂度和降低可阅读性,也加大了支持多种存储引擎的难度。KairosDB严格定义了存储层的API Interface,整体逻辑与存储层耦合度较低,能比较容易的扩展多种存储引擎。当然现在最新版的OpenTSDB也能够额外支持Cassandra和BigTable,但是从整体的架构上,还不能说是一个支持可插拔式存储引擎的架构。
支持多种数据类型及自定义类型的值:OpenTSDB只支持numeric的值,而KairosDB支持numeric、string类型的值,也支持自定义数值类型。在某些场景下,metric value不是一个简单的数值,例如你要统计这个时间点的TopN,对应的metric value可能是一组string值。可扩展的类型,让未来的需求扩展会变得容易。从第一第二点差异可以看出,KairosDB基于OpenTSDB的第一大改造就是将OpenTSDB的功能模型和代码架构变得更加灵活。
支持Auto-rollup:目前大部分TSDB都在朝着支持pre-aggregation和auto-rollup的方向发展,OpenTSDB是少数的不支持该feature的TSDB,在最新发布的OpenTSDB版本中,甚至都不支持多精度数据的存储。不过现在KairosDB支持的auto-rollup功能,采取的还是一个比较原始的实现方式,在下面的章节会详细讲解。
不同的存储模型:存储是TSDB核心中的核心,OpenTSDB在存储模型上使用了UID的压缩优化,来优化查询和存储。KairosDB采取了一个不同的思路,利用了Cassandra宽表的特性,这也是它从HBase转向Cassandra的一个最重要的原因,在下面的章节会详细讲解。
存储模型
OpenTSDB的存储模型细节,可以参考这篇文章。其主要设计特点是采用了UID编码,大大节省了存储空间,并且利用UID编码的固定字节数的特性,利用HBase的Filter做了很多查询的优化。但是采用UID编码后也带来了很多的缺陷,一是需要维护metric/tagKey/tagValue到UID的映射表,所有data point的写入和读取都需要经过映射表的转换,映射表通常会缓存在TSD或者client,增加了额外的内存消耗;二是由于采用了UID编码,导致metric/tagKey/tagValue的基数是有上限的,取决于UID使用的字节数,并且在UID的分配上会有冲突,会影响写入。
本质上,OpenTSDB存储模型采用的UID编码优化,主要解决的就两个问题:
存储空间优化:UID编码解决重复的row key存储造成的冗余的存储空间问题。
查询优化:利用UID编码后TagKey和TagValue固定字节长度的特性,利用HBase的FuzzyRowFilter做特定场景的查询优化。
KairosDB在解决这两个问题上,采取了另外一种不同的方式,使其不需要使用UID编码,也不存在使用UID编码后遗留的问题。先看下KairosDB的存储模型是怎样的,它主要由以下三张表构成:
DataPoints: 存储所有原始数据点,每个数据点也是由metric、tags、timestamp和value构成。该表中一行数据的时间跨度是三周,也就是说三周内的所有数据点都存储在同一行,而OpenTSDB内的行的时间跨度只有一个小时。RowKey的组成与OpenTSDB类似,结构为
RowKeyIndex: 该表存储所有metric对应DataPoints表内所有row key的映射,也就是说同一个metric上写入的所有的row key,都会存储在同一行内,并且按时间排序。该表主要被用于查询,在根据tag key或者tag value做过滤时,会先从这张表过滤出要查询的时间段内所有符合条件的row key,后在DataPoints表内查询数据。
StringIndex: 该表就三行数据,每一行分别存储所有的metric、tag key和tag value。
KairosDB采取的存储模型,是利用了Cassandra宽表的特性。HBase的底层文件存储格式中,每一列会对应一个KeyValue,Key为该行的RowKey,所以HBase中一行中的每一列,都会重复的存储相同的RowKey,这也是为何采用了UID编码后能大大节省存储空间的主要原因,也是为何有了UID编码后还能采用compaction策略(将一行中所有列合并为一列)来进一步压缩存储空间的原因。而Cassandra的底层文件存储格式与HBase不同,它一行数据不会为每一列都重复的存储RowKey,所以它不需要使用UID编码。Cassandra内降低存储空间的一个优化方案就是缩减行数,这也是为何它一行存储三周数据而不是一个小时数据的原因。要进一步了解两种设计方案的原因,可以看下HBase文件格式以及Cassandra文件格式。
利用Cassandra的宽表特性,即使不采用UID编码,存储空间上相比采用UID编码的OpenTSDB,也不会差太多。可以看下官方的解释:
在查询优化上,采取的也是和OpenTSDB不一样的优化方式。先看下KairosDB内查询的整个流程:
如果有自定义的plugin,则从plugin中获取要查询的所有row key。(通过Plugin可以扩展使用外部索引系统来对row key进行索引,例如使用ElasticSearch)
如果没有自定义的plugin,则在RowKeyIndex表里根据metric和时间范围,找出所有的row key。(根据列名的范围来缩小查询范围,列名的范围是(metric+startTime, metric+endTime))
相比OpenTSDB直接在数据表上进行扫描来过滤row key的方式,KairosDB利用索引表无疑会大大减少扫描的数据量。在metric下tagKey和tagValue组合有限的情况下,会大大的提高查询效率。并且KairosDB还提供了QueryPlugin的方式,能够扩展利用外部组件来对row key进行索引,例如可以利用ElasticSearch,或者其他的索引系统,毕竟通过索引的方式,才是最优的查询方案,这也是Heroic相比KairosDB最大的一个改进的地方。
Auto-rollup
KairosDB的官方文档中有关于auto-rollup如何配置的章节,但是在讨论组内,其关于auto-rollup的说明如下:
总结来说,目前KairosDB提供的auto-rollup方案,还是比较简单的实现。就是一个可配置的单机组件,能够定时启动,把已经写入的数据读出后进行aggregation后再次写入,确实非常的原始,可用性和性能都比较低。
但是有总比没有好,支持auto-rollup一定是所有TSDB的趋势,也是能拉开功能差异和提高核心竞争力的关键功能。
BlueFlood
上面主要分析了KairosDB,第一个基于Cassandra构建的TSDB,那干脆继续分析下其他基于Cassandra构建的TSDB。
BlueFlood也是一个基于Cassandra构建的TSDB,从这个PPT介绍上可以看到整体架构上核心组成部分主要有三个:
Ingest module: 处理数据写入。
Rollup module: 做自动的预聚合和降精度。
Query module: 处理数据查询。
相比KairosDB,其在数据模型上与其他的TSDB有略微差异,主要在:
引入了租户的维度:这是一个创新,如果你是做一个服务化的TSDB,那租户这个维度是必需的。
不支持Tag:这一点上,是比较让我差异的地方。在大多数TSDB都基本上把Tag作为模型的不可缺少部分的情况下,BlueFlood在模型上居然不支持Tag。不过这有可能是其没有想好如何优化Tag维度查询的一种取舍,既然没想好怎么优化,那干脆就先不支持,反正未来再去扩展Tag是可以完全兼容的。BlueFlood当前已经利用ElasticSearch去构建metric的索引,我相信它未来的方案,应该也是基于ElasticSearch去构建Tag的索引,在这个方案完全支持好后,应该才会去引入Tag。
模型上的不足,BlueFlood不需要去考虑Tag查询如何优化,把精力都投入到了其他功能的优化上,例如auto-rollup。它在auto-rollup的功能支持上,甩了KairosDB和OpenTSDB几条街。来看看它的Auto-rollup功能的特点:
仅支持固定的Interval:5min,20min,60min,4hour,1day。
提供分布式的Rollup Service:rollup任务可以分布式的调度,rollup的数据是通过离线的批量扫描获取。
从它14年的介绍PPT上,还可以看到它在未来规划的几个功能点:
ElasticSearch Indexer and discovery: 目前这个已经实现,但是仅支持metric的索引,未来引入Tag后,可能也会用于Tag的索引。
Cloud files exporter for rollups: 这种方式对离线计算更加优化,rollup的大批量历史数据读取就不会影响在线的业务。
Apache Kafka exporter for rollups: 这种方式相比离线计算更进一步,rollup可以用流计算来做,实时性更加高。
总结来说,如果你不需要Tag的支持,并且对Rollup有强需求,那BlueFlood相比KairosDB会是一个更好的选择,反之还是选择KairosDB。
Heroic
第三个要分析的基于Cassandra的TSDB是Heroic,它在DB-Engines上的排名是第19,虽然比BlueFlood和KairosDB都落后,但是我认为它的设计实现却是最好的一个。关于它的起源,可以看下这篇文章或者这个PPT,都是宝贵的经验教训。
Spotify在决定研发Heroic之前,在OpenTSDB、InfluxDB、KairosDB等TSDB中选用KairosDB来替换他们老的监控系统的底层。但是很快就遇到了KairosDB在查询方面的问题,最主要还是KairosDB对metric和tag没有索引,在metric和tag基数达到一定数量级后,查询会变的很慢。所以Spotify研发Heroic的最大动机就是解决KairosDB的查询问题,采用的解决方案是使用ElasticSearch来作为索引优化查询引擎,而数据的写入和数据表的Schema则完全与KairosDB一致。
简单总结下它的特点:
完整的数据模型,完全遵循metric2.0的规范。
数据存储模型与KairosDB一致,使用ElasticSearch优化查询引擎。(这是除了InfluxDB外,其他TSDB如KairosDB、OpenTSDB、BlueFlood等现存最大的问题,是其核心竞争力之一)
不支持auto-rollup,这是它的缺陷之一。
如果你需要TSDB支持完整的数据模型,且希望得到高效的索引查询,那Heroic会是你的选择。
三、InfluxDB
InfluxDB在DB-Engines的时序数据库类别里排名第一,实至名归,从它的功能丰富性、易用性以及底层实现来看,都有很多的亮点,值得大篇幅来分析。
首先简单归纳下它的几个比较重要的特性:
极简架构:单机版的InfluxDB只需要安装一个binary,即可运行使用,完全没有任何的外部依赖。相比来看几个反面例子,OpenTSDB底层是HBase,拖家带口就得带上ZooKeeper、HDFS等,如果你不熟悉Hadoop技术栈,一般运维起来是有一定的难度,这也是其被人抱怨最多的一个点。KairosDB稍微好点,它依赖Cassandra和ZooKeeper,单机测试可以使用H2。总的来说,依赖一个外部的分布式数据库的TSDB,在架构上会比完全自包含的TSDB复杂一点,毕竟一个成熟的分布式数据库本身就很复杂,当然这一点在云计算这个时代已经完全消除。
TSM Engine:底层采用自研的TSM存储引擎,TSM也是基于LSM的思想,提供极强的写能力以及高压缩率,在后面的章节会对其做一个比较详细的分析。
InfluxQL:提供SQL-Like的查询语言,极大的方便了使用,数据库在易用性上演进的终极目标都是提供Query Language。
Continuous Queries: 通过CQ能够支持auto-rollup和pre-aggregation,对常见的查询操作可以通过CQ来预计算加速查询。
TimeSeries Index: 对Tags会进行索引,提供高效的检索。这一项功能,对比OpenTSDB和KairosDB等,在Tags检索的效率上提升了不少。OpenTSDB在Tags检索上做了不少的查询优化,但是受限于HBase的功能和数据模型,所以然并卵。不过目前稳定版中的实现采用的是memory-based index的实现方式,这种方案在实现上比较简单,查询上效率最高,但是带来了不少的问题,在下面的章节会详细描述。
Plugin Support: 支持自定义插件,能够扩展到兼容多种协议,如Graphite、collectd和OpenTSDB。
的数据回收。缺点是LSM的实现上,物理删除发生在compaction的时候,比较不及时。RocksDB、HBase、Cassandra和阿里云表格存储都提供数据TTL的功能。
InfluxDB采用的是第一种策略,会按7天一个周期,将数据分为多个不同的Shard,每个Shard都是一个独立的数据库实例。随着运行时间的增长,shard的个数会越来越多。而由于每个shard都是一个独立的数据库实例,底层都是一套独立的LevelDB存储引擎,这时带来的问题是,每个存储引擎都会打开比较多的文件,随着shard的增多,最终进程打开的文件句柄会很快触及到上限。LevelDB底层采用level compaction策略,是文件数多的原因之一。实际上level compaction策略不适合时序数据这种写入模式,这点原因InfluxDB没有提及。
由于遇到大量的客户反馈文件句柄过多的问题,InfluxDB在新版本的存储引擎选型中选择了BoltDB替换LevelDB。BoltDB底层数据结构是mmap B+树,其给出的选型理由是:1.与LevelDB相同语义的API;2.纯Go实现,便于集成和跨平台;3.单个数据库只使用一个文件,解决了文件句柄消耗过多的问题,这条是他们选型BoltDB的最主要理由。但是BoltDB的B+树结构与LSM相比,在写入能力上是一个弱势,B+树会产生大量的随机写。所以InfluxDB在使用BoltDB之后,很快遇到了IOPS的问题,当数据库大小达到几个GB后,会经常遇到IOPS的瓶颈,极大影响写入能力。虽然InfluxDB后续也采用了一些写入优化措施,例如在BoltDB之前加了一层WAL,数据写入先写WAL,WAL能保证数据是顺序写盘,但是最终写入BoltDB还是会带来比较大的IOPS资源消耗。
InfluxDB在经历了几个小版本的BoltDB后,最终决定自研TSM,TSM的设计目标一是解决LevelDB的文件句柄过多问题,二是解决BoltDB的写入性能问题。TSM全称是Time-Structured Merge Tree,思想类似LSM,不过是基于时序数据的特性做了一些特殊的优化。来看下TSM的一些重要组件:
Write Ahead Log(WAL) : 数据会先写入WAL,后进入memory-index和cache,写入WAL会同步刷盘,保证数据持久化。Cache内数据会异步刷入TSM File,在Cache内数据未持久化到TSM File之前若遇到进程crash,则会通过WAL内的数据来恢复cache内的数据,这个行为与LSM是完全类似的。
Cache: TSM的Cache与LSM的MemoryTable类似,其内部的数据为WAL中未持久化到TSM File的数据。若进程发生failover,则cache中的数据会根据WAL中的数据进行重建。Cache内数据保存在一个SortedMap中,Map的Key为TimeSeries+Timestamp的组成。所以可以看到,在内存中数据是按TimeSeries组织的,TimeSeries中的数据按时间顺序存放。
TSM Files: TSM File与LSM的SSTable类似,TSM File由四个部分组成,分别为:header, blocks, index和footer。其中最重要的部分是blocks和index:
Block:每个block内存储的是某个TimeSeries的一段时间范围内的值,即某个时间段下某个measurement的某组tag set对应的某个field的所有值,Block内部会根据field的不同的值的类型采取不同的压缩策略,以达到最优的压缩效率。
Index:文件内的索引信息保存了每个TimeSeries下所有的数据Block的位置信息,索引数据按TimeSeries的Key的字典序排序。在内存中不会把完整的index数据加载进去,这样会很大,而是只对部分Key做索引,称之为indirectIndex。indirectIndex中会有一些辅助定位的信息,例如该文件中的最小最大时间以及最小最大Key等,最重要的是保存了部分Key以及其Index数据的文件offset信息。若想要定位某个TimeSeries的Index数据,会先根据内存中的部分Key信息找到与其最相近的Index Offset,之后从该起点开始顺序扫描文件内容再精确定位到该Key的Index数据位置。
LevelCompaction: InfluxDB将TSM文件分为4个层级(Level 1-4),compaction只会发生在同层级文件内,同层级的文件compaction后会晋升到下一层级。从这个规则看,根据时序数据的产生特性,level越高数据生成时间越旧,访问热度越低。由Cache数据初次生成的TSM文件称为Snapshot,多个Snapshot文件compaction后产生Level1的TSM文件,Level1的文件compaction后生成level2的文件,依次类推。低Level和高Level的compaction会采用不同的算法,低level文件的compaction采用低CPU消耗的做法,例如不会做解压缩和block合并,而高level文件的compaction则会做block解压缩以及block合并,以进一步提高压缩率。我理解这种设计是一种权衡,compaction通常在后台工作,为了不影响实时的数据写入,对compaction消耗的资源是有严格的控制,资源受限的情况下必然会影响compaction的速度。而level越低的数据越新,热度也越高,需要有一种更快的加速查询的compaction,所以InfluxDB在低level采用低资源消耗的compaction策略,这完全是贴合时序数据的写入和查询特性来设计的。
IndexOptimizationCompaction: 当Level4的文件积攒到一定个数后,index会变得很大,查询效率会变的比较低。影响查询效率低的因素主要在于同一个TimeSeries数据会被多个TSM文件所包含,所以查询不可避免的需要跨多个文件进行数据整合。所以IndexOptimizationCompaction的主要作用就是将同一TimeSeries下的数据合并到同一个TSM文件中,尽量减少不同TSM文件间的TimeSeries重合度。
FullCompaction: InfluxDB在判断某个Shard长时间内不会再有数据写入之后,会对数据做一次FullCompaction。FullCompaction是LevelCompaction和IndexOptimization的整合,在做完一次FullCompaction之后,这个Shard不会再做任何的compaction,除非有新的数据写入或者删除发生。这个策略是对冷数据的一个规整,主要目的在于提高压缩率。
http://www.inf.ufpr.br/eduardo/ensino/ci809/papers/lsmtree.pdf
http://167.114.231.105/influxdb/v1.6/concepts/storage_engine/
Elasticsearch
Elasticsearch 是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名。
Elasticsearch以ELK stack被人所熟知。许多公司基于ELK搭建日志分析系统和实时搜索系统。之前我们在ELK的基础上开始开发metric监控系统。即想到了使用Elasticsearch来存储时间序列数据库。对Elasticserach的mapping做相应的优化,使其更适合存储时间序列数据模型,收获了不错的效果,完全满足了业务的需求。后期发现Elasticsearch新版本竟然也开始发布Metrics组件和APM组件,并大量的推广其全文检索外,对时间序列的存储能力。真是和我们当时的想法不谋而合。
http://blog.fatedier.com/2016/07/06/test-influxdb-and-opentsdb/
**