时长20:25大小18.71M
作者回复: 这是一个好问题!实际上,这也是levelDB的一个瓶颈。当immutable memtable还没有完全写入磁盘时,memtable如果写满了,就会被阻塞住。
因此,Facebook基于Google的levelDB,开源了一个rocksDB,rocksDB允许创建多个memtable,这样就解决了由于写入磁盘速度太慢导致memtable阻塞的问题。
作者回复: lsm树和b+树会有许多不同的特点。但是如果从使用场景来看,最大的区别就是看读和写的需求。
在随机读很多,但是写入很少的场合,适合使用b+树。因为b+树能快速二分找到任何数据,并且磁盘io很少;但如果是使用lsm树,对于大量的随机读,它无法在内存中命中,因此会去读磁盘,并且是一层一层地多次读磁盘,会带来很严重的读放大效应。
但如果是大量的写操作的场景的话,lsm树进行了大量的批量写操作优化,因此效率会比b+树高许多。b+树每次写入都要去修改叶子节点,这会带来大量的磁盘io,使得效率急剧下降。这也是为什么日志系统,监控系统这类大量生成写入数据的应用会采用lsm树的原因。
作者回复: 1.合并列表其实就是记录需要合并的sstable的列表。实际上,每次合并时,系统都会生成两个合并列表。
以你提问的level 0层的情况为例,先选定一个要合并的sstable,然后将level 0层中和它范围重叠的sstable都加入到这个列表中;这就是合并列表1。
然后,对于合并列表1中所有的sstable,我们能找到整体的范围begin和end。那么我们在下一层中,将和begin和end范围重叠的所有sstable文件加入合并列表2。
那么,对于合并列表1和合并列表2中的所有的sstable,我们将它们一起做一次多路归并就可以了。
2.如果下层空间满了,没关系,先合并完,这时候,下层空间就超容量了。那么,我们再针对这一层,按之前介绍的规则,选择一个sstable再和下层合并即可。
PS:再补充一下知识点:合并的触发条件。
系统会统计每个level的文件容量是否超过限制。超过上限比例最大的,将会被触发合并操作。
作者回复: 你的问题我重新整理一下,尤其是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中。
作者回复: 首先,levelDB并没有“脏缓存”的问题。因为lsm树和b+树不一样。
b+树的缓存对应着磁盘上的叶子节点,叶子节点是可以被修改的,因此会出现缓存在内存中的数据被修改,但是磁盘对应的叶子节点还未修改的“脏缓存”问题。
而levelDB中,data block存的是sstable的数据,而每个sstable文件是只读的,不可修改的,因此不会出现“脏缓存”问题。
另一点,如果缓存数据被大片读入的新数据驱除,是否会有优化方案?这其实就依赖于lru的具体实现了(比如分为old和young区),levelDB本身并没有做特殊处理。
作者回复: 因为对于b+树,当内存中的change buffer写满的时候,会去更新多个叶子节点,这会带来多次磁盘IO;但lsm当内存中的memtable写满时,只会去写一次sstable文件。因此它们的主要差异,还是在怎么将数据写入磁盘上。
当然所有的系统设计都是有利有弊,要做权衡。b+树写入磁盘后,随机读性能比较好;而lsm树写磁盘一时爽,但要随机读的时候就不爽了,它可能得在多层去寻找sstable文件,因此随机读性能比b+树差。
作者回复: 你说得对,在MySQL的b+树的具体实现中,其实借鉴了许多lsm树的设计思想来提升性能,比如使用wal技术+change buffer,然后批量写叶子节点,而不是每次都随机写。这样就能减少磁盘IO。的确比原始的b+树快。
不过在大批量写的应用场景中,这样优化后的b+树性能还是没有lsm树更好。因此日志系统这类场景还是使用lsm树更合适。
作者回复: 1.没错,最新的数据在上层,所以上层能找到,就不需要去下层读旧数据了。
2.是的,由于在生成sstable文件时,有这么一个限制:新生成的sstable文件不能和下层的sstable覆盖度超过十个,因此可能会生成多个小的sstable文件。那如果只看文件数的话,多个小的sstable文件可能容量和下一层差不多,这样就没有了分层的作用了。
作者回复: 1.不用停止计算,而是算完后,判断容量是否达到上限,如果超过,就根据文中介绍的选择文件的方式,将多余的文件和下一层进行合并。
2.如果存在多个filter block,而且每个filter都很大的话(比如说bloomfilter就有许多数据),将所有的filter都读入内存会造成多次磁盘IO,因此需要有metaphor index block,帮助我们只读取我们需要的filter即可。
作者回复: 问题1:你进一步去思考删除问题。这一点很好。数据删除时,是会将记录打上一个删除标记,然后写入sstable中。
sstable和下一层合并时,对于带着删除标记的记录,levelDB会判断下层到最后一层是否还有这个key记录,有两种结果:
1.如果还有,那么这个删除标记就不能去掉,要一直保留到最后一层遇到相同key的时候才能删除;
2.如果没有,那么就可以删除掉这个带删除标记的记录。
问题2:因为在进行合并时,新生成的sstable会受到一个约束:如果和下一层的sstable重叠数超过了十个,就要停止生成这个sstable,要再继续生成一个新的sstable。这个机制会导致我们不好控制文件的个数。不如限定容量更合适。
作者回复: 哈哈,凌晨更新是系统默认上线操作,是由极客时间的工作人员们负责的,他们辛苦了。
作者回复: 你思考得很仔细,的确是的。sstable中,每一条记录都有一个标志位,表示是否是删除。这样就能避免误查询。
对于有删除标志的记录,其实查询流程是一致的,就是查到数据就返回,不再往下查。然后看这个数据的状态位是有效还是删除,决定是否使用。
作者回复: 1.你提到的这个问题非常好,lsm的范围查询比较弱,需要遍历。一种优化思路是先在level 1层中找到range,然后基于start和end的位置,去下一层再找start和end的位置。这样能提高范围查询的性能。
2.我说一下我的理解,由于有“生成的每个sstable和下一层的sstable重合度不能超过十个”这个约束,所以sstable生成过程中可能随时被截断,因此不好控制sstable的数量。不如控制容量简单。