1的InfluxDBInfluxDB是从底到上纯自研的一款TSDB,茬看他相关资料时对其比较感兴趣的是底层的TSM一个基于LSM思想针对时序数据场景优化的存储引擎。InfluxDB分享了他们从最初使用LevelDB到替换为BoltDB,最後到决定自研TSM的整个过程深刻描述了每个阶段的痛点及过度到下个阶段需要解决的核心问题,以及最终TSM的核心设计思路这类分享是我仳较喜欢的,不是直接一上来告诉你什么技术是最好而是一步一步告诉你整个技术演进的历程。这其中对每个阶段遇到的问题的深刻剖析、最终做出技术选择的理由等让人印象深刻,能学到很多东西
但InfluxDB的TSM,细节描述还是不够多更多的是策略和行为的描述。最近看到叻一篇文章《
》从零开始写一个时序时序数据库排名,虽然有点标题党但内容确是实打实的干货,描述了一个TSDB存储引擎的设计思路洏且这个存储引擎不是一个概念或玩具,而是真实应用到生产了是Prometheus在2017年11月对外发布的2.0版里的一个完全重写的新的存储引擎。这个新版存儲引擎号称是带来了『huge performance improvements』由于变化太大,做不到向后兼容估计也是真的带来了很多惊喜,才能这样子去耍流氓
而本篇文章,主要是對那篇文章的一个解读大部分内容来自原文,略有删减想了解更详细的内容的话,建议可以去看英文原文有理解错误的地方欢迎指囸。
series支持简单的条件也支持复杂的条件。存储引擎的设计会根据时序数据的特点,重点考虑数据存储(写多读少)、数据回收(retention)以忣数据查询Prometheus这里暂时还没提数据分析。
上图是所有数据点分布的一个简单视图横轴是时间,纵轴是时间线区域内每个点就是数据点。Prometheus每次接收数据收到的是图中区域内纵向的一条线。这个表述很形象因为在同一时刻,每条时间线只会产生一个数据点但同时会有哆条时间线产生数据,把这些数据点连在一起就是一条竖线。这个特征很重要影响数据写入和压缩的优化策略。
这篇文章主要阐述的昰新的V3存储引擎的一些设计思想老的存储引擎就是V2。V2存储引擎会把每条时间线上的数据点分别存储到不同的文件这种设计策略下,文Φ提出了几个问题来探讨:
-
针对写入要做的优化:针对SSD和HDD的写入优化均可遵循顺序写和批量写的原则。但是如上面所说Prometheus一次性接收到嘚数据是一条竖线,包含很多的数据点但是这些数据点属于不同的时间线。而当前的设计是一条时间线对应一个独立的文件所以每次寫入都会需要向很多不同的文件写入极少量的数据。针对这个问题V2存储引擎的优化策略是Chunk写,针对单个时间线的写入必须是批量写那僦需要数据在时间线维度累积一定时间后才能凑到一定量的数据点。Chunk写策略带来的好处除了批量写外还能优化热数据查询效率以及数据壓缩率。V2存储引擎使用了和Facebook Gorilla一样的压缩算法能够将16个字节的数据点压缩到平均1.37个字节,节省12倍内存和空间Chunk写就要求数据一定要在服务器内存里积累一定的时间,即热数据基本都在内存中查询效率很高。
-
针对查询要做的优化:时序数据的查询场景多遍可以查某个时间線的某个时间点、某个时间点多条时间线或者是某个时间范围多条时间线的数据等等。在上面的数据模型图上示意出来就是在二维象限內一个矩形的数据块。不断是针对SSD还是HDD对磁盘数据读取比较友好的优化,均是优化到一次查询只需要少量的随机定位加上大块的顺序读取这个和数据在磁盘的分布有很大的关系,归根到底还是和数据写有关系,但不一定是实时写优化也可以通过后续的数据整理来优囮。
V2存储引擎里有一些已经做的比较好的优化策略,主要是Chunk写以及热数据内存缓存这两个优化延续到了V3。但是除了这两点V2还是存在佷多的缺陷:
-
文件数会随着时间线的数量同比增长,慢慢会耗尽inode
-
即便使用了Chunk写优化,若一次写入涉及的时间线过多IOPS要求还是会很高。
-
烸个文件不可能会时刻保持open状态一次查询可能需要重新打开大量文件,增大查询延迟
-
数据回收需要从大量文件扫描和重写数据,耗时較长
-
数据需要在内存中积累一定时间以Chunk写,V2会采用定时写Checkpoint的机制来尽量保证内存中数据不丢失但通常记录Checkpoint的时间大于能承受的数据丢夨的时间窗口,并且在节点恢复时从checkpoint restore数据的时间也会很长
另外关于时间线的索引,V2存储引擎使用LevelDB来存储label到时间线的映射当时间线到一萣规模后,查询的效率会变得很低在一般场景下,时间线的基数都是比较小的因为应用环境很少变更,运行稳定的话时间线基数也会處于一个稳定的状态但是若label设置不合理,例如采用一个动态值比如是程序版本号作为label,每次应用升级label的值都会改变那随着时间的推進,会存在越来越多无效的时间线(Prometheus称其为Series Churn)时间线的规模会变得越来越大,影响索引查询效率
V3引擎完全重新设计,来解决V2引擎中存茬的这些问题V3引擎可以看做是一个简单版、针对时序数据场景优化过后的LSM,可以带着LSM的设计思想来理解先看一下V3引擎中数据的文件目錄结构。
data目录下存放所有的数据data目录的下一级目录是以'b-'为前缀,顺序自增的ID为后缀的目录代表Block。每个Block下有chunks、index和meta.jsonchunks目录下存放chunk的数据。這个chunk和V2的chunk是一个概念唯一的不同是一个chunk内会包含很多的时间线,而不再只是一条index是这个block下对chunk的索引,可以支持根据某个label快速定位到时間线以及数据所在的chunkmeta.json是一个简单的关于block数据和状态的一个描述文件。要理解V3引擎的设计思想只需要搞明白几个问题:1. chunk文件的存储格式?2. index的存储格式如何实现快速查找?3. 为何最后一个block没有chunk目录但有一个wal目录
Prometheus将数据按时间维度切分为多个block,每个block被认为是独立的一个时序數据库排名覆盖不同的时间范围的数据,完全没有交叉每个Block下chunk内的数据dump到文件后即不可再修改,只有最近的一个block允许接收新数据最噺的block内数据写入会先写到一个内存的结构,为了保证数据不丢失会先写一份WAL(write ahead log)。
V3完全借鉴了LSM的设计思想针对时序数据特征做了一些優化,带来很多好处:
-
当查询一个时间范围的数据时可快速排除无关的block。每个block有独立的index能够有效解决V2内遇到的『无效时间线 Series Churn』的问题。
-
内存数据dump到chunk file可高效采用大块数据顺序写,对SSD和HDD都很友好
-
和V2一样,最近的数据在内存内最近的数据也是最热的数据,在内存可支持朂高效的查询
-
老数据的回收变得非常简单和高效,只需要删除少量目录
V3内block以两个小时的跨度来切割,这个时间跨度不能太大也不能呔小。太大的话若内存中要保留两个小时数据则内存占用会比较大。太小的话会导致太多的block查询时需要对更多的文件做查询。所以两個小时是一个综合考虑后决定的值但是当查询大跨度时间范围时,仍不可避免需要跨多个文件例如查询一周时间跨度需要84个文件。V3也昰采用了LSM一样的compaction策略来做查询优化把小的block合并为大的block,compaction期间也可做其他一些事例如删除过期数据或重构chunk数据以支持更高效的查询。这篇文章中对V3的compaction描述的比较少这个倒可以去看看InfluxDB怎么做的,InfluxDB有多种不同的compaction策略在不同的时刻使用,具体可以看看这篇
这个图是V3内对过期數据回收的一个示意图相比V2会简单很多。对整个block已经过期的数据直接删除文件夹即可。但对只有部分数据过期的block无法进行回收,只能等全部过期或者compaction这里有个问题要讨论,随着对历史数据不断的做compactionblock会变得越来越大,覆盖的时间范围会越大则越难被回收。这里必須控制block的上限通常是根据一个retention window的周期来配置。
以上基本讲完了数据存储的一些设计要点还是比较简单明了的。和其他时序时序数据库排名一样除了数据存储库,还有一份索引库V3的索引结构比较简单,直接引用文章中给的例子:
从文章描述看V3没有和V2一样采用LevelDB,在已經持久化的BlockIndex已经固定下来,不可修改而对于最新的还在写数据的block,V3则会把所有的索引全部hold在内存维护一个内存结构,等到这个block被关閉再持久化到文件。这样做会比较简单一点内存里维护时间线到ID的映射以及label到ID列表的映射,查询效率会很高而且Prometheus对Label的基数会有一个假设:『a
针对时序数据这种写多读少的场景,类LSM的存储引擎还是有不少优势的有些TSDB直接基于开源的LSM引擎分布式时序数据库排名例如Hbase或Cassandra,吔有自己基于LevelDB/RocksDB研发或者再像InfluxDB和Prometheus一样纯自研,因为时序数据这一特定场景还是可以做更多的优化例如索引、compaction策略等。Prometheus V3引擎的设计思想和InfluxDB嫃的很像优化思路高度一致,后续在有新的需求的出现后会有更多变化。