开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情
论文阅读: Constructing and Analyzing the LSM Compaction Design Space(三)
上一篇文章juejin.cn/post/717478… 讨论了进一步理解LSM compaction原语,分析compaction需要关注的指标,本文开始正式进行compaction 实验。
实验目标
- 性能影响:compaction会如何影响LSM引擎的性能
- 工作负载: 工作负载(workload)的分布情况(正态,randon)和组合如何影响compaction,以及进而如何影响性能?
-
这个问题其实非常难回答,组合空间太多,我都怀疑能否得出准确的结论,因为免不了引入一个变量时,其副作用未曾得知而被忽视,导致结论不准确。
- Tuning影响:LSM compaction vs tuning 是如何相互作用
环境:
在Ubuntu 20.04上的修改后的rocksdb上完成了所有实验,机器为:有8个英特尔可扩展处理器(vCPUs),频率为3.0GHz,32GB的DIMM内存,45MB的L3缓存的Amazon 虚拟机,附带4000IOPS的SSD卷。
默认设置。 除非另有说明,所有的实验都是在RocksDB设置上进行的,LSM树的大小比例为10[2127, 30].内存缓冲区被实现为一个hash-skiplist[29]。
写入缓冲区的大小被设定为8MB,可以容纳512个16KB的磁盘页[21, 22, 27, 34]。为每个磁盘页维护栅栏指针,并为每个文件构建bloomfilter,为每个条目分配10比特的内存[22, 24]。此外,我们还为数据、过滤器和索引块分配了8MB的块缓存(RocksDB默认)[27]。为了捕捉RocksDB作为LSM引擎的真正的原始性能,我们(i)为压缩分配了比写入更高的优先级,(ii)为读和写操作启用直接I/O,(iii)将内存缓冲区(或memtables)的数量限制为两个(一个不可变,一个可变),以及(iv)将负责压缩的后台线程数量设置为1。
默认配置不是很好,这些默认配置都不是我日常使用的最佳配置,例如blockcache过小,memtable过少,writebuffer过小导致非常频繁的compaction,可能这正是作者本意?compaction的分析如此地复杂,我们可以看看作者是如何打开,测量这个黑盒。
工作负载。 除非另外提到,ingestion和查找是统一生成的,一个Key-Value的平均大小是128B,有4B个键。我们改变了插入的数量,最高为228 。由于compaction性能被证明与数据大小无关,并且为了实验更多的配置,我们用10M的插入进行基本实验,在查找方面都是交错和串行的。在每组实验之前,我们会进一步介绍工作负载的规格。
compaction哪里被证明是和数据大小无关? 作者居然不没加引用。或许后面他会自己证明?
介绍。 对于每个实验,我们将主要的观察结果 (O)以及TakeAway message(TA)信息。为了节省篇幅,我们只讨论最有趣的结果。注意TSD和TSA,在没有删除的情况下会回落到LO+1,因此,在没有删除的实验中被省略了。
性能的影响
我们首先分析了压缩对LSM-引擎的ingestion、查找和整体性能的影响。
5.1.1 数据加载。在这个实验中,我们在一个空的数据库中插入统一生成的1000万个键值条目,以量化原始ingestion和压缩性能。
O1:压缩导致高数据移动。 图4(a)显示,由于压缩导致的整体(读和写)数据移动明显大于实际ingestion的数据大小。
下图中
在Levle LSM设计中,Full移动了比原始导入数据的 63x倍的(32倍的读和31倍的写)size。
Tier的数据移动要小得多,但是,它仍然是数据大小的23倍。
1-Lvl的数据移动规模与局部压缩中的分级策略相似。
以上观察突出了由于compaction导致读放大问题,导致设备带宽利用率低。
O2:部分压缩减少了数据的移动,但要付出增加压缩次数的代价 。 我们现在把我们的注意力转移到注意到平整度的不同变化。
图4(a)显示分级的部分压缩导致的数据移动量比完全压缩少34%~56%。原因有两个方面:
(1)一个文件与它的父级没有重叠,只是在逻辑上进行合并。这样的伪压缩需要在内存中进行简单的元数据(文件指针)操作,而且没有I/O。
(2) 较小的压缩粒度,通过选择具有(i)最小的重叠,(ii)最多的更新,或(iii)最多的墓碑的文件进行压缩,减少整体数据移动。.具体而言。LO+1(和LO+2)被设计为挑选与父辈i+1(和祖辈i+2)级别重叠最少的文件。它们比其他的部分压缩策略少移动10%~23%的数据。
TODO:怎么不分析下Tier的数据移动为何这么少(写放大最小)。并不是数据移动最少就最好,因为可能为此付出其他代价,比如CPU耗损。相比我希望更少更快地清理掉不必要的数据。
图4(b)显示,局部compaction策略以及1-Lvl比full compaction多执行4倍的compaction工作,这等于树级的数量。请注意,对于部分compaction的LSM树来说,每一次缓冲区刷新都会触发对所有L层的级联compaction,而在全层compaction系统中,这种情况发生在一个层级满的时候(每T次compaction)。最后,由于Tier和Full都是full compaction,所以compaction次数是相似的。
TA I:较大的compaction粒度会导致较少但较大的com¬ pactions。全级compaction比局部compaction少了1/L的compactions例程,但是,full compaction每次compaction的数据量多了近2L倍。
O3:Full Compaction@Leveling具有最高的平均compaction延时。
正如预期的那样,完全compaction具有最高的平均延迟(比局部compaction高1.2倍,比tiering高2.1倍)。平均compaction延迟被观察到与每次compaction的平均数据移动量成正比。
Full compaction既不能利用pseudo-compaction(这个不知道如何理解,但是不太重要)的优势,也不能优化compaction过程中的数据移动,因此,平均来说,每次compaction的数据移动量仍然很大。
1-Lvl在compaction延迟方面提供了最可预测的性能。图4(c)显示了所有策略的平均compaction延迟,以及中位数(P50)、第90百分位数(P90)、第99百分位数(P99)和最大值(P100)。尾部压缩延迟在很大程度上取决于工作负载执行期间触发的最大压缩作业所移动的数据量。我们观察到,尾部延迟(P90、P99、P100)对于Full来说更容易预测,而局部compaction,特别是Tiering,由于数据移动策略的不同,具有很高的可变性。
图4(c)中展示的压缩延迟可以分解为IO时间和CPU时间。我们观察到,无论采用何种compaction策略,CPU的工作量都是50%左右。在compaction过程中,CPU周期花费在:
(1)获得锁和快照。
(2) 合并条目
(3) 更新文件指针和元数据
(4)compaction后同步输出文件。其中,在内存中对数据进行排序-合并所花费的时间占主导地位。
尾部写入延迟在tiering中是最高的。 图4(d)显示,tiering的尾部写入延迟是最高的。tiering的尾部写延迟比完全写延迟大2.5倍,比局部compaction写延迟大5~12倍。
RocksDB中的tiering优化了写和并寻求机会将所有数据压缩到一个大的单层。这种设计实现了较低的平均写入延迟(图5(b)),但代价是在最坏的情况下,即两个连续层次之间的重叠度非常高时,会出现长时间的写入停顿。
这很重要,rocksdb 的Tiering压缩可能会卡顿,而且未持续曾优化。未来我将在线上用learner节点去灰度调优新的compaction策略。上图说write stall 时间低于25ms,请注意,这是本次实验环境配置下的策略,当你使用更大的write buffer,一次flush会需要更大的级联compaction,这个长尾延迟将会很大,经验表明,我们出现过>5s的情况。
Full也比局部compaction的尾部写入停顿高2倍,因为当多个连续水平接近饱和时,一个write buffer的flush会导致级联压缩。1-Lvl也有较高的尾部写入延迟(write stall导致),因为第一层是作为tiering实现的。
TA II: Tier可能会导致长时间的写入停顿。Tier的尾部写停滞是25ms,而对于局部压缩(旧),它低至1.3ms。
查询数据
对之前生成的已经加载的数据库进行了100万次的点查询(有10M个key)。查询是这些Key中均匀分布,我们在0和1之间改变空查询的比例。具体来说,α=0表示我们只考虑非空的查询,而对于α=1,我们对不存在的键的查询。我们还执行了1000个范围查询,同时改变了它们的选择性。
O4:点查询延迟对于tiering来说是最高的,对于Full压缩来说是最低的。
图4(e)显示,点查询在Full leveling中表现最好,而在tiering中最差。对于非空查询,在tiering的情况下,点查询的平均延迟比使用leveling compaction 的查询要高出1.1-1.9倍之间。而在空查询上的查询要高2.2倍。
请注意,对非空查询必须始终执行至少一个I/O(除非它们被缓存了)。对于大小比为T的树中的非空查找,理论上,tiering的查找成本应该比其在level compation的存储上高T×倍。然而,这个最坏情况下的成本并不总是准确的;在实践中,它取决于
(i) blockcache的大小和缓存策略,
(ii) 检索键的时效性(没看懂)
(iii)compaction策略的实现。
这段其实蛮奇怪,tiering和leveling的点查询的区别应该没这么大。根据前文,作者认为数据的布局非常影响性能,或许在loaded 10M的key中,他们在写入的时候,使用不同的compaction策略,导致其摆放位置非常不同,进而影响了点查性能(此时数据应该不要在blockcache)中。果然,后文说道block cache是数据大小的0.05%。请注意,这不是我们日常使用的case,我们通常会将cache设置的比较大。但O4这观察蛮有意思,我们知道,LSM中的blockCache不可能全量放入所有数据,不同的压缩策略对点查的影响是此前没有想到的,anyway,从图e也看到,这里的数据应该也没差别很大。
RocksDB-tiering总体上比教科书上的tiering有更少的sorted run。考虑到块缓存和时效性,在点查工作负载观察到的tiering成本比full compaction小于Tx1。此外,full 比局部compaction低3%-15%。
因为在Full的正常操作中,一些级别可能是完全空的,而对于部分compaction,所有级别总是接近于满的。最后,我们注意到,数据移动策略的选择并不明显影响点查询延迟,它总是受益于布隆过滤器(每键10比特)和块缓存(数据大小的0.05%)。
在空查询和非空查询数量相当的情况下,点查询的延迟会增加。 图4(e)显示了一个令人惊讶的结果,即当空查询和非空查询的比例平衡时,点查询的表现更差。直观地讲,我们可以预期,当我们有更多的空查询时(也就是说,当α增加时),延迟会减少,因为空查询所需要的唯一数据访问是由于bloomfilter的假阳性[21]。为了进一步研究这个结果,我们绘制了在图4(h)中,第90百分位数(P 90)的延迟显示了类似的情况。
当我们改变α时,点查询延迟的曲线。在我们的配置中,每个文件的布隆过滤器使用20页,索引块使用4页,假阳性率为FPR=0.8%。一个非空的查询需要加载它所访问的级别的布隆过滤器,直到它结束。对于所有的中间层,它以概率FPR访问索引和数据块,然后再获取目标层的索引和数据块。另一方面,一个空查询在返回空结果之前会探测所有级别的Bloom过滤器。请注意,对于每个级别,它也是以FPR访问索引和数据块。这种反直觉的形状是由于当α=0时,非空查询不需要加载所有级别的bloomfilter,而当α=1时,空查询只在出现假阳性时访问索引和数据。图4(h)还显示了1-Lvl的高度可预测的点查询性能。
TA III:点查询延迟基本不受数据移动策略的影响。在有布隆过滤器(有足够大的内存)和足够小的块缓存的情况下,只要树中sorted run的数量保持不变,点查询的延迟基本上不受数据移动策略的影响。这是因为过滤器和索引块的块状缓存大大减少了执行磁盘I/O的时间。