使用 NovelLSM 为 NVM 重新设计 LSM

最近比较关注 Nonvolatile Memory 相关的技术,也发现业界现在对这块的研究越来越多了,刚好看到了一篇 Redesigning LSMs for Nonvolatile Memory with NoveLSM,讲的是如何在 NVM 上面重新设计和优化 LSM,这一下子就引起了我的兴趣,毕竟我们现在也在基于 RocksDB 构建新的存储引擎,另外也跟一些团队在研究改引擎在 Intel 新的硬件下面的优化,另外在加上作者来自威斯康星大学,自然就有了好好研究一下的想法。

LSM 问题

大家知道,LSM 是现今用的非常广泛的一种技术,很多存储引擎都是基于它来实现的,包括 LevelDB,BigTable,以及我们 TiKV 用的 RocksDB。LSM 对写入很友好,在读上面也有不错的性能,但 LSM 并不是万能的,还是有一些问题,首当其冲的就是写放大以及写放大的带来的操作的 latency 上升。

大家对 LSM 的 insert 流程应该比较熟悉,主要有几个步骤:

  1. 写入到 WAL
  2. 写入到 memtable,通常都是一个 skip list
  3. 当 memtable 满了,就变成 immutable memtable
  4. 一个后台线程会将 immutable memtable flush 到磁盘,放到 Level 0 层
  5. 后台线程继续 compaction,将 Level 0 的 SST 逐层的放到下层

这里会有几个问题,在 4 那一步中,如果写入太快,造成之前的 immutable memtable 还没 flush 到磁盘,就会造成 stall。另外,就是在 5,如果 flush 的太快,compaction 进行的太慢,造成 Level 0 有太多的文件,也会造成 stall。当然,如果 compaction 在其他几层有累积,造成大量的 SST 没法 compaction,也会 stall。这些都会影响 insert 的 latency,而且 insert 的 value size 越大,影响也就越大。为了解决这个问题,Wisckey 提出了将 value 从 LSM 里面分离的思路,很多新的引擎也是这样设计的,但这个不在今天的讨论范围里面,以后再说。

上面是 insert,对于 read 操作来说,譬如对于 point get 来说,也会有几个步骤:

  1. 从 memtable 查找
  2. 从 immutable memtable 查找
  3. 在 Level 0 层的 SST 里面依次查找
  4. 使用 binary search 在后面 Level 查找

虽然对于 read,会有很多优化,譬如增加 bloom filter,以及给 SST 建立 index 便于快速定位,使用 block cache 等。多数时候,如果 cache 能够命中,那么 read 的性能还是能有保证的,但如果没有,我们就需要将 SST 的 block 读出来,并且解压放到 block cache 里面,这个开销也会随着 value 增大而增大。

NovelLSM 采用了一些方法来解决 LSM 遇到的问题,主要是依赖 NVM 超高的吞吐以及很低的延迟特性。NovelLSM 致力于解决几个问题:

  1. 解决序列化,反序列化的开销
  2. 减少 Compaction 的开销
  3. 减少 Logging 的开销
  4. 支持并发读取

解决序列化,反序列化的开销

大家可以把 NVM 当成一个只是比 DRAM 性能慢一点的盘,我们能像操作 memory 那样直接操作 NVM,譬如对某一个 memory 地址直接进行更新,这在 NVM 上面也是能做到的。

所以,NovelLSM 做的第一个优化就是实现了一个 skip list,这个 skip list 是能够直接持久化到 NVM 上面的。当实际的 DRAM 上面的 memtable flush 的时候,会将里面的 key-value 数据直接通过 memcpy 的方式移动到一个 immutable NVM skip list-base memtable 上面。

NovelLSM 会将 NVM 的 skip list 通过 memory map 的方式映射到 memory 上面,在 skip list 上面的节点都是通过基于 root 节点的物理偏移来连接的。所以在遍历这个 skip list 的时候,我们需要知道 root 节点的实际地址,而这个就是根据当前映射的地址加上 root 节点的物理偏移来算出来的。所以我们需要一个地方来保存 root 节点的偏移。

对于 NVM 上面的 skip list 的操作,主要是基于 DAX 的文件系统,以及 Intel 的 NVML 库来进行的。这块大家如果感兴趣,可以考虑研究下 NOVA,这里就不过多说明了。

总的来说,NovelLSM 团队弄了个可持久化的 skip list,操作这个 skip list 就跟通常的 memory skip list 没啥两样,只不过不用担心数据丢失。而且也不需要担心序列化和反序列了。

减少 Compaction 的开销

对于 LSM 来说,如果要减少 compaction 的开销,一个比较好的做法就是增大 memtable 的大小,让 memory 里面能写入更多的数据,这样才 flush 到磁盘的时候就能保证大量的数据是有序的,从而减少后续的 compaction 的开销。但通常,机器的内存是受限的,我们不可能给 memtable 分配太大的内存,另外如果 memtable 过大,相应的 WAL 也需要记录更多的数据,在 recovery 的时候时间也会比较长。

NovelLSM 会创建两个 memtable,一个是 DRAM memtable,而另一个则是 NVM memtable。当有数据写入的时候,NovelLSM 流程如下:

  1. 将 insert 的 key-value 写入到 WAL,同时写入到 DRAM memtable
  2. 如果 DRAM memtable 满了,变成 immutable,后台线程开始 flush 到磁盘
  3. 如果这时候写入太快,compaction 来不及,NovelLSM 会激活 NVM memtable
  4. 新的 key-value 直接写写入到 NVM memtable

因为 NVM 比 DRAM 有更大的空间,所以能存放更多的数据,所以能让后台线程有充分的时间去将 DRAM 和 NVM 的 immutable memtable 进行 flush 以及 compaction,防止前台 stall。而对于 read 操作来说,最近的数据就在 DRAM 和 NVM 的 memtable 上面,然后再是 immutable memtable,最后则是 SST 里面。

减少 Logging 的开销

对于一般的 LSM 实现来说,更新操作都会先写入到 WAL,然后在写入到 DRAM memtable,如果为了性能,都会在写入 WAL 的时候关掉 fsync。

对于 NovelLSM 来说,当更新写入到 NVM memtable 上面的时候,因为这个是直接持久化写入的,所以并不需要写入到 WAL。

当 NovelLSM 需要 recovery 的时候,会将 memtable 直接重新 map 到 memory 里面,同时也通过 WAL 来恢复 DRAM 的 memtable。这里我们就需要注意,一个 key 的不同的版本可能在 DRAM memtable 里面,但更新的版本在 NVM memtable 里面,或者相反。为了保证正确性,NovelLSM 会做如下操作:

  1. 当每次 DRAM memtable 被分配出来的时候,一个新的 WAL 就会生成,所有写入到 DRAM memtable 的数据都会持久化到 WAL 上面。
  2. 当 NVM memtable 被激活写入的时候,我们可以直接将 NVM memtable 当成一个 WAL
  3. NVM memtable 的名字可以就是认为就是 WAL,只不过会有一个更新的 version number

这样恢复起来就很容易了,哪一个 WAL 的 version 最新,里面的 key 就是最新的版本。

支持并发读取

在 read 上面,NovelLSM 通过 read pool 来并发的处理,这样就将 read latency 从通常的 T-MemDRAM + T-MemNVM + T-Imm + T-SST 变成了 max(T-MemDRAM + T-MemNVM, T-Imm, T-SST)T-MemDRAM T-MemNVM是 DRAM 和 NVM memtable 的时间开销,T-Imm 则是 immutable memtable 的开销,而 T-SST 就是 SST 的开销。因为 T-MemDRAMT-MemNVM 小的多,所以这两个仍然是串行执行的。

因为 read 是并行读取的,所以可能会正确性问题,譬如下层的 SST 先读取到了数据,所以 NovelLSM 在读取的时候还是需要一定的约束,如果线程 Ti 在读取 Level i 的时候读取到了数据,那么它仍然需要保证之前的线程在 Level 0 到 Level i - 1 上面已经执行完毕,才会返回。同样,如果一个线程在 Level i 上面查到了数据,那么下面的层就不需要执行了。

当然,NovelLSM 还做了其他优化,譬如给 DRAM 和 NVM 的 memtable 加上了 bloom filter 支持,以及让线程跑到固定的 CPU 上面等。

小结

具体的测试以及效果,大家可以详细看看论文。有时候,对于程序员来说,一个比较悲催的事实是 - 在软件层面上面做了特别多的优化,还不如直接换硬件提升的快。而我个人认为,NVM 是未来的一个趋势,我们要做的就是,提前布局,做好相关的技术储备,这样当真的 NVM 成本降低到能大规模应用的时候,我们能给用户提供非常好的解决方案。如果你对这块很感兴趣,欢迎联系我 [email protected]

你可能感兴趣的:(使用 NovelLSM 为 NVM 重新设计 LSM)