优化的重心集中在减少写放大上,同时将读性能维持在和rocksdb 原生读性能接近,优化思想是中国科学院的2位博士 提出的。
论文原地址:
dCompaction: Speeding up Compaction of the LSM-Tree via Delayed Compaction
背景描述
关于LSM的问题,这里不多介绍,有兴趣的同学可以看看之前 总结的相关优化论文的描述:
1. PebblesDB Building Key-Value Stores using FLSM-Tree(Fragmented) SOSP‘17
2. X-Engine: An Optimized Storage Engine for
Large-scale E-commerce Transaction Processing SIGMOD’19
论文中 通过YSCB 构造的workload 来统计 三种类型的操作造成的IO:Compaction, Get, Put.
如下图:
其中IO操作主要是由compaction 造成的,而读IO仅仅会由读的比例的增加而增加,且最大不会超过 总的IO的20%,也就是大部分IO操作都是由Compaction造成的。
再通过分析Compaction的实现 如下图:
可以发现实际compaction过程中针对同一个key可能会写入多次,比如C2–>C3 compaction,会将选择的C3的文件经过合并再次写入到C3之中,这就是写放大的源头。
dCompaction设计
为了减少写放大,dCompaction核心设计了 virtual compaction 和 virtual SSTables,如下图:
compaction过程中 内存里维护virtual sstables ,之间以树状结构来管理实际的real stabiles。virtual sstables 包含如下几种类型的元数据:
- smallest key
- largest key
- file number
- file size
- Parent SStables
相比于实际的real sstables 多了一个parent SStables,用来管理实际的sst文件
dCompaction 实际的过程如下图
原本的Compaction实现:
-
T22@L2 + T32,T33@L3 --> T34,T35,T36@L3
-
T34,T35,T36@L3 + T42,T43@L4 --> T44,T45,T46,T47,T48@L4
也就是T34,T35,T36中的key-value会被多读、多写一次
dCompaction实现:
-
T22@L2 + T32,T33@L3 --> VT34,VT35,VT36@L3
这一步仅仅将实际的sstable中的元数据读上来,在内存中构造 virtual table,合并real table的元数据,从而完成virtual compaction。不需要对T34,T35,T36中的数据多读写一次。
-
VT34,VT35,VT36@L3 + VT42,VT43@L4 --> T44,T45,T46,T47,T48@L4
这一步通过L3的virtual table + L4的virtual table 完成一次real compaction。
通过dCompaction,将实际T22@L2 + T32,T33@L3 + T22@L2 + T32,T33@L3 + T42,T43@L4 --> T44,T45,T46,T47,T48@L4
降低为T22@L2 + T32,T33@L3 + T42,T43@L4 --> T44,T45,T46,T47,T48@L4
所有的key-value仅仅只需要产生一次IO操作即可,能够有效得降低读写放大,提升写性能。
但是这个过程 对读性能有影响。dCompaction 下的读 和 原生rocksdb的读 流程对比如下图:
实际的读在有virtual table的情况下会有如下几步:
- 读memtable
- 读block cache
- 读virtual table
- 确认key是在VT smallest – largest key之间
- 读Parent sstables
- 从实际的T 中读取key-value
相比于从实际的T中直接读取 多了两步。
触发条件 VCT
VCT定义了一个阈值用来控制当前compaction是virtual compaction还是real compaction,触发算法如下:
-
compaction开始
-
选择需要被合并的 sst files
-
sst files 计数
-
Count = sst files 计数
-
如果 Count < VCT
触发virtual compaction
否则
触发real compaction
如果VCT 被设置为0,那么默认就是real compaction;
随着VCT被设置的数值不断增加,virtual compaction会触发得越来越频繁,compaction产生的IO会降低,但是VCT 越大,并不代表系统性能越好。
- 这其实也是变相的compaction delay,virtual compaction进行的越多,单次real compaction的量会越大,这一次的 real compaction会将系统的性能拖到低谷。
- Virtual compaction越多,real ssttables 也会越多,这其实会增加读延时,从而降低读性能。
所以VCT的值需要做一个实际的测试来平衡real compaction 和 sst files 计数 compaction
触发VT 合并的条件 VSMT
这个阈值主要是用来控制一个virtual sstables 能够包含多少real sstables,如果读的过程中发现virtual sstables的real sstables 超过了这个阈值,就对当前virtual sstables进行合并。
这个参数主要是为了 降低 的读请求命中VT 的时 对读性能的影响。
比如读请求命中 VT34,发现其中的real sstables 的个数超过VSMT,那么触发VT的合并。
将VT34 管理的三个real sstables T22,T32,T33都读到内存中,合并为T34,T35,T36。
同理 其他的VT35,VT36 也需要合并到T35,T36。这个过程其实是对IO带宽的一种浪费,但是VT merge 能够减少查找时的IO,提升读性能。
实际的合并算法:
-
如果 sstable.type == virtaul sstable
2. 计算VT中有多少个real sstables
3. Count = the number of real sstables
4. 如果 Count >= VSMT
合并virtual sstable 到real sstables
实际VSMT 也不能随意设置,否则同样会对性能有很大的影响。
如果VSMT设置的过小,VT merge会很频繁,将增加合并过程中的IO消耗(需要读到内存,合并 并写入)。为了降低一部分读延时,缺增加了dCompaction对读写放大的优化效果。
而如果VSMT设置的过大,VT 得不到合并,同样无法达到提升读性能的效果。
测试数据
硬件如下:
CPU: Intel Xeon E5645 (6core,2.4GHz, 12MB L3 cache)
DRAM:2GB
2 * 1TB SATA hdd( 平局寻道时间/旋转延时 : 8.5 ms/4.2 ms, 传输带宽:150M/s)
1 * 480GB SATA intel ssd (平均读/写延时:80 μs/85 μs, 平均读/写带宽:550/520 MB/s)
rocksdb 配置 都是默认的配置,dCompaction的VCT 是12, VSMT 是5。
使用YCSB 按照读写比,生成的workload。
和开头提到的 dCompaction的目标集中在减少写放大,保证读性能。
如下图为四种workload下的测试情况:
D1: (2.4×10^7, 1, 0, 256 B, 6 GB),
D2: (2.4×10^7, 0.8, 0.2, 256 B, 5 GB),
D3: (2.4×10^7, 0.2, 0.8, 256 B, 2 GB),
D4: (2.4×10^7, 0, 1, 256 B, 6 GB)
其中 D(O, W, R, V, S) 为数据集的描述,O表示操作次数,W 表示写占的比例,R 表示读占的比例,V 表示value siz,S 表示总的数据集大小。
如上图可以看到 dCompaction的实现对整体的写性能有很大的提高,同时也有不弱于rocksdb的读性能。
后续还有更加详细的性能数据,相同数据量下两种compaction产生的IO量对比,读写延时对比,不同数据集对性能的影响 以及 VCT和VSMT 指标对性能的影响。