LSM-Tree的结构
LSM-Tree从存储格式上分为内存中的memtable / immutable memtable作为数据插入首先进入的数据结构,以及磁盘上的sstable作为底层持久化存储的文件结构。这其中,分层的概念主要关乎于磁盘上的sstable。在一个LSM-Tree的存储引擎中,我们会把磁盘上的sstable按照金字塔的形式从上而下组织成多层结构,通过初始化设置的各个层级的阈值来控制该层的数据量。
当数据量超过阈值时就会触发compaction,从该层开始逐一向下层合并,直到所有层级的数据都在阈值范围之内。compaction的机制可以大致理解为一个归并排序的过程,根据一定的条件从Ln层和Ln+1层各自选择需要合并的sstable文件,然后将两者合并并排序,最后将新文件写入Ln+1中(有关compaction的问题以及优化方式也会在另外一篇文章中详细讲述)。因此我们可以看到,一个新写入的数据会经过内存中的memtable,然后随着memtable的下刷到达磁盘上的L0层,然后再随着后续的compaction到达L1, L2以及更下层。熟悉这个大概的写入流程对理解为什么要分层尤为重要。
为什么要分层?
先放结论:为了将I/O的开销最小化。
- 从空间放大的角度
-
不分层
因为L0无法保证全局有序,所以这里假定有L0和L1两层,L1存放着全量的数据n,L0存放的数据量为memtable的个数*memtable的阈值大小,整体大小近似为n。 -
分层
最下层一定包含全部的数据量n,Ln层的数据量大约是Ln+1层的十分之一,那么7层架构下总共的数据量近似于1.1n(n + 0.1n + 0.01n + 0.001n + …)可以看到,分层与不分层所带来的空间放大差距不大,不足以作为一个解释为什么要分层的依据。
-
- 从write stall的角度
- 不分层
依旧假定只有L0和L1两层,然后我们再做这样一个假设:L0当中只有一个文件,且这个文件的key range被L1层所有的sst文件包含。那么这个时候compaction会怎么做呢?对L1层的每一个文件,它都会做一次I/O操作将文件打开,然后把L0中对应范围内的key与他们做归并排序,来得到合并后有序的新sst文件。这是一个很恐怖的事情,因为这个操作带来的大量的I/O操作,阻塞住了前台的写入流程,带来了很大的write stall。 - 分层
上述的问题在分层的结构下可以得到很好的缓解,本质上来说就是将一次性的大量I/O开销分摊到了多层当中,并且在在多层的这些I/O也不是完全同步的。这样一来某个时刻下的I/O开销会小很多,相应带来write stall的latency与次数也会有所减少。
- 不分层
未来还需要分层吗?
随着新的存储介质的到来,我们或许需要重新思考分层的设计是否还是合理的。
- NVMe SSD
SSD本身拥有更快的读写速度以及相比于HDD更强的随机读取能力,而接入NVMe协议则在此基础上支持了更深度的并发操作,提供了更高的带宽以及更低的延迟。因为上文提到分层的主要目的是分摊I/O带来的开销,那么SSD本身就从硬件结构和设计上带来了比HDD更大的优势,分层与不分层所带来的I/O开销差异是否还会如此明显? - PMem
PMem基于内存技术,IOPS要比SSD快上十倍左右,相应的带宽和延迟也要更小。因此,如果不计成本的将存储介质全部换为PMem,分层是一定不需要的了,直接的内存操作就可以满足需求,整个系统也就变成了面向内存特性设计的类内存kv存储。
总而言之,存储系统这类底层系统一定要结合其基于的硬件做相应的design和tradeoff,才可以在相应的场景中性能最大化。