LSM-Tree(Log Structured Merge Tree)是数据库领域内较高效的key-value存储结构,被广泛应用于工业界数据库系统,如经典的单机kv数据库LevelDB、RocksDB,以及被诸多分布式NewSQL作为底层存储引擎。
本期将由腾讯云数据库高级工程师韩硕来为大家分享基于LSM-Tree存储的数据库性能改进,重点介绍近年来学术界对LSM-Tree的性能改进工作,并探讨这些改进措施在工业界数据库产品中的应用情况以及落地的可能性。以下是分享实录:
1. LSM-Tree基本结构
LSM-Tree全称为“Log Structured Merge Tree”,是一种基于磁盘存储的数据结构。1996年Patrick O’Neil等人在信息系统期刊上发表了一篇题名为“Log Structured Merge Tree”的论文,首次提出LSM-Tree结构。相比于传统的B+树,LSM-Tree具有更好的写性能,可以将离散的随机写请求转换成批量的顺序写操作,无论是在RAM、HDD还是在SSD中,LSM-Tree的写性能都更加优秀。
作为高效的key-value存储结构,LSM-Tree已被广泛应用到工业界数据库系统中,如经典的单机kv数据库LevelDB、RocksDB,以及被诸多分布式NewSQL作为底层存储引擎,近日发布的TDSQL新敏态引擎存储模块也运用了LSM-Tree结构。LSM-Tree结构有较多优点:写性能强大、空间利用率高、较高的可调参特性、并发控制简单、有完备的修复方案等。
LSM-Tree的本质是基于out-place update即不在原地更新。下图展示了in-place update即原地更新与out-place update即不在原地更新的区别。
在in-place update中,数据更新操作是将数据的新值直接覆盖旧值,但会带来随机读写问题。在out-place update中,数据更新操作则是将新值按时间顺序追加到文件末尾,通常会带有版本号用于标记新值或旧值,一般为顺序写,因此写性能更好,但缺点是会产生冗余数据,在读性能和空间利用率上也无法与in-place update相比。
当前主流的LSM-Tree基本结构如下图,分为内存和磁盘两部分。内存采用MemTable数据结构,可以通过B+树或跳表实现。MemTable用于接受最新的数据更新操作,维护内部数据in-tree逻辑上的有序。磁盘中的部分数据存储物理有序。数据按照层进行堆放,层次越往下,数据写入的时间就越早。每层内部按是否有序可划分为一个或多个sorted run。一个sorted run内部的数据必定有序(通常指物理上有序)。sorted run数据可进一步划分为不同的SSTable。当MemTable中的数据写满时,其会向L0进行落盘操作即Flush操作。如果LSM-Tree的某一层也达到容量阈值,就会向下合并,将同样的key的过期版本清除,即compaction操作。
RocksDB是基于LSM-Tree的存储引擎,其基本结构如下图。它与LSM-Tree基本结构在主体上保持一致,不同点在于RocksDB的内存中增加了Immutable MemTable部分,这是用于Flush操作的写缓存机制。当MemTable中的数据写满时,会先暂存到Immutable MemTable中,再通过后台的异步线程将Immutable MemTable慢慢落盘到磁盘中的SST基本件。RocksDB中还增加了WAL日志,用于crash时的数据恢复。
LSM-Tree支持的查询可分为点查与范围查询两大类,对应的执行方式如下: ● 点查:先查MemTable,再从SST中的Level0、Level1…...一层层向下探查,找到数据就返回。由于上层数据必定比下层数据的版本新,因此返回的都是最新数据。 ● 范围查询:每一层都会找到一个匹配数据项的范围,再将该范围进行多路归并,归并过程中同一key只会保留最新版本。
LSM-Tree性能的衡量主要考虑三类因素:空间放大、读放大和写放大。
第一类因素是空间放大。在LSM-Tree中所有写操作都是顺序追加写,数据的更新操作则是通过创建一个新的空间来存储新值,即out-place update。与此同时,因为旧值不会立即被删除,因此会占用部分空间。实际上这部分冗余数据占用空间的大小要远大于有效数据本身,这种现象被称为“空间放大”。LSM-Tree会利用compaction操作来清理旧数据从而降低空间放大。
第二类因素是读放大。在LSM-Tree、B+树等外存索引结构中,进行读操作时需要按从上到下的顺序一层层去读取存储节点,如果想读一条数据,就会触发多次操作,即一次读操作所读到的数据量实际上要远大于读目标数据本身,从而影响读性能,这种现象被称为“读放大”。 第三类因素是写放大。在LSM-Tree中,compaction操作会将多个SST文件反复读取,合并为新的SSTable文件后再次写入磁盘,因此导致一条kv数据的多次反复写入操作,由此带来的IO性能损失即写放大。
LSM-Tree的初衷是想通过空间放大和读放大来换取写放大的降低,从而达到极致的写性能,但也需要做好三方面因素的权衡。EDBT 2016的一篇论文首先提出RUM猜想(R为read,U为update,M为memory usage,RUM为三者缩写)。该论文认为,三者之间存在权衡关系,无法使得三个方面的性能都达到最优,因此必须在三者之间进行有效权衡。
以compaction操