在 8 月 13 日的 TDengine 开发者大会上,TDengine 存储引擎架构师程洪泽带来题为《TDengine 的存储引擎降级之路——从 1.0 到 3.0》的主题演讲,具体论述了 TDengine 3.0 存储引擎的技术优化与降级。本文依据此演讲整顿而成。
点击【这里】查看残缺演讲视频
相比前两个版本,3.0 的存储引擎更重视各种场景下的存储和查问效率,不仅要对治理节点进一步“减负”,提供高效正当的更新、删除性能,反对数据备份、流式解决等性能,还要思考到数据维度收缩下的高效解决、多表场景下的开机启动速度、正当高效且精确地应用系统资源等需要。
TDengine 3.0 存储引擎的更新能够分为三大块,首先是 TQ,基于 WAL 的音讯队列;其次是 META,基于 TDB 的元数据存储引擎;第三是 TSDB(Time Series Database),用来存储时序数据的类 LSM 存储引擎(TSDB SE)。
音讯队列存储引擎
在 1.0 和 2.0 时咱们提供了一个基于 TSDB 存储的间断查问性能,过后的想法是用它来代替流式解决。工作流程能够概括为:App 定时发查问工作上来,查问引擎执行查问,TSDB 返回后果给查问引擎,查问引擎再把后果返回给 App。
这一性能的长处是能够复用查问引擎,简略易开发,但毛病也很显著,不仅会呈现计算实时性差、查问压力大导致算力节约的状况,更重要的是乱序问题无奈解决。数据在进入 TSDB 之后都会依照工夫戳进行排序,一个乱序的数据进来后插到后面,TSDB 是无奈推出这条数据的,这就会导致乱序问题的呈现;另外因为查问引擎是复用的,TSDB 的查问引擎也不会对新的乱序数据进行解决、对后果进行更改校验。
从这一技术背景登程,TDengine 3.0 中须要设计一个存储引擎来反对音讯队列和流式计算。 这个存储引擎能通知咱们什么样的数据是增量数据,这样一来,流式计算只需解决增量数据就好了,其余的数据就不必管了。
此外这个存储引擎须要构建在一个 Pipe 之上,保证数据进入和进来的前后程序统一。在设计时,咱们发现 TDengine 的 WAL 其实就是一个人造的 Pipe。于是咱们在 WAL 之上加了一层索引,并进行大量的适配开发,实现了 TQ 存储引擎。
如果大家深入研究过 TDengine 的模型,就会发现它的架构模型和 Kafka 的很多设计都是绝对应的,超级表和 Kafka 的 Topic 类似、Vnode 跟 Kafka 中的 Partition 也很靠近,子表的表名跟 Kafka 中的 Event Key 对应,因而这个架构设计人造地就带有音讯队列的特点,从这点登程,TDengine 3.0 想要实现一个音讯队列是非常容易的。
基于 TQ 存储引擎,在实际操作时,查问引擎只会解决增量数据,将计算结果修改后返回给 App,而不会再进行全量数据的再查问。 它带来的长处是实时性十分高,因为能对增量数据进行明确地区分,乱序数据也得以高效解决,同时还节俭了更多的计算资源,将计算结果修改。
元数据存储引擎
在元数据存储这块,此前的 1.0 和 2.0 采取的都是比较简单的存储机制,即全内存存储,数据在内存中以 hash 表的形式存储,并辅以跳表索引,这个 hash 表中有一个 Backup Storage Engine,它能够保证数据的长久化。该形式的长处是全内存、效率高,但毛病也很显著,当启动时,这部分数据就会全副加载到内存之中,不仅内存占用无奈精准管制,还会导致开机启动工夫长。
为了解决这些问题,在 3.0 中咱们研发了 TDB(一个 B+ 树格局的的存储引擎),来存储元数据及元数据索引。TDB 的 B+ 树存储适宜元数据读多写少的场景,可能反对百亿工夫线的存储,防止了元数据全内存存储以及长时间的加载,同时解决了在无限内存下,表数量收缩的问题。对于 TDB 是如何实现的,大家如果感兴趣,能够去 GitHub(https://github.com/taosdata/T…)上看一下源代码。
TDB 的长处是内存能够准确管制,开机启动速度快,在无限内存下也能够存储海量的元数据,此外如果 TDB 外加 Cache 辅助的话,在肯定水平上能够提供靠近全内存 hash 表的查问速度。
时序数据存储引擎
时序数据的更新和删除
在 2.0 中,更新删除性能是在引擎开发玩后补充开发的一个性能,因而 2.0 的更新和删除性能绝对简略,但性能较弱。2.0 的更新是基于一个散布在横轴上的工夫戳,更新数据的操作就是在前面追加雷同工夫戳的数据,简略来讲就是用乱序数据的办法来解决更新,而后查问引擎把这些乱序数据进行合并,就失去了更新后的后果。删除的实现更加简略,近似于物理删除,要删除的数据会在内存、硬盘上被间接“干掉”,效率绝对较低。
TDengine 3.0 齐全摈弃了 2.0 的更新删除机制,在设计层面思考了更新和删除的实现,引入了版本号, 把时序数据变成了二维图形上的点,每个写入申请都带有一个版本号,版本号依照写入申请解决程序递增。
那 3.0 具体是如何做更新的?如上图所示,这些蓝色的点是你要更新的数据,数据的版本号必定比要更新的旧数据版本号大,所以咱们就引入了版本号机制。当工夫戳雷同时,版本号大的数据将更新版本号小的数据,因为版本号大的数据是后写入的数据,绝对较“新”。
以前每张表中的数据,不管在内存里还是在硬盘中,都是依照工夫戳进行排序的,但在引入了版本号之后排序规定也进行了批改。首先还是按工夫戳进行排序,在工夫戳雷同的状况下要依照版本号进行排序,在这样的排序流程下,咱们就能够把数据更新用一个近乎于追加的形式解决,查问引擎负责将最初的数据合并整顿后失去最终后果。
在 3.0 中,时序数据的删除机制也齐全重做。相比 2.0,3.0 反对的过滤条件也明显增加,比方 where tag、where timestamp 等等。那具体底层是如何实现的呢?首先还是基于版本号机制。
对于删除操作来说,咱们须要记录开始和完结的工夫区间,以及删除申请的版本号,如上图所示,一个删除申请对应二维图上的一个紫色矩形,这个矩形外部的所有点都被删除了。 在 3.0 中,时序数据删除时会追加一条(st, et, version)的记录元组,在查问时,查问引擎会将写入的数据和删除记录元组进行最终的合并,并失去删除后的最终后果。
采纳这种机制,删除性能对于写操作变得绝对简略了,然而对于查问而言,则变得更加简单了。查问在合并数据时,要判断记录是不是被删除了,即查看记录是不是在所有的删除区间(矩形)外面,这是相当耗时的。
TDengine 3.0 采纳 Skyline 算法来进步有删除数据下的查问速度。这一算法的实质是结构一个点数据,用来代替所有的删除区间,如上图中的两个矩形删除区域能够用三个点来示意。原来对于每条记录都要查看是否被删除的算法,当初变成了一个单向扫描过滤的操作,从而大大提高查问速度。
多表场景下的存储优化
在时序数据场景下,海量数据代表的也有可能是海量的表。在有些业务场景下表数量十分之多,但采集的数据却很少,比方有一千万张表,但每天每张表采集数据只有两条,这种场景对于 2.0 的存储构造并不是很敌对。
在 TDengine 2.0 中,一张表会落在硬盘上,一个数据块外面只有一张表的数据;如果有一千万张表,每张表两条数据,那一个数据块就只有两条记录,压缩都没法压缩,而且这种状况下压缩的话还会导致数据的收缩。
TDengine 的超级表上面所有子表都共享同一个 schema,这样的话在 last 文件里咱们就能够将同一个超级表下不同子表的数据合并成一个数据块——原来一个表里只有两条记录,但如果能把一百张表的两条记录合并成一个数据块,那就有两百条记录。然而这可能须要读一百次表,如果咱们就想读一次那要怎么操作呢?
为了解决这个问题,3.0 在数据块中又加了一个属性,那就是表的 UID。 在 3.0 的 last 文件中,数据块中的数据会依照 UID、Timestamp、Version 排序,即先比拟 UID、UID 雷同的状况下比拟工夫戳,工夫戳雷同的状况下比拟版本号,这样的话就能够更无效地解决多表低频的场景了。
结语
TDengine 是一个开源产品,3.0 的代码也曾经凋谢在了 GitHub 上,十分心愿大家可能踊跃地参加进来,去下载和体验。也欢送大家退出 TDengine 的生态交换群,和咱们以及 TDengine 的关注者和支持者一起交换和探讨。
想理解更多 TDengine Database 的具体细节,欢送大家在 GitHub 上查看相干源代码。