检索技术核心 20 讲
陈东
前数禾科技 CTO,前奇虎 360 商业化资深总监
21492 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 29 讲
检索技术核心 20 讲
15
15
1.0x
00:00/00:00
登录|注册

17 | 存储系统:从检索技术角度剖析LevelDB的架构设计思想

课堂讨论
SSTable文件的检索加速
SSTable的分层管理设计
内存数据存储到磁盘
LevelDB存储系统架构设计思想

该思维导图由 AI 生成,仅供参考

你好,我是陈东。
LevelDB 是由 Google 开源的存储系统的代表,在工业界中被广泛地使用。它的性能非常突出,官方公布的 LevelDB 的随机读性能可以达到 6 万条记录 / 秒。那这是怎么做到的呢?这就和 LevelDB 的具体设计和实现有关了。
LevelDB 是基于 LSM 树优化而来的存储系统。都做了哪些优化呢?我们知道,LSM 树会将索引分为内存和磁盘两部分,并在内存达到阈值时启动树合并。但是,这里面存在着大量的细节问题。比如说,数据在内存中如何高效检索?数据是如何高效地从内存转移到磁盘的?以及我们如何在磁盘中对数据进行组织管理?还有数据是如何从磁盘中高效地检索出来的?
其实,这些问题也是很有代表性的工业级系统的实现问题。LevelDB 针对这些问题,使用了大量的检索技术进行优化设计。今天,我们就一起来看看,LevelDB 究竟是怎么优化检索系统,提高效率的。

如何利用读写分离设计将内存数据高效存储到磁盘?

首先,对内存中索引的高效检索,我们可以用很多检索技术,如红黑树、跳表等,这些数据结构会比 B+ 树更高效。因此,LevelDB 对于 LSM 树的第一个改进,就是使用跳表代替 B+ 树来实现内存中的 C0 树。
好,解决了第一个问题。那接下来的问题就是,内存数据要如何高效存储到磁盘。在第 7 讲中我们说过,我们是将内存中的 C0 树和磁盘上的 C1 树归并来存储的。但如果内存中的数据一边被写入修改,一边被写入磁盘,我们在归并的时候就会遇到数据的一致性管理问题。一般来说,这种情况是需要进行“加锁”处理的,但“加锁”处理又会大幅度降低检索效率。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

LevelDB是一个由Google开源的存储系统代表,其性能卓越,随机读性能可达6万条记录/秒。该系统基于LSM树优化,使用跳表代替B+树实现内存中的C0树,并采用读写分离设计将内存数据高效存储到磁盘。LevelDB采用延迟合并的设计,先将Immutable MemTable顺序快速写入磁盘,再对这些SSTable文件进行合并,避免了昂贵的合并代价。LevelDB提出了快速提高磁盘中多个SSTable文件的检索效率的解决方案。LevelDB的分层管理设计和分层思想,以及利用缓存加速检索SSTable文件的过程,为读者提供了深入了解存储系统架构设计思想的重要参考。LevelDB通过限制每层的文件总容量大小,能保证做多路归并时,会有一个开销上限。LevelDB使用索引与数据分离的设计思想,将SSTable分为数据存储区和数据索引区两大部分。LevelDB的设计使得查询操作可以快速被限定在有限的SSTable中,从而达到了加速检索的目的。LevelDB的优化设计体现了其在检索技术方面的创新和高效性,为读者提供了深入了解存储系统架构设计思想的重要参考。LevelDB提升检索效率的优化方案包括使用跳表代替B+树、读写分离设计、延迟合并、分层和滚动合并、多层二分查找、索引与数据分离设计、BloomFilter和缓存技术等,综合运用多种检索技术,体现了高性能系统的综合应用。 LevelDB的实现是各种检索技术的落地实践,对于读者的学习具有重要帮助。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《检索技术核心 20 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(30)

  • 最新
  • 精选
  • 当 MemTable 的存储数据达到上限时,我们直接将它切换为只读的 Immutable MemTable,然后重新生成一个新的 MemTable ------------------ 这样的一个机制,内存中会出现多个Immutable MemTable 吗? 上一个Immutable MemTable 没有及时写入到磁盘

    作者回复: 这是一个好问题!实际上,这也是levelDB的一个瓶颈。当immutable memtable还没有完全写入磁盘时,memtable如果写满了,就会被阻塞住。 因此,Facebook基于Google的levelDB,开源了一个rocksDB,rocksDB允许创建多个memtable,这样就解决了由于写入磁盘速度太慢导致memtable阻塞的问题。

    2020-05-08
    2
    26
  • LevelDB 分层的逻辑没有理解 当 Level0 层 有四个 SSTable 的时候,这时候把这个四个进行归并,然后放到 Level1 层,这时候 Level0 层清空,这个有个问题是 当进行归并后 后生成几个 SSTable ,这里是有什么规则吗? 接下来,然后 Level0 层在满了之后,是Level0 层的每个 SSTable 分别与 Level1 所有的 SSTable 进行多路归并吗? 再然后 Level1 层满了之后,是按照顺序取 Level1 层的一个 SSTable 与 Level2所有的 SSTable 进行多路归并吗? 这样会有大量的 磁盘 IO,老师说利用判断重合度进行解决的? 这个重合度是怎么计算计算判断的呢? ============================== 老师的文中的这句话没有看明白: 在多路归并生成第 n 层的 SSTable 文件时,LevelDB 会判断生成的 SSTable 和第 n+1 层的重合覆盖度,如果重合覆盖度超过了 10 个文件,就结束这个 SSTable 的生成,继续生成下一个 SSTable 文件

    作者回复: 你的问题我重新整理一下,尤其是level 0层怎么处理,这其实是一个很好的问题: 问题1:level 0层到level 1层合并的时候,level 0层是有多少个sstable参与合并? 回答:按道理来说,我们应该是根据轮流选择的策略,选择一个level 0层的sstable进行和下层的合并,但是由于level 0层中的sstable可能范围是重叠的,因此我们需要检查每一个sstable,将有重叠部分的都加入到合并列表中。 问题2:level n层中的一个sstable要和level n+1层中的所有sstable进行合并么? 回答:不需要。如果level n层的sstable的最大最小值是begin和end,我们只需要在level n+1层中,找到可能包含begin到end之间的sstable即可。这个数量不会超过10个。因此不会带来太大的io。 问题3:为什么level n层的sstable和level n+1层的合并,个数不会超过10个? 回答:在level n层的sstable生成的时候,我们会开始判断这个sstable和level n+1层的哪些sstable有重叠。如果发现重叠个数达到十个,就要结束这个sstable文件的生成。 举个例子,如果level n+1层的11个sstable的第一个元素分别是[100,200,300,400,……,1000,1100],即开头都是100的整数倍。那么,如果level n层的sstable文件生成时,准备写入的数据就是[100,200,300,400,……,1000,1100],那么在要写入1100的时候,系统会发现,如果写入1100,那么这个sstable文件就会和下一层的11个sstable文件有重叠了,会违反规则,因此,这时候会结束这个sstable,也就是说,这个sstable文件中只有100到1000十个数。然后1100会被写入到一个新的sstable中。

    2020-05-09
    2
    19
  • 吴小智
    之前看过基于 lsm 的存储系统的代码,能很好理解这篇文章。不过,还是不太理解基于 B+ 树与基于 lsm 的存储系统,两者的优缺点和使用场景有何不同,老师有时间可以解答一下。

    作者回复: lsm树和b+树会有许多不同的特点。但是如果从使用场景来看,最大的区别就是看读和写的需求。 在随机读很多,但是写入很少的场合,适合使用b+树。因为b+树能快速二分找到任何数据,并且磁盘io很少;但如果是使用lsm树,对于大量的随机读,它无法在内存中命中,因此会去读磁盘,并且是一层一层地多次读磁盘,会带来很严重的读放大效应。 但如果是大量的写操作的场景的话,lsm树进行了大量的批量写操作优化,因此效率会比b+树高许多。b+树每次写入都要去修改叶子节点,这会带来大量的磁盘io,使得效率急剧下降。这也是为什么日志系统,监控系统这类大量生成写入数据的应用会采用lsm树的原因。

    2020-05-08
    3
    17
  • 王坤祥
    1. 既然要查找的数据在某一层查到了,按照LevelDB的分层管理的设计,即使下一层数据也存在,数据也是一样的,没有必要再去查找下一层的数据了。 2. “在多路归并生成第 n 层的 SSTable 文件时,LevelDB 会判断生成的 SSTable 和第 n+1 层的重合覆盖度,如果重合覆盖度超过了 10 个文件,就结束这个 SSTable 的生成,继续生成下一个 SSTable 文件。”———如果通过控制sstable文件数量来限制每层容量的话,有可能每个sstable会比较小,很快就达到数量限制,可能分层作用就不明显了。

    作者回复: 1.结论是对的,不过有一个小细节要注意一下,下一层的数据,是更老的一个数据,不一定是“一样的”。由于它不够上一层的新,因此可以放弃。 2.是的。你把这一个约束规则找到了!因为有着这一条约束规则,可能某一层的sstable在某个时刻数量很多,但是每个文件都很小,如果通过文件数量限制,就使得这一层可能存不了什么数据。因此用总容量进行限制更合理。

    2020-05-18
    8
  • xaviers
    老师,不好意思哈,再追问一下😬那为啥用change buffer + WAL优化后的MySQL的写性能还是不如LSM类的存储系统啊?原因是啥啊

    作者回复: 因为对于b+树,当内存中的change buffer写满的时候,会去更新多个叶子节点,这会带来多次磁盘IO;但lsm当内存中的memtable写满时,只会去写一次sstable文件。因此它们的主要差异,还是在怎么将数据写入磁盘上。 当然所有的系统设计都是有利有弊,要做权衡。b+树写入磁盘后,随机读性能比较好;而lsm树写磁盘一时爽,但要随机读的时候就不爽了,它可能得在多层去寻找sstable文件,因此随机读性能比b+树差。

    2020-05-08
    7
  • 时隐时现
    老师好,有2个问题: 1、内存中的C0树,采用跳表替换掉B+树,检索效率会有提升吗?我一直觉得两者是差不多的吧,什么场景下跳表会比B+树性能高很多? 2、滚动合并应该是后台操作,在合并的过程中,相应的sstable应该是被写锁锁定的吧?此时如果有应用执行读,会不会被阻塞?如果不阻塞,如何保证读写一致性?

    作者回复: 1.虽然跳表和b+树在时间代价上都是一个量级的,但是跳表的插入删除都很简单,而b+树的插入删除会有节点分裂,节点合并,节点调整等问题,因此从工程效率来看,在纯内存的环境下,b+树并不比跳表和红黑树更合适。 2.所有的sstable都是只读的,不可更改。新的sstable生成了以后,老的sstable才会被删除,读操作才会转移到新的sstable上。因此,sstable不会被同时读写,没有读写阻塞的问题。

    2020-05-11
    2
    6
  • 在评论下回复老师看不到啊,那就在评论问一下 第一问还有点疑问:level0层的每个sstable可能会有范围重叠,需要把重叠的部分提取到合并列表,这个这个合并列表是什么?还有就是提取之后呢,还是要遍及level0层的每个sstable与level1层的sstable进行归并吗? 还有个问题就是:当某层的sstable向下层转移的时候,碰巧下层的空间也满了,这时候的处理方案是向下层递归吗?一直往下找,然后在向上处理

    作者回复: 1.合并列表其实就是记录需要合并的sstable的列表。实际上,每次合并时,系统都会生成两个合并列表。 以你提问的level 0层的情况为例,先选定一个要合并的sstable,然后将level 0层中和它范围重叠的sstable都加入到这个列表中;这就是合并列表1。 然后,对于合并列表1中所有的sstable,我们能找到整体的范围begin和end。那么我们在下一层中,将和begin和end范围重叠的所有sstable文件加入合并列表2。 那么,对于合并列表1和合并列表2中的所有的sstable,我们将它们一起做一次多路归并就可以了。 2.如果下层空间满了,没关系,先合并完,这时候,下层空间就超容量了。那么,我们再针对这一层,按之前介绍的规则,选择一个sstable再和下层合并即可。 PS:再补充一下知识点:合并的触发条件。 系统会统计每个level的文件容量是否超过限制。超过上限比例最大的,将会被触发合并操作。

    2020-05-09
    2
    6
  • 那时刻
    有两个问题,请教下老师。 1。在多路归并生成第 n 层的 SSTable 文件时,如何控制当前层最大容量呢?如果超过当前层的容量是停止计算还是把多余的量挪到下一层? 2。数据索引区里meta index block,当存在多个过滤器时,对过滤器进行索引。这是涉及到filter block过滤么?

    作者回复: 1.不用停止计算,而是算完后,判断容量是否达到上限,如果超过,就根据文中介绍的选择文件的方式,将多余的文件和下一层进行合并。 2.如果存在多个filter block,而且每个filter都很大的话(比如说bloomfilter就有许多数据),将所有的filter都读入内存会造成多次磁盘IO,因此需要有metaphor index block,帮助我们只读取我们需要的filter即可。

    2020-05-08
    4
  • Bachue Zhou
    这个数据库在海量数据的情况下真的很快吗,我总感觉一般般的样子啊。 1. 层次没有上限 单层文件总容量却有上限,因此极端情况下需要搜索的文件依然很多,虽然每个文件有布隆过滤器预搜索,所以单个文件检索性能还不错,但需要一层层打开文件解析文件然后开始搜索,文件数量如果很多,则性能不佳 2. 如果是范围检索,则注定所有层次都必须被查询,性能不佳 3. 每个文件尺寸有上限,而且很小,意味着文件数量很多,文件打开数就会很多,当达到通常的 65536 的上限时(如果每个文件 2m 大小,那么也就存了 128g,实际上由于进程自身也有文件打开数开销,实际上能提供给 leveldb 的文件打开数配额会远远小于这个值),就只能被迫使用 lru 来关闭一些不常用的小文件了,如果频繁打开解析关闭小文件时,性能不佳 4. 多路归并多个文件的数据,意味着磁盘在多个文件中来回寻道,哪怕只有最多十个文件,性能也不佳 5. 无法理解为何只选一个文件参与和下一层的归并,选一个文件和选两个文件的区别在哪里?

    作者回复: 可以看出来你思考得很深入,包括分析解决方案的缺陷和局限性,这是很好的学习方法。我也说一下我对你的问题的一些解读。 lsm类型的数据库不是用来做随机检索或范围检索的最佳选择,它更适合的是写多读少,尤其是读近期数据的场景。 至于打开多个文件效率低,包括寻道性能低的问题,这其实可以通过缓存来部分解决。但的确当大量文件被打开的时候,磁盘读写性能是不高的。因此文件的分层,缓存的使用,其实都是为了解决这个问题。包括你的第五个问题,为什么不对多个文件进行处理,也有这一方面的原因。(以上是对1-4问题的回答)。 至于第五个问题,为什么只选一个文件,我的理解是为了保证一次合并不要涉及过多的文件,减少io。实际上,只要知道了这个合并原理,一次选两个或三个文件来合并也不是不可以。比如说seek compaction过程,就是可以将经常查询失败的sstable放在合并列表中,进行批量处理。主要还是要注意io性能。

    2021-01-06
    2
    3
  • piboye
    如果把sstable换成B+树,也有bloomfilter,是不是可以不用限制文件为2m的大小?

    作者回复: 其实,sstable是lsm树的基本单位,大约等于b+树中的叶子节点。你会发现,b+树中的每个叶子节点其实也不大,仅仅是一个block大小而已。 或者你也可以将两个sstable用分隔符区分,然后写到同一个文件中,这样文件个数会减少,然后每个文件也会更大。但这样和维持两个sstable文件有什么区别呢? 其实回到磁盘读写的本质来看,多个小文件和一个大文件相比,只是多了一些文件打开的操作(读取对应inode),后面的读取数据其实都是以block为单位进行的,并没有本质区别。 因此,只要能高效管理多个sstable文件,那么整体性能能得到保证,那么读取性能上和一个大文件差异不大。

    2020-09-18
    3
收起评论
显示
设置
留言
30
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部