LSM树是针对写友好的,但是Rocksdb在实现时有很多地方的代码不能充分发挥底层存储的性能。比如:
单线程写WAL,写WAL的IO size上限写死为65536字节,不利于做IO聚合;
读sst和写sst的IO size设置为相同的,写速度的加快需要加大写的io size,这样做反过来又降低了读的效率,因为读同一个kv需要从盘上读更大的block;
写大的value时没有将kv分离,而是将kv一起都存到WAL和SST,造成写放大不说,后台合并的数据量巨大带来的带宽和CPU及内存占用对前台的性能影响非常大,常常导致剧烈的性能抖动和性能降低。
所有的读写都是同步方式,导致写wal和写sst都不能并发,不能充分发挥盘的带宽。
当前的流控策略根据内存占用和后台level0层文件的个数和体积来减缓或者停止前台IO,这种方式导致了剧烈的性能抖动。
笔者在HDD/NVME SSD/Ceph Rados上分别测试过Rocksdb的性能,这三者的同步写性能分别是(170MB/s, 1200GB/s, 120MB/s)。不过最终的测试结果却让人大跌眼镜, Rocksdb的性能分别为(<1MB/s, 50MB/s, 4MB/s),最多只能发挥存储侧的%5性能。根据测试结果和以上的5点分析,笔者整理了一下我们系统中的优化方式,希望能给大家帮助,也希望大家能给我们提出一些更好的优化建议。
- 写WAL size动态调整
当前的写io大小上限为65536,不能动态调整;笔者通过对log格式稍作修改,将请求的上限扩展到4GB。具体修改方式就不具体介绍了,修改log_reader/log_writer中的编解码方式即可。 - 并发写WAL优化
而且各个写请求串行执行,不能并发,这导致了写的效率很低。笔者曾经考虑将WAL做分核,每个写都写到当前核上,无奈这种方法对写流程改动太大,同时还影响到flush, load, transaction流程,所以迟迟没能动手。后来我想到了一种简单办法,可以在只改动env里面的写操作流程就可以实现并发,无需其他改动。大概流程如下:
3.写SST io size和读SST IO size单独设置
这个笔者还没有仔细总结,应该调整FlushBlockBySizePolicy中的block size即可。但是合并过程中的SST 读和业务的SST读笔者认为还是有必要分别设置的,这里还没有找到简单合适的办法。希望有读者知道的可以不吝赐教。
- 流控策略优化
笔者建议通过合理的带宽来做流控,在系统上线之前通过存储的性能及系统的写放大合理分配前台和后台的带宽。在我们的系统中,我通过前后台的请求队列深度来控制带宽,基本上没有触发流控。不过由于仅仅是根据请求队列而没有更精细的流量控制,导致队列满时系统的性能还是会下降,但是比起原生的流控方式性能明显更稳定。后续还是希望能根据流量做流控。
不早了,先睡觉,明天继续……