时间序列数据的存储和计算 - 开源时序数据库解析(四)

本文涉及的产品
对象存储 OSS,标准 - 本地冗余存储 20GB 3个月
对象存储 OSS,内容安全 1000 次 1年
对象存储OSS,敏感数据保护2.0 200GB 1年
简介: Prometheus 开源时序数据库解析的系列文章在之前已经完成了几篇,对比分析了Hbase系的OpenTSDB、Cassandra系的KairosDB、BlueFlood及Heroic,最后是tsdb ranking top 1的InfluxDB。

Prometheus

开源时序数据库解析的系列文章在之前已经完成了几篇,对比分析了Hbase系的OpenTSDB、Cassandra系的KairosDB、BlueFlood及Heroic,最后是tsdb ranking top 1的InfluxDB。InfluxDB是从底到上纯自研的一款TSDB,在看他相关资料时对其比较感兴趣的是底层的TSM,一个基于LSM思想针对时序数据场景优化的存储引擎。InfluxDB分享了他们从最初使用LevelDB,到替换成BoltDB,最后到决定自研TSM的整个过程,深刻描述了每个阶段的痛点及过度到下个阶段需要解决的核心问题,以及最终TSM的核心设计思路。这类分享是我比较喜欢的,不是直接一上来告诉你什么技术是最好,而是一步一步告诉你整个技术演进的历程。这其中对每个阶段遇到的问题的深刻剖析、最终做出技术选择的理由等,让人印象深刻,能学到很多东西。
但InfluxDB的TSM,细节描述还是不够多,更多的是策略和行为的描述。最近看到了一篇文章《 Writing a Time Series Database from Scratch》,从零开始写一个时序数据库,虽然有点标题党,但内容确是实打实的干货,描述了一个TSDB存储引擎的设计思路。而且这个存储引擎不是一个概念或玩具,而是真实应用到生产了,是Prometheus在2017年11月对外发布的2.0版里的一个完全重写的新的存储引擎。这个新版存储引擎号称是带来了『huge performance improvements』,由于变化太大,做不到向后兼容,估计也是真的带来了很多惊喜,才能这样子去耍流氓。
而本篇文章,主要是对那篇文章的一个解读,大部分内容来自原文,略有删减。想了解更详细的内容的话,建议可以去看英文原文,有理解错误的地方欢迎指正。

数据模型

Prometheus与其他主流时序数据库一样,在数据模型定义上,也会包含metric name、一个或多个labels(同tags)以及metric value。metric name加一组labels作为唯一标识,来定义time series,也就是时间线。在查询时,支持根据labels条件查找time series,支持简单的条件也支持复杂的条件。存储引擎的设计,会根据时序数据的特点,重点考虑数据存储(写多读少)、数据回收(retention)以及数据查询,Prometheus这里暂时还没提数据分析。
上图是所有数据点分布的一个简单视图,横轴是时间,纵轴是时间线,区域内每个点就是数据点。Prometheus每次接收数据,收到的是图中区域内纵向的一条线。这个表述很形象,因为在同一时刻,每条时间线只会产生一个数据点,但同时会有多条时间线产生数据,把这些数据点连在一起,就是一条竖线。这个特征很重要,影响数据写入和压缩的优化策略。

V2存储引擎

这篇文章主要阐述的是新的V3存储引擎的一些设计思想,老的存储引擎就是V2。V2存储引擎会把每条时间线上的数据点分别存储到不同的文件,这种设计策略下,文中提出了几个问题来探讨:
  1. 针对写入要做的优化:针对SSD和HDD的写入优化,均可遵循顺序写和批量写的原则。但是如上面所说,Prometheus一次性接收到的数据是一条竖线,包含很多的数据点,但是这些数据点属于不同的时间线。而当前的设计是一条时间线对应一个独立的文件,所以每次写入都会需要向很多不同的文件写入极少量的数据。针对这个问题,V2存储引擎的优化策略是Chunk写,针对单个时间线的写入必须是批量写,那就需要数据在时间线维度累积一定时间后才能凑到一定量的数据点。Chunk写策略带来的好处除了批量写外,还能优化热数据查询效率以及数据压缩率。V2存储引擎使用了和Facebook Gorilla一样的压缩算法,能够将16个字节的数据点压缩到平均1.37个字节,节省12倍内存和空间。Chunk写就要求数据一定要在服务器内存里积累一定的时间,即热数据基本都在内存中,查询效率很高。
  2. 针对查询要做的优化:时序数据的查询场景多遍,可以查某个时间线的某个时间点、某个时间点多条时间线或者是某个时间范围多条时间线的数据等等。在上面的数据模型图上示意出来,就是在二维象限内一个矩形的数据块。不断是针对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存储引擎

V3引擎完全重新设计,来解决V2引擎中存在的这些问题。V3引擎可以看做是一个简单版、针对时序数据场景优化过后的LSM,可以带着LSM的设计思想来理解,先看一下V3引擎中数据的文件目录结构。
data目录下存放所有的数据,data目录的下一级目录是以'b-'为前缀,顺序自增的ID为后缀的目录,代表Block。每个Block下有chunks、index和meta.json,chunks目录下存放chunk的数据。这个chunk和V2的chunk是一个概念,唯一的不同是一个chunk内会包含很多的时间线,而不再只是一条。index是这个block下对chunk的索引,可以支持根据某个label快速定位到时间线以及数据所在的chunk。meta.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的设计思想,针对时序数据特征做了一些优化,带来很多好处:
  1. 当查询一个时间范围的数据时,可快速排除无关的block。每个block有独立的index,能够有效解决V2内遇到的『无效时间线 Series Churn』的问题。
  2. 内存数据dump到chunk file,可高效采用大块数据顺序写,对SSD和HDD都很友好。
  3. 和V2一样,最近的数据在内存内,最近的数据也是最热的数据,在内存可支持最高效的查询。
  4. 老数据的回收变得非常简单和高效,只需要删除少量目录。
V3内block以两个小时的跨度来切割,这个时间跨度不能太大,也不能太小。太大的话若内存中要保留两个小时数据,则内存占用会比较大。太小的话会导致太多的block,查询时需要对更多的文件做查询。所以两个小时是一个综合考虑后决定的值,但是当查询大跨度时间范围时,仍不可避免需要跨多个文件,例如查询一周时间跨度需要84个文件。V3也是采用了LSM一样的compaction策略来做查询优化,把小的block合并为大的block,compaction期间也可做其他一些事,例如删除过期数据或重构chunk数据以支持更高效的查询。这篇文章中对V3的compaction描述的比较少,这个倒可以去看看InfluxDB怎么做的,InfluxDB有多种不同的compaction策略,在不同的时刻使用,具体可以看看这篇 文章
这个图是V3内对过期数据回收的一个示意图,相比V2会简单很多。对整个block已经过期的数据,直接删除文件夹即可。但对只有部分数据过期的block,无法进行回收,只能等全部过期或者compaction。这里有个问题要讨论,随着对历史数据不断的做compaction,block会变得越来越大,覆盖的时间范围会越大,则越难被回收。这里必须控制block的上限,通常是根据一个retention window的周期来配置。
以上基本讲完了数据存储的一些设计要点,还是比较简单明了的。和其他时序数据库一样,除了数据存储库,还有一份索引库。V3的索引结构比较简单,直接引用文章中给的例子:
从文章描述看,V3没有和V2一样采用LevelDB,在已经持久化的Block,Index已经固定下来,不可修改。而对于最新的还在写数据的block,V3则会把所有的索引全部hold在内存,维护一个内存结构,等到这个block被关闭,再持久化到文件。这样做会比较简单一点,内存里维护时间线到ID的映射以及label到ID列表的映射,查询效率会很高。而且Prometheus对Label的基数会有一个假设:『a real-world dataset of ~4.4 million series with about 12 labels each has less than 5,000 unique labels』,这个全部保存在内存也是一个很小的量级,完全没有问题。InfluxDB采用的是类似的策略,而其他一些TSDB则直接使用ElasticSearch作为索引引擎。

总结

针对时序数据这种写多读少的场景,类LSM的存储引擎还是有不少优势的。有些TSDB直接基于开源的LSM引擎分布式数据库例如Hbase或Cassandra,也有自己基于LevelDB/RocksDB研发,或者再像InfluxDB和Prometheus一样纯自研,因为时序数据这一特定场景还是可以做更多的优化,例如索引、compaction策略等。Prometheus V3引擎的设计思想和InfluxDB真的很像,优化思路高度一致,后续在有新的需求的出现后,会有更多变化。
目录
相关文章
|
4月前
|
存储 Oracle 关系型数据库
服务器数据恢复—光纤存储上oracle数据库数据恢复案例
一台光纤服务器存储上有16块FC硬盘,上层部署了Oracle数据库。服务器存储前面板2个硬盘指示灯显示异常,存储映射到linux操作系统上的卷挂载不上,业务中断。 通过storage manager查看存储状态,发现逻辑卷状态失败。再查看物理磁盘状态,发现其中一块盘报告“警告”,硬盘指示灯显示异常的2块盘报告“失败”。 将当前存储的完整日志状态备份下来,解析备份出来的存储日志并获得了关于逻辑卷结构的部分信息。
|
6月前
|
存储 缓存 自然语言处理
评论功能开发全解析:从数据库设计到多语言实现-优雅草卓伊凡
评论功能开发全解析:从数据库设计到多语言实现-优雅草卓伊凡
160 8
评论功能开发全解析:从数据库设计到多语言实现-优雅草卓伊凡
|
5月前
|
存储 关系型数据库 数据库
高性能云盘:一文解析RDS数据库存储架构升级
性能、成本、弹性,是客户实际使用数据库过程中关注的三个重要方面。RDS业界率先推出的高性能云盘(原通用云盘),是PaaS层和IaaS层的深度融合的技术最佳实践,通过使用不同的存储介质,为客户提供同时满足低成本、低延迟、高持久性的体验。
|
7月前
|
SQL 存储 分布式数据库
分布式存储数据恢复—hbase和hive数据库数据恢复案例
分布式存储数据恢复环境: 16台某品牌R730xd服务器节点,每台服务器节点上有数台虚拟机。 虚拟机上部署Hbase和Hive数据库。 分布式存储故障: 数据库底层文件被误删除,数据库不能使用。要求恢复hbase和hive数据库。
256 12
|
8月前
|
存储 SQL NoSQL
【赵渝强老师】达梦数据库的逻辑存储结构
本文介绍了达梦数据库的存储结构,包括逻辑和物理存储两部分。逻辑存储结构由数据库(Database)、表空间(Tablespaces)、段(Segments)、簇(Cluster)和页(Page)组成。数据库是最大逻辑单元,包含所有表、索引等;表空间由数据文件组成,用于存储对象;段由簇构成,簇包含连续的数据页;页是最小存储单元。文中还提供了查询表空间、段和页大小的SQL语句,并附有视频讲解和示意图。
313 7
|
9月前
|
存储 索引 Python
Python入门:6.深入解析Python中的序列
在 Python 中,**序列**是一种有序的数据结构,广泛应用于数据存储、操作和处理。序列的一个显著特点是支持通过**索引**访问数据。常见的序列类型包括字符串(`str`)、列表(`list`)和元组(`tuple`)。这些序列各有特点,既可以存储简单的字符,也可以存储复杂的对象。 为了帮助初学者掌握 Python 中的序列操作,本文将围绕**字符串**、**列表**和**元组**这三种序列类型,详细介绍其定义、常用方法和具体示例。
Python入门:6.深入解析Python中的序列
|
9月前
|
数据可视化 算法 数据挖掘
用傅里叶变换解码时间序列:从频域视角解析季节性模式
本文介绍了如何使用傅里叶变换和周期图分析来识别时间序列中的季节性模式,特别是在能源消耗数据中。通过Python实现傅里叶变换和周期图,可以有效提取并量化时间序列中的主要和次要频率成分,克服传统可视化分析的局限性。这对于准确捕捉时间序列中的季节性变化具有重要意义。文章以AEP能源消耗数据为例,展示了如何应用这些方法识别日、周、半年等周期模式。
384 3
用傅里叶变换解码时间序列:从频域视角解析季节性模式
|
9月前
|
存储 关系型数据库 分布式数据库
PolarDB开源数据库进阶课3 共享存储在线扩容
本文继续探讨穷鬼玩PolarDB RAC一写多读集群系列,介绍如何在线扩容共享存储。实验环境依赖《在Docker容器中用loop设备模拟共享存储》搭建。主要步骤包括:1) 扩容虚拟磁盘;2) 刷新loop设备容量;3) 使用PFS工具进行文件系统扩容;4) 更新数据库实例以识别新空间。通过这些步骤,成功将共享存储从20GB扩容至30GB,并确保所有节点都能使用新的存储空间。
178 1
|
8月前
|
存储 SQL 安全
【赵渝强老师】达梦数据库的物理存储结构
本文介绍了达梦数据库的存储结构及各类物理文件的作用。达梦数据库通过逻辑和物理存储结构管理数据,包含配置文件(如dm.ini、sqllog.ini)、控制文件(dm.ctl)、数据文件(*.dbf)、重做日志文件(*.log)、归档日志文件、备份文件(*.bak)等。配置文件用于功能设置,控制文件记录数据库初始信息,数据文件存储实际数据,重做日志用于故障恢复,归档日志增强数据安全性,备份文件保障数据完整性,跟踪与事件日志辅助问题分析。这些文件共同确保数据库高效、稳定运行。
370 0
|
9月前
|
存储 人工智能 监控
时序数据库 TDengine 化工新签约:存储降本一半,查询提速十倍
化工行业在数字化转型过程中面临数据接入复杂、实时性要求高、系统集成难度大等诸多挑战。福州力川数码科技有限公司科技依托深厚的行业积累,精准聚焦行业痛点,并携手 TDengine 提供高效解决方案。
178 0

推荐镜像

更多
  • DNS