Loading

KVELL:快速持续键值存储的设计与实现

摘要

现代块寻址NVMe固态硬盘为随机与顺序的存取提供了更高的带宽和相似的性能。持续键值存储是为更早的存储设备设计的,它使用了日志结构(LSM)或者B树,并没有充分利用这些新的设备。避免随机存取的逻辑、为了保持数据在磁盘上有序存放的昂贵操作以及同步瓶颈使得这些千伏安处理器受限于NVMe固态硬盘。

我们将展示一种新的持续KV设计。与先前的设计不同,并没有去做有序存储的尝试,并且数据并不是有序存储在硬盘中的。我们采用无共享的理念来避免同步开销。和设备访问的批处理一起,这些设计决策使得读写性能接近设备带宽。最终,在内存中维持一个廉价的部分排序可以产生足够的扫描性能。

我们在KVell中实施了这个设计,第一个持续的KV能够在最大的带宽下利用现代块寻址NVMe固态硬盘。我们将KVell与可用的最新技术的LSM和B树KVs相比较,包括综合基准测试和生产工作负载。在以读取为主的工作负载上,KVell的吞吐量至少是其最接近的竞争对手的两倍,在以写为基础的工作负载上为五倍。对于主要包含扫描的工作负载中,KVell比其竞争者表现相当甚至更好。KVell提供的最大延迟比其最好的竞争对手低一个数量级,即使在基于扫描的工作负载上也是如此。

关键词:键值存储,持续,性能,SSD,NVMe,B+树,日志结构的合并树(LSM)

1 介绍

键值存储(KVs)已经成为在广泛的云应用中提供存储的标准平台,其中包括缓存、元数据管理、消息传递和在线购物。在本文中,我们的目标是块寻址存储设备上的KVs,这些设备提供持久性保证(即,数据和更新在失败时不能丢失),并且工作集不完全适合主存。

过去,存储设备和CPU之间的速度差距是很大,以至于投资CPU周期来优化存储访问是一个有益的折衷。因此,一些KVs包含复杂的索引,比如B+树。LogStructured Merge (LSM) KVs强制执行顺序写操作,因为它们比随机写操作效率更高。所有的KVs都包含某种形式的缓存。为了支持检索给定键范围内的所有项的高效扫描,KVs在内存和磁盘上以排序的顺序维护项。尽管所有这些优化都需要一些CPU周期的投资,但存储设备仍然是瓶颈,而这些优化被证明是有益的,因为它们缓解了这个瓶颈。

当我们测量运行在块寻址NVMe SSD设备上的最先进的KVs的性能时,我们发现瓶颈是CPU,而不是存储设备,这证实了早期的定性观察。NVMe SSD在随机和顺序访问方面表现出非常高的带宽和相当的性能。因此,许多为传统存储设备开发的优化不再有用,实际上还会产生反效果,因为它们在CPU周期方面的成本加剧了CPU瓶颈。一个明显的例子是LSM KVs强制执行顺序写操作的尝试。

在设备访问方面不再有任何好处,并且LSM KVs(主要是压缩)所需要的维护操作会加重CPU的负担,并且因此对性能造成负面的影响。一个更令人惊讶的例子是,在共享(内存中)数据结构上进行同步以进行缓存和维护顺序会导致CPU成为瓶颈。我们得出的结论是,存储设备特性的变化需要在KVs的设计中进行范型转换,其中重点关注CPU的流线型使用。

在本文中,我们介绍了以下四个KVs设计原则,我们发现它们在此过程中非常有用。

\1. 无共享:所有数据结构在核之间分区,这样每个核几乎可以完全不同步地运行。

\2. 项目不在磁盘上排序。每个分区都维护一个内存中排序索引,以便进行高效扫描。

\3. 没有尝试强制顺序访问,但是对I/O操作进行批处理,以减少代价高昂的系统调用的数量。批处理还提供了对设备队列长度的控制,使同时实现低延迟和高吞吐量成为可能。

\4. 没有提交日志:只有在更新被持久化到磁盘上的最终位置后,更新才被确认。

除了提供更好的平均吞吐量之外,通过消除许多复杂的维护过程,这些原则还可以在吞吐量和延迟方面带来更可预测的性能。有些设计决策并不是没有权衡的。例如,无共享架构会有负载不平衡的风险。没有排序顺序会影响扫描。我们在评估中显示,对于主要由中到大型键值对(400B+)组成的工作负载,精简的CPU操作带来的好处超过了这些缺点。

我们在KVell中实现了这些技术,我们展示了KVell可以与四个最先进的KVs相媲美:RocksDB和PebblesDB,它们都是LSM KVs,以及TokuMX和WiredTiger,它们是基于B树的衍生物。我们使用行业标准的YCSB基准测试,包括统一的和倾斜的密钥访问分布。此外,我们还展示了吞吐量在整个实验中保持稳定,而其他系统则经历了性能较差的时期。我们通过对替代工作负载和平台的测量来确认这些结果。贡献。本文的贡献是:

1.对传统的LSM和B树型KVs进行了分析,论证了NVMe SSDs的CPU瓶颈问题。

2.持久KVs利用NVMe ssd的属性的新范例,它重点关注CPU的流线型使用。

3.KVell KV包含了这个新范式。

4.将KVell与最新的LSM和B树KVs在合成基准和生产工作负载上进行深入评估。

路线图。本文的其余部分组织如下。第2节展示了SSD性能的发展。第三节讨论当前KV设计的限制。第4节介绍KVell背后的设计原则。第5节将更详细地描述KVell中采用的技术解决方案。第6节对KVell进行了深入的评价。第7节讨论相关工作,第8节总结全文。

2 SSD性能的发展

硬件。我们考虑一下在过去5年中引入的三种设备:

• Config-SSD. A 32-core 2.4GHz Intel Xeon, 128GB of

RAM, and a 480GB Intel DC S3500 SSD (2013).

• Config-Amazon-8NVMe.An A WS i3.metal instance,

with 36 CPUs (72 cores) running at 2.3GHz, 488GB of

RAM, and 8 NVMe SSD drives of 1.9TB each (brand

unknown, 2016 technology).

• Config-Optane. A 4-core 4.2GHz Intel i7, 60GB of

RAM, and a 480GB Intel Optane 905P (2018).

IOPS(每秒输入输出次数,是一个用于计算机存储设备(如硬盘(HDD)、固态硬盘(SSD)或存储区域网络(SAN))性能测试的量测方式). 表1显示了这三种设备的最大读写IOPS数以及随机和顺序带宽。首先,IOPS的数量和带宽显著增加。其次,在较老的设备上,随机写操作比顺序写操作要慢得多,但现在已经不是这样了。类似地,混合随机读写不再会像老式设备那样导致性能低下。例如,Config-Optane使用在2018年Q3发布的Intel Optane 905P驱动器,不管读写的混合方式如何,它都能维持500K+ IOPS,而且随机访问也只比顺序访问稍微慢一点。

延迟和带宽。表2展示了三个设备的延迟和带宽测量值,它们是设备队列深度的函数,其中一个核心执行随机写操作。上一代设备只能通过少量的同步I/O请求维持亚毫秒级的响应时间(在Config-SSD的情况下是32个)。Config-Amazon-8NVMe和Config-Optane支持更高的并行性,这两个驱动器都能够以亚毫秒级的延迟响应256个同时发生的请求。当设备队列中的请求太少时,这两个驱动器只能达到其带宽的一小部分。因此,即使在现代设备上,也必须在发送太少的并发请求(导致次优带宽)和发送太多(导致高延迟)之间保持良好的平衡。

吞吐量降低。图1显示了这三种设备上的IOPS随时间的变化。这张图也显示了设备技术的进步。例如,配置ssd可以在40分钟内维持50K的写入IOPS,但随后它的性能慢慢下降到11K IOPS。较新的ssd就不会有这个问题,而且随着时间的推移,IOPS会保持在较高的水平。

img

表1. IOPS和带宽取决于3个SSD的工作负载。 新驱动器中的IOPS和带宽已大大增加,并且随机访问和顺序访问之间的性能差异很小。

img

表2.根据队列大小的平均延迟和带宽,在单个内核上进行随机写入。队列长度的适当选择是必需的同时实现低等待时间和高带宽利用率。

img

图1. 3个SSD随时间推移的IOPS数量。 图2. AWS和Intel Optane上的4K

老式SSD仅在最大IOPS时支持短暂的 写请求(队列深度64)的延迟时间。

I / O突发。

延迟峰值。图2显示了Config-Amazon-8NVMe和Config-Optane上队列深度为64的4K写操作的延迟。由于内部维护操作,老一代ssd在写量大的工作负载中受到延迟高峰的影响。在Config-SSD上,我们观察到5小时后延迟峰值高达100ms,而正常的写入延迟为1.5ms。图2中没有显示这些结果,因为这个设备的峰值大小会掩盖其他设备的结果。Config-Amazon-8NVMe驱动器也会受到周期性延迟高峰的影响。最大观察到的延迟是15ms(而99.1%是3ms)。在Config-Optane驱动器上,潜伏期的峰值不规律地发生,它们的幅度通常小于1ms,观察到的最大峰值为3.6ms(相比之下,99%的峰值为700us)。

3 NVMe ssd上当前KVs的问题

目前持久KVs主要有两种范式:(1)LSM KVs,被广泛认为是写为主的工作负载的最佳选择;(2)B树KVs,被认为更适合读密集型工作负载。LSM KVs在流行的系统中使用,如RocksDB和Cassandra。在MongoDB中使用了B树及其变体。接下来,我们将证明这两种设计在NVMe ssd上都成为了cpu瓶颈,并遭受严重的性能波动。我们超越了现有的工作,表明这些观察结果也适用于B树KVs,并提供了一个详细的CPU开销核算。

3.1 CPU是瓶颈

img

3. Config-Optane RocksDB(LSM KV)和WiredTiger(B树KV)的I / O带宽(左)和CPU消耗(右)时间线。 这两个系统都无法利用整个设备的I / O带宽。 工作负载:YCSB写入50%–读取50%,均匀密钥分配,KV项大小为1KB。

图3显示了两个最先进的KVs在Config-Optane上的磁盘利用率(左)和CPU利用率(右):RocksDB (LSM KV)和Wired Tiger (一个B树KV) (其他KVs的结果类似)。在本例中,我们使用具有统一密钥分发的YCSB核心工作负载A(写密集型)。这两个系统都使CPU饱和,并且没有充分利用可用带宽。现在我们来解释这些行为。

CPU****是LSM KVs的瓶颈。LSM KVs通过吸收内存缓冲区中的更新来优化写密集型工作负载。当缓冲区满时,它被刷新到磁盘。然后,后台线程将刷新的缓冲区合并到持久存储中维护的树状结构中。磁盘结构包含多个级别,级别大小不断增加。每一层都包含多个不可变的排序文件,它们的键值范围不一致(除了第一层,它是为写内存缓冲区保留的)。为了在磁盘上保留这种结构,LSM KVs实现了称为压缩的CPU和I/O密集维护操作,该操作将LSM树中较低级别的数据合并到较高级别的数据,维护项目排序并丢弃重复项。

在较旧的设备上,压缩会争夺磁盘带宽,但是合并、索引和内核代码也会争夺CPU时间,而在较新的设备上,CPU已经成为主要的瓶颈。分析显示,RocksDB在压缩上花费了高达60%的CPU时间(28%用于合并数据,15%用于构建索引,剩下的用于读取和写入磁盘数据)。压缩需求源于LSM的设计要求,即顺序磁盘访问和在磁盘上保持数据排序。这种设计对于旧的驱动器是有益的,在这些驱动器中,花费CPU周期来保持数据排序和确保长时间顺序的磁盘访问是值得的。

CPU是B树KVs的瓶颈。有两种变体B树为持久性存储设计:B +树和Bϵ树。B+树在叶子中包含KV项。内部节点只包含键并用于路由。通常,内部节点驻留在内存中,叶子驻留在持久存储中。每个叶节点都有一个已排序的KV项范围,并且叶节点被链接到一个链表中,便于扫描。最先进的B+树(例如,WiredTiger)依靠缓存来实现良好的性能。更新首先写入每个线程的提交日志,然后写入缓存。最终,当数据从缓存中被逐出时,树将使用新信息进行更新。更新使用序列号,这是扫描所必需的。读操作遍历缓存,只有在项目未被缓存时才访问树。

有两种类型的操作可以将数据持久化到树中:(1)检查点和(2)驱逐。检查点定期发生,或者在日志中达到某个大小阈值时发生。检查点对于保持提交日志的大小有限制是必要的。驱逐将脏数据从缓存写入树中。当缓存中的脏数据量超过某个阈值时,将触发驱逐。

Bϵ树是B +树的变体,增强临时存储键和值在每个节点的缓冲区。最终,当缓冲区满了时,KV项沿着树结构向下延伸,并被写入持久存储。

B树的设计容易产生同步开销。WiredTiger中的剖析揭示了这一点:工作线程花费47%的时间忙于等待日志中的插槽(在__log_wait_for_earlier_slot函数中,该函数使用sched_yield系统调用来忙碌等待)。这个问题源于不能足够快地推进序列号进行更新。在更新的主代码路径中,不包括内核所花费的时间,WiredTiger只花费18%的时间来执行客户端请求逻辑,其余时间用于等待。WiredTiger还需要执行后台操作:从页面缓存中清除脏数据占总时间的12%,管理提交日志占总时间的5%。内核中只有25%的时间用于读写调用,其余的时间用于futex和yield函数。

Bϵ树也经历同步开销。因为Bϵ树保持磁盘上的数据命令有序,工作线程最终修改共享数据结构,导致争用。对TokuMX的分析表明,线程将30%的时间花费在锁或用于保护共享页面的原子操作上。缓冲也被证明是Bϵ树中的一个主要开销来源。在YCSB中,一个工作负载TokuMX花费超过20%的时间将数据从缓冲区移动到叶子中的正确位置。这些同步开销大大超过了其他开销。

3.2 LSM和B树KVs的性能波动

除了受制于cpu之外,LSM和B树KVs都受到了显著的性能波动。图4显示了RocksDB和WiredTiger运行YCSB核心工作负载a的吞吐量随时间的波动。吞吐量是每秒测量一次的。在LSM和B树KVs中,基本问题是相似的:客户端更新会因为维护操作而停止。

在LSM KVs中,吞吐量下降是因为有时更新需要等待压缩完成。当LSM树的第一级已满时,更新需要等待,直到通过压缩使空间可用。但是,我们已经看到,当LSM KVs在现代驱动器上运行时,压缩会遇到CPU瓶颈。随着时间的推移,吞吐量方面的性能差异会上升一个数量级:RocksDB平均维持63K /s的请求,但在写入停止时下降到1.5K。分析显示,写线程大约有22%的时间被搁置,等待内存组件被刷新。内存组件的刷新被延迟,因为压缩无法跟上更新的速度。

降低压实对性能影响的解决方案已被提出,即延迟压缩或者仅在系统空闲时运行它们,但是这些解决方案不适合在高端ssd上运行。例如,Config-Optane机器以2GB/s的速度刷新内存组件。延迟压缩超过几秒钟会导致大量的工作积压和浪费空间。

在B树中,用户工作负载中的暂停也会影响性能。用户写入被延迟,因为驱逐不能足够快地进行。暂停导致吞吐量下降了一个数量级,从120 Kops/s下降到8.5 Kops/s。我们的结论是,在这两种情况下,诸如压缩和驱逐之类的维护操作严重干扰用户的工作负载,导致持续数秒的暂停。因此,新的KV设计应避免维护操作。

img

图4. Config-Optane. RocksDB和WiredTiger中的吞吐量波动。 工作负载:YCSB写入50%–读取50%,密钥分配均匀,KV项大小为1KB。

4 KVell设计原则

为了有效地利用现代持久存储,KVs现在需要强调低CPU开销。我们将说明以下原则是在现代ssd上实现最高性能的关键。

  1. 不共享。在KVell中,这可以转化为支持并行性和最小化KV工作线程之间的共享状态,以减少CPU开销。

  2. 不要在磁盘上排序,而是将索引保存在内存中。KVell将未排序的项目保存在磁盘的最终位置,避免了昂贵的重新排序操作。

  3. 目标是减少系统调用,而不是连续的I/O。KVell的目标不是顺序磁盘访问,它利用了这样一个事实:随机访问几乎和现代ssd上的顺序访问一样高效。相反,它努力通过批处理I/O来最小化系统调用造成的CPU开销。

  4. 不提交日志。KVell不缓冲更新,因此不需要依赖于提交日志,避免了不必要的I/O。

4.1不共享

对于单个读和写的常见情况,处理请求的工作线程不需要与其他线程同步。每个线程处理给定的键子集的请求,并维护一个线程私密的数据结构集来管理这组键。关键数据结构是:(i)一个轻量级内存B树索引,用于跟踪键在持久存储中的位置,(ii) i / O队列,从持久存储负责有效地存储和检索信息,(iii)自由列表,部分内存磁盘块列表包含用于存储项和(iv)页面缓存——KVell使用自己不依赖于os级结构的内部页面缓存。扫描是在内存中的B树索引上需要最小同步的惟一操作。

与传统KV设计相比,这种无共享方法是一个关键的区别,在传统KV设计中,所有或大多数主要数据结构都由所有工作线程共享。传统的方法需要对每个请求进行同步,这是KVell完全避免的。分区请求可能会导致负载不平衡,但我们发现,如果使用合适的分区,这种影响很小。

4.2不要在磁盘上排序,而是将索引保存在内存中

KVell不会对工作线程的工作集中的数据进行排序。因为KVell不对键进行排序,所以它可以将项保存在磁盘上的最终位置。磁盘上完全没有顺序减少了插入项的开销(即找到插入的正确位置),并消除了与磁盘上的维护操作(或者写入磁盘之前的排序)相关的CPU开销。在磁盘上以无序方式存储密钥尤其有利于写操作,并有助于实现较低的尾延迟。

在扫描期间,连续的键不再在同一个磁盘块中,这可能是一个缺点。然而,可能令人惊讶的是,对于具有中型和大型kv -项的工作负载(例如YCSB基准测试或生产工作负载中的扫描,如我们在第6节中所示),扫描性能不会受到显著影响。

4.3目标是减少系统调用,而不是顺序I/O

在KVell中,所有操作(包括扫描)都对磁盘执行随机访问。因为随机访问和顺序访问一样有效,所以KVell不会浪费CPU周期来强制执行顺序I/O。

与LSM KVs类似,KVell将请求批量发送到磁盘。然而,目标是不同的。LSM KVs主要使用批处理I/O和对KV项进行排序,以便利用顺序磁盘访问。KVell对I/O请求进行批处理,主要目标是减少系统调用的数量,从而减少cpu开销。

批量是需要折中的,正如在第2节中看到的,磁盘需要一直处于忙碌状态,以实现其峰值IOPS,但只有在硬件队列中包含的请求数小于给定数量(例如Config-Optane上的256个请求)时,磁盘才会以亚毫秒级的延迟响应。一个高效的系统应该向磁盘推送足够的请求,以使它们保持忙碌,但是不要让大的请求队列淹没它们,这会导致高延迟。

在具有多个磁盘的配置中,每个worker只在一个磁盘上存储文件。这种设计决策对于限制每个磁盘挂起的请求数量非常关键。实际上,由于worker之间不通信,所以它们不知道其他worker向给定磁盘发送了多少请求。如果worker将数据存储在单个磁盘上,那么对磁盘的请求数量是有限的(批处理大小乘以每个磁盘上的worker数量)。如果工作者要访问所有磁盘,那么磁盘可能有多达(批大小乘以工作者总数)的挂起请求。

4.4不提交日志

KVell只在更新被持久化到最终位置的磁盘上之后才承认更新,而不依赖于提交日志。一旦更新被提交给工作线程,它将在下一个I/O批中持久化到磁盘上。删除提交日志允许KVell仅将磁盘带宽用于有用的客户机请求处理。

5 KVell的实现

尽管KVell的设计原则看起来很简单,但要在实践中正确地实现它们是很有挑战性的。KVell的源代码可以在https://github.com/ BLepers/KVell上获得。

5.1客户端操作界面

KVell实现了与LSM KVs相同的核心接口:写Update(k,v)、读Get(k)和范围扫描Scan(k1,k2)。Update(k,v)将值 v与键 k关联。Update(k,v)只在值被持久化到磁盘后返回。Get(k)返回k的最近值。扫描(k1,k2)返回k1和k2之间的KV项目范围。

5.2磁盘数据结构

为了避免碎片化,适合相同大小范围的项被存储在相同的文件中。我们称这些文件为slab。KVell以块粒度访问slab,也就是我们机器上的页面大小(4KB)。

如果项小于页面大小(例如,多个项可以容纳在一个页面中),KVell会在slab中的项前面加上时间戳、键大小和值大小。大于4K的项在磁盘上每个块的开头都有一个时间戳头。对于小于页面大小的项,将在适当的位置进行更新。对于较大的项,一次更新包括将项目附加到slab上,然后在项目曾经所在的地方写一个墓碑。当一个项更改大小时,KVell首先将更新后的项写入新slab,然后从旧slab中删除它。

5.3内存中的数据结构

索引。KVell依赖于快速而轻量级的内存索引,这些索引具有可预测的插入和查找时间来查找磁盘上项的位置。KVell为每个worker使用一个内存中的B树来存储磁盘上项目的位置。项根据其键(前缀)建立索引。我们使用前缀而不是哈希来保持范围扫描的键的顺序。B树在存储中/大型数据时性能很差,但当数据(大部分)能放入内存且键很小时,速度很快。KVell利用了这个属性,并且只使用B树存储查找信息(前缀和位置信息占13B)。

KVell的树实现目前平均每个条目使用19B(存储前缀、位置信息和B树结构),相当于1.7GB的RAM来存储100M个条目。在YCSB工作负载(1KB项)上,索引相当于数据库大小的1.7%。我们发现这个值在实践中是合理的。KVell目前不显式地支持将B树的一部分刷新到磁盘,但是B树数据是从mmap-ed文件中分配的,可以由内核调出。

页面缓存。KVell维护自己的内部页面缓存,以避免从持久存储中获取频繁访问的页面。缓存的页面大小是一个系统参数。页面缓存会记住哪些页面缓存在索引中,并按照LRU顺序从缓存中回收页面。

确保索引中的查找和插入具有最小的CPU开销对于获得良好的性能至关重要。我们的第一个页面缓存实现使用快速uthash哈希表作为索引。但是,当页面缓存很大时,散列中的插入可能会花费高达100ms的时间(增长散列表的时间),从而提高了尾延迟。切换到B树可以消除这些延迟峰值。

空闲列表。当从一个slab中删除一个项时,它在该slab中的位置将插入到每个slab的内存堆栈中,我们称之为slab的空闲列表。然后在磁盘上的项目位置写入一个tombstone。为了绑定内存使用,我们只保留内存中最后的N个释放的位置(目前N被设置为64)。其目标是限制内存使用,同时保持在不需要额外磁盘访问的情况下重用每批I/O的多个空闲点的能力。

当(N + 1)th项被释放时,KVell使其磁盘tombstone指向第一个被释放的位置。然后KVell从内存堆栈中删除第一个被释放的位置,并插入(N +1)被释放的位置。当(N +2)th项被释放时,它的tombstone指向第二个被释放的位置,以此类推。简而言之,KVell维护N个独立的堆栈,它们的头驻留在内存中,其余的在磁盘上。这使得Kvell在每批I/O能重用最多N个空闲点。如果只有一个堆栈,KVell就必须从磁盘上依次读取N个tombstone,以找到下一个被释放的N个位置。

5.4有效地执行I/O

KVell依赖于Linux (AIO)的异步I/O API将请求发送到磁盘,最多分批发送64个请求。通过批量处理请求,KVell将系统调用的开销分摊到多个客户机请求上。我们选择使用Linux异步I/O,是因为它为我们提供了一种通过单个系统调用执行多个I/Os的方法。我们估计,如果这样的调用在同步I/O API中可用,性能将大致相同。

我们拒绝了两个流行的执行I/O的替代方案:(1)使用依赖于OS页面缓存的mmap (例如,RocksDB),和(2)使用直接读写I/O系统调用(例如,TokuMX)。这两种技术的效率都不如使用AIO接口。表3总结了我们的发现,展示了在Config-Optane上可以实现的最大IOPS,随机写入4K块(这需要对设备进行读-修改-写操作)。被访问的数据集比可用RAM大3倍。

img

表3. Config-Optane. 最大IOPS取决于磁盘访问技术。

第一种方法是依赖os级页面缓存。在单线程情况下,这种方法的性能不是最优的,因为当发生页面错误时,它一次只能发出一次磁盘读取(由于数据是随机访问的,所以提前读取值设置为0)。此外,脏页只定期刷新到磁盘。这在大多数情况下会导致次优化的队列深度,然后是I/Os的爆发。当数据集不能完全装入RAM时,内核还必须从进程的虚拟地址空间映射和取消映射页出页,这会带来显著的CPU开销。对于多线程,在刷新LRU时,页面缓存会受到锁开销的影响(平均每32KB刷新一个锁到磁盘),以及系统使远程核心的TLB项失效的速度。实际上,当一个页面从虚拟地址空间中取消映射时,需要在所有访问过该页面的核心上使虚拟到物理的映射失效,这将导致IPI通信[33]带来巨大的开销。

第二种方法是依赖于直接I/O。然而,当请求以同步方式完成时(每个线程有一个挂起的请求),直接I/O读/写系统调用不会填充磁盘队列。因为没有必要处理从虚拟地址空间映射和取消映射页面的复杂逻辑,此技术优于mmap方法。

相反,批处理I/O每个批处理只需要一个系统调用,并允许KVell控制设备队列长度,以实现低延迟和高带宽。尽管理论上I/O批处理技术可以应用于LSM和B树KVs,但实现将需要很大的努力。在B树中,不同的操作可能会对I/O产生冲突的影响(例如,由插入引起的叶的分裂,然后是两个叶的合并)。此外,数据可能由于重新排序而在磁盘上移动,这也使得异步批处理请求难以实现。写请求的批处理已经通过内存组件在LSM KVs中实现。然而,批处理略微增加了读取路径的复杂性,因为工作人员必须确保压缩线程不会删除所有需要读取的文件。

img

5.5客户端操作实施

算法1总结了KVell架构。为简单起见,该算法只显示单页KV项。当一个请求进入系统,根据它的键被分配给worker(第3行和第5行,算法1). worker线程执行磁盘I/O并处理客户端请求的逻辑。

得到(k)。读取一个项(第17-22行,算法1)包括从索引中获取它在磁盘上的位置,并读取相应的页。如果已经缓存了页面,则不需要访问持久存储,并且值将同步返回给客户机。如果没有,则处理请求的worker将其推入其I/O引擎队列。

更新(k、v)。更新一个项(第24-35行,算法1)包括首先读取存储它的页面,修改值,然后将该页面写入磁盘。删除一个项目包括写入一个tombstone值和在slab的空闲列表中添加项目位置。在添加新项时重用释放的位置,如果不存在空闲位置,则追加项目。KVell仅在更新的项完全持久化到磁盘时(即io_getevents系统调用通知我们,与更新对应的磁盘写操作已经完成(算法1的第37行),才确认更新已经完成。脏数据会立即刷新到磁盘,因为KVell的页面缓存不用于缓冲区更新。通过这种方式,KVell提供了比最先进的KVs更强的持久性保证。例如,RocksDB只在提交日志同步到磁盘的粒度上保证持久性。在典型的配置中,同步只发生在几批更新中。

扫描(k1, k2)。扫描包括(1)从索引中获取键的位置,(2)读取对应的页面。为了计算键的列表,KVell扫描所有的索引:一个线程简单地依次锁定、扫描和解锁所有worker的索引,最后合并结果以获得一个要从KV读取的键的列表。然后使用Get()命令执行读取,该命令绕过了索引查找(因为KVell已经访问了索引)。扫描是唯一需要在线程之间共享的操作。KVell返回与扫描触及的每个键相关联的最新值。相比之下,RocksDB和WiredTiger都在KV快照上执行扫描。

5.6故障模型与恢复

KVell目前的实现是为无故障运行而优化的。在崩溃的情况下,所有的平板被扫描,内存中的索引被重新构建。即使扫描使顺序磁盘带宽最大化,在非常大的数据集上恢复仍然需要几分钟。

如果一个项在磁盘上出现了两次(例如,在将一个项从一个slab迁移到另一个slab的过程中发生了崩溃),那么只有最近的项保存在内存索引中,而另一个项插入到空闲列表中。对于大于块大小的项,KVell使用时间戳标头来丢弃仅被部分写入的项。

KVell是为能够自动写入4KB页面的驱动器设计的,即使在电源故障的情况下也是如此。通过避免对页面进行就地修改,在新页面中写入新值,然后在第一次写入操作完成后在slab的空闲列表中添加旧位置,可以解除该约束。

6评价

6.1目标

我们用各种生产和合成工作负载来评估KVell,并将其与最先进的KVs进行比较。评估旨在回答以下问题:

\1. 在吞吐量、性能波动和读取、写入和扫描的尾部延迟方面,KVell与现代ssd上现有的KVs相比如何?

\2. KVell如何在大型数据库和生产工作负载上执行?

3.在超出其设计范围的工作负载中使用KVell的利弊和限制是什么(小型项目、内存限制极端的环境和较旧的驱动器)?

6.2实验设置硬件

硬件。我们使用第2节中描述的三种硬件配置。我们的重点是Config-Optane,因为它是三个驱动器中最近的配置。我们还在Config-Amazon-8NVMe中评估KVell,目的是展示大型配置中的系统行为。最后,我们评估了Config-SSD中的KVell,展示了在旧硬件上使用KVell的权衡。

工作负载。我们使用YCSB[10]和Nutanix的两个生产工作负载。我们的重点是YCSB基准测试,因为它包含各种类型的工作负载,提供了KVell行为的更完整视图。表4显示了YCSB核心工作负载的摘要。我们评估所有YCSB工作负载的uniform和Zipfian密钥分发。KV项目大小为1024B,小测试的数据集大小约为100GB (100M keys),大测试的数据集大小约为5TB (5B keys)。生产工作负载是两个写密集型工作负载,配置文件的为写:读:扫描的比例为57:41:2。KV项目大小在250B ~ 1KB之间,中位数400B。生产工作负载的总数据集大小是256GB。这两种工作负载之间的差异是数据倾斜:生产工作负载1更接近统一的键分布,而生产工作负载2的倾斜程度更大。

现有的KVs, 我们将Kvell与四种最先进的系统相比:(1) RocksDB 6.2[15],一个由Facebook开发并在产业上大量使用的LSM KV存储,(2)PebblesDB[43],一个最近的学术性LSM KV存储,在LSM管理开销上做出了显著的改进(3)TokuMX [42],一个作为存储引擎被MongoDB[35]支持的B epsilon树[7],(4)WiredTiger 3.2[48]配置为使用B+树。我们使用它的LevelDB接口来查询WiredTiger,这个接口是从WiredTiger 3.1移植过来的。我们还尝试了CouchDB[2],这是一种优化的B+树实现,但在评估中没有报告结果,因为它始终比TokuMX慢。

img

表4 YCSB核心工作负载描述。

img

图****5. Config-Optane。 具有统一和Zipfian密钥分配的YCSB工作负载的平均吞吐量。 KVell在写入密集型工作负载方面高达5.8倍,在读取密集型工作负载方面高达2.2倍,胜过次优竞争对手,同时提供了出色的扫描性能(与性能最佳的统一系统相比,而Zipfian则高出32%)。

系统配置。所有系统都被分配了相同数量的内存,用cgroups来设置。内存大小是数据集大小的三分之一,以确保从内存和持久存储同时为请求提供服务。当数据库大于可用RAM的3倍时,我们不使用cgroups(也就是说,应用程序可以使用整个可用RAM)。对于B树,我们将块大小配置为4KB。两个LSM KVs配置为最多有5个级别和两个128MB内存组件(一个活动的和一个不可变的)。此外,我们将write-ahead-log设置为1MB的缓冲区,这样它只会偶尔被刷新,而不会对LSM KVs造成不利影响。

6.3主实验设置的结果

6.3.1吞吐量

图5显示了KVell和竞争者系统在Config-Optane上的统一和Zipfian密钥分布下YCSB的平均吞吐量。

写密集型工作负载(YCSB A和****F)。在YCSB A工作负载上,KVell的性能比TokuMX高15倍,比WiredTiger和RocksDB高8倍,比PebblesDB高5.8倍(50%读,50%写)。在这个工作负载中,未缓存的读产生1个I/O,缓存的读产生0个I/Os,未缓存的写产生2个I/Os(1个读+ 1个写),缓存的写产生1个I/O(1个写)。由于页面缓存包含数据库的1/3,平均1个请求在磁盘上产生1.17个I/Os,这意味着最大的理论吞吐量为500K IOPS/1.17 = 428K请求/s。KVell的平均吞吐量为420K请求/s。因此,KVell在不受cpu限制的情况下利用了其峰值带宽的98%的磁盘,如图6所示。在这个工作负载中,KVell将20%的时间花在由页面缓存和内存中索引完成的B树查找上,20%的时间花在I/O函数上,60%的时间花在等待上。

正如在第3节中看到的,LSM KVs受到压缩成本的限制。WiredTiger受到日志争用的限制(占总周期的50%)。TokuMX被共享数据结构的争用和不必要的缓冲(两者都占总周期的50%)所限制。

读取密集型工作负载****(YCSB, C, D). KVell在YCSB上比现有的KVs性能高出2.2倍,在YCSB上比现有KVs高出2.7倍,在接近完全IOPS的情况下使用磁盘。在YCSB C上,KVell花费40%的时间在查找上,20%的时间在I/O函数上,40%的时间在等待上。在这些工作负载中,现有的KVs执行得不是很理想,因为它们共享缓存,或者因为它们没有将读请求批处理到磁盘(每个读一个系统调用)。例如,RocksDB将41%的时间花费在pread()系统调用上,并且是cpu绑定的。

扫描工作负载****(YCSB E)。令人惊讶的是,KVell在扫描密集型工作负载中表现良好,无论是在uniform还是Zipfian密钥分发中。注意,为了公平起见,我们修改了YCSB工作负载,以便它在KVell中以随机顺序插入键。默认情况下,YCSB按顺序插入键。

在Zipfian工作负载上,KVell的性能至少比所有系统高出25%。由于工作负载倾斜,热数据被捕获在KVell的缓存中。无共享的设计,加上低开销的缓存实现,使KVell比其竞争对手具有优势。

在统一工作负载上,KVell的性能比PebblesDB高出5倍,比WiredTiger高出33%,并且提供了与RocksDB相当的性能(13.9K扫描/s vs. 14.4K扫描/s)。由于KVell并不在磁盘上对数据进行排序,所以它平均访问每个扫描项的一个页面。相比之下,对数据进行排序的RocksDB大约访问每个页面上的三个项(页面大小为4K,项大小为1024K)。因此,RocksDB的最大吞吐量大约是KVell的三倍。图7显示了每个系统的吞吐量时间线,证实了这种推理。对于YCSB E, RocksDB的峰值吞吐量达到55Kops/s,而KVell的最大吞吐量为18Kops/s。然而,RocksDB的维护操作会干扰客户端负载,导致大的波动,而KVell的吞吐量保持稳定在15Kops/s左右。因此,平均而言,RocksDB和KVell的表现相似。

img

图****6. Config-Optane。 YCSBA上的KVell I / O带宽(左)和CPU利用率(右)一致。 KVell使用整个磁盘I / O带宽,而不会成为CPU绑定的。

6.3.2吞吐量随时间变化

图7展示了KVell、RocksDB、PebblesDB和WiredTiger的吞吐量随时间的变化。吞吐量是每秒测量一次的。除了提供高平均吞吐量之外,KVell不会受到维护操作带来的性能波动的影响。在YCSB A中,RocksDB的吞吐量降至最低1.4K请求/秒,PebblesDB的吞吐量降至10K请求/秒,而WiredTiger的吞吐量降至8.5K请求/秒。在只包含5%更新的扫描密集型工作负载中,RocksDB的吞吐量下降到1.8K扫描/s, PebblesDB下降到1.1K扫描/s。这些下降经常出现。

相比之下,KVell的性能在一个短暂的爬升阶段(页面缓存被填满的时间)之后保持不变。KVell在YCSB a上至少维护400K /s的请求,在过渡阶段之后维护15K /s的扫描。

6.3.3尾延迟

表5给出了KVell、RocksDB、PebblesDB和WiredTiger的第九十九百分位数和最大延迟。LSM KVs的尾部延迟超过9秒,而WiredTiger的最大延迟为3秒。这是由于维护操作直接影响了LSM和B树KVs的尾部延迟。这样的数字在LSM KVs中并不少见,在之前的工作[6]中也有报道。相比之下,KVell提供了较低的最大延迟(3.9ms),同时提供了强大的持久性保证。

img

表****5. YCSB A工作负载上99%的请求延迟和最大请求延迟(写密集型)。

img

图****7. Config-Optane。 KVell,RocksDB,PebblesDB和WiredTiger的吞吐量时间线,用于YCSB A(写密集型),YCSB B(读密集型),YCSB C(只读)和YCSB E(扫描密集型),且密钥分布均匀。 每秒测量。 与LSM和B树KV相比,KVell提供高且稳定的吞吐量,而LSM和B树KV由于维护操作而有很大的波动。

6.4备选配置和工作负载

6.4.1 Config-Amazon-8NVMe上的YCSB

图8显示了YCSB用于统一密钥分发的平均吞吐量。所有系统都配置为使用30GB缓存(数据库大小的1/3)。在YCSB A上,KVell的平均性能比RocksDB高6.7倍,PebblesDB高8倍,TokuMX高13倍,WiredTiger高9.3倍。对于YCSB E, KVell的性能略优于RocksDB,速度也比其他系统快。虽然结果的概要类似于上面的Config-Optane,但是我们可以看到KVell和竞争对手之间的性能差距是如何拉大的,因为KVell能够更好地利用磁盘,而其他系统则是cpu绑定的。

AWS磁盘有不同的最大IOPS取决于读/写比率。KVell在混合读写操作的工作负载上最大化磁盘IOPS。在这些工作负载上,KVell将50%的时间用于等待,20%用于查找,10%用于管理回调(malloc和free), 20%用于I/O函数。KVell以3.8m req/s的速度在均匀密钥分发中成为cpu绑定,使用了磁盘能够维持的3.3m只读IOPS的75%。然而,它仍然比现有的KVs快5.6倍。在Config-Amazon-8NVMe机器上,这样的工作负载会受到cpu限制。当内核在I/O函数之外花费的时间超过其时间的一小部分时,这台机器就无法达到只读IOPS的峰值。在一个微基准测试中,我们测量了每个I/O请求使用超过3us的CPU周期将可达到的IOPS限制为最大IOPS的75%。

尽管存在这些限制,但KVell甚至在扫描工作负载上也优于现有的KVs因为它的开销较低(例如,没有争用页面缓存和批处理请求到磁盘)。

与Config-Optane的情况一样,LSM和B树KVs受到吞吐量波动和高尾延迟(10秒以上)的困扰。在YCSB E中,RocksDB的每秒吞吐量经常低于6K扫描/s(相比之下,平均为57K扫描/s)。在运行维护操作时,RocksDB、PebblesDB和WiredTiger只能维持它们平均速度的一小部分(例如,RocksDB的YCSB a平均速度只有5%)。

我们在KVell中观察到的最大延迟是YCSB A的20ms (99p 12ms), YCSB C的12ms (99p 2.9ms)。

img

图****8. Config-Amazon-8NVMe。 YCSB平均吞吐量,统一密钥分配。 KVell全面超越竞争对手系统。

img

图****9. A. Config-Optane。 生产工作负载平均吞吐量。 在两种工作负载下,KVell的性能均比所有竞争对手的系统高出约4倍。 B.Config-Amazon-8NVMe。 具有统一密钥分配的YCSB 5TB数据集上的KVell吞吐量。 在所有工作负载中,KVell随数据集的大小扩展。

6.4.2 Nutanix生产工作负载

图9 A展示了RocksDB、PebblesDB、TokuMX、WiredTiger和KVell在Nutanix的两个写密集型生产工作负载上的性能。在第一个生产工作负载中,21%的读请求来自缓存,而在第二个工作负载中,99%的读请求来自缓存。就平均吞吐量而言,KVell在生产工作负载方面都比RocksDB(性能仅次于它的竞争对手)高出大约4倍。KVell还设法在第一个生产工作负载中最大化磁盘带宽,并在第二个工作负载中使用78%的磁盘带宽(在第二个工作负载中,磁盘仅用于更新,其他所有请求都来自缓存)。

与之前的实验类似,RocksDB的吞吐量随着时间的推移出现了很大的波动,低至3K请求/秒。另一方面,KVell在吞吐量方面表现出最小的波动。在延迟方面,KVell在最大延迟方面比RocksDB多3个数量级(RocksDB是8秒,KVell是2.5毫秒),在99百分位上比RocksDB多一个数量级(RocksDB是12毫秒,KVell是1.7毫秒)。

6.4.3配置在Config-Amazon-8NVMe上的YCSB 5TB数据库

图9 B显示了在包含5B (5TB)密钥的数据库上,YCSB基准测试中的KVell的性能。与前面的实验一样,KVell被配置为使用30GB的页面缓存。我们执行这个实验是为了测试KVell在数据集大小方面的可伸缩性。

在这个实验中,KVell只保存了0.6%的项。由于键的访问是统一的,所以大多数读和写都是从磁盘提供的。在YCSB A中,这导致每个请求平均有1.5个I/Os,即,最大1.4m IOPS/1.5 = 935K请求/s。KVell实现866K请求/秒,92%的峰值带宽。在YCSB C和E, KVell执行高达2.7m请求/秒和52K扫描/秒;数字比100M项数据集的数字略低,因为缓存的数据更少,并且在内存索引中查找平均要多花25%的时间。

6.5权衡和限制

6.5.1平均延迟

KVell分批提交I/O请求。在饱和IOPS(大批次)和最小化平均延迟(小批次)之间进行权衡。对于Config-Optane上的YCSB A, KVell可以最大限度地利用磁盘带宽,每个worker处理64个批次的请求,平均延迟为158us。当批处理大小为32个请求时,平均延迟降低到76us,磁盘占用最大带宽的88%。

6.5.2项规模的影响

对于未缓存的项,项大小不会影响Get()和Update()请求的性能。但是,对于保持条目有序的KVs,条目大小会影响扫描速度。图10显示了RocksDB,在执行压缩时的RocksDB和KVell的平均吞吐量,其中YCSB E上的项大小不同(扫描占优势)。由于KVell不对磁盘上的项进行排序,因此无论项的大小如何,它平均为每个扫描项读取一个4KB的页面,并且性能稳定。对于小条目,RocksDB优于KVell,因为它读取的页面比KVell少(64B项少64倍)。随着项大小的增长,保持项排序的好处就会减少。

在运行压缩时,对于所有的项大小,RocksDB只能维持其吞吐量的一小部分。虽然KVell不是为处理小项目而设计的,但它在所有配置中都提供可预测的性能。

img

图****10. Config-Amazon-8NVMe。 RocksDB和KVell的YCSB E(扫描为主)吞吐量。

6.5.3内存大小的影响

表6给出了索引可以执行的查询次数,这取决于Config-Amazon-8NVMe上索引大小与可用RAM的比率。当索引适合RAM时,工作人员可以在一个统一的工作负载上进行总共1500万次/秒的查找(如果以Zipfian方式访问索引,有更好的数据缓存,则为24M)。当索引比分配的RAM大5倍时,在统一的工作负载上,这个数目下降到109K /s。因此,当索引无法装入RAM时,它们就会成为瓶颈。KVell只支持将索引部分刷新到磁盘,以避免索引超过可用RAM时崩溃,但没有优化到在这种情况下工作。在实践中,索引的内存开销非常低,足以让它们在RAM中适合大多数工作负载(例如,对于YCSB, 100GB数据集索引为1.7GB)。

img

表****6. Config-Amazon-8NVMe. 内存索引在具有100M键的受限内存环境中可以维持的操作数(查找或插入)。

6.5.4较旧的驱动器(Config-SSD)

在Config-SSD上,KVell在读写方面与LSM KVs相当,但是在扫描方面提供了相对较低的平均性能(KVell是3K扫描/s, RocksDB是15K扫描/s, PebblesDB是5K扫描/s)。在较旧的驱动器上,花费CPU周期来优化磁盘访问通常是有益的,而且没有系统是CPU绑定的。压缩仍然会争夺磁盘资源,造成延迟高峰(18s以上)和吞吐量波动(最极端的是11扫描/秒,而RocksDB的平均扫描次数是15K)。KVell的延迟仅受峰值磁盘延迟(在配置- ssd上为100ms)的限制。因此,在旧驱动器上使用KVell vs. LSM KVs是一种权衡:如果稳定性和延迟的可预测性很重要,那么KVell是比LSM KVs更好的选择,但是对于扫描为主的工作负载,LSM KVs在旧驱动器上提供更高的平均性能。

6.6恢复时间

KVell的恢复时间取决于数据库的大小,而其他系统的恢复时间主要取决于提交日志的最大大小。对于所有系统,我们都使用默认的提交日志配置,并测量YCSB数据库上的恢复时间(100M密钥,100GB)。我们通过在YCSB a工作负载中间终止数据库来模拟崩溃,并测量Config-Amazon-8NVMe上数据库的恢复时间。KVell需要6.6秒扫描数据库并从崩溃中恢复,最大限度地提高磁盘带宽。RocksDB和WiredTiger平均恢复时间分别为18s和24s。这两个系统主要花费时间回放来自提交日志的查询和重新构建索引。尽管KVell针对无故障操作进行了优化,并且必须扫描整个数据库才能从崩溃中恢复,但它的恢复时间比现有系统的恢复时间要短。

7相关工作

7.1 ssd的KVs

LSM[36, 39]是最流行的书面优化KVs设计之一,例如,在LevelDB [11], RocksDB [15], HyperLevelDB [18], HyperDex[13]和Cassandra[16]中使用。将最初为硬盘驱动器设计的LSM kv改造为ssd已经做了很多工作。WiscKey[30]和HashKV[8]通过在LSM树中保持键的排序来进行ssd优化,而值则分别存储在日志中。与KVell类似,它们探索了打破顺序性的想法,然而,两个系统仍然执行昂贵的后台压缩,这与客户端操作相竞争。

PebblesDB[43]使用片段LSM树,通过将片段LSM树延迟到LSM树的最后一层来减少压缩的影响。SILK为LSM KVs提出了一个I/O调度器,以减少压缩对客户端请求延迟[6]的影响。三联[5]使用不同技术的组合来减少写放大。在高负载下,这三个系统最终都需要运行压缩,从而导致客户机操作的延迟峰值和吞吐量下降。

SlimDB[45]利用ssd,改进了LSM KVs中的索引和过滤方案,以获得良好的读取性能。NoveLSM [21], PapyrusKV[22]和NVMRocks[14]采用LSM设计来适应持久化存储器,平滑了RAM和SSD之间的过渡。与KVell不同,这些LSM增强仍然保留了密钥顺序性。

BetrFS[19]和TokuMX[42]利用Bϵ树[7]t o write-optimized之间取得平衡和读取最优化的数据结构,仍然利用钥匙的前后顺序。正如第3节中所讨论的,虽然树对于小的键很有效,但是对于中、大的键,它们的性能很差,而这正是KVell的目标工作负载。

更普遍的是,在以前的工作中已经注意到CPU开销限制了高速磁盘上的性能,并提出了新的KV设计。淤泥[28]i是一个KV的闪存驱动器设计。淤泥探索了使用小内存索引在磁盘上执行高效查找的想法,但是依赖于昂贵的后台操作将数据转换和合并到磁盘上的HashStores和排序stores中。相反,KVell不需要后台操作就可以将数据持久化到磁盘上。Udepot[24]使用内存中的哈希表来查找存储在NVMe驱动器上的数据。Udepot使用锁来防止磁盘页和内存结构上的竞争,使用垃圾收集器来避免磁盘碎片,并且不支持扫描。Papagiannis等人[40,41]提出了支持随机I/O的备选KV设计,以减少SSD和NVMe驱动器上的CPU开销。在杜鹃座[40],Papagiannis称Bϵ树等人修改,删除缓冲指数水平。Tucana仍然依赖于叶级缓存来获得良好的性能,也依赖于页面缓存来缓存数据。在KVell中,我们说明了缓冲在高速驱动器上不再有用,页面缓存严重限制了性能,单个共享数据结构上的争用可能导致不必要的开销。在Kreon[41]中,Papagiannis等人修改了LSM KVs以减少压缩的开销。它们只对磁盘上的键进行排序,并使用小型随机I/Os将它们合并到LSM树的不同级别。Kreon依靠缓冲来实现良好的性能,并且每分钟只刷新它的10到磁盘来实现峰值吞吐量。与传统的LSM设计相似,维护工作的积累会导致CPU使用量的激增,从而可能导致性能的波动。相反,KVell不使用任何维护操作,并提供稳定的吞吐量。

LOCS[47]、BlueCache[51]和NVMKV[32]是ssd上向操作系统公开FTL操作的有效KVs。这样的ssd很少见,而且优化是与特定的硬件设计相关联的。在这项工作中,我们说明了没有必要进行特定于底层硬件的优化,并且可以通过通用系统调用以低延迟和最大磁盘带宽执行I/O操作。Mickens等人[34]和Klimovic等人[23]采用了存储分解,以便能够利用数据中心的全部磁盘带宽。KVell的目标是利用单机上的全部磁盘带宽。

7.2KVs for byte-addressable persistent memory

大量的工作提出了为字节寻址持久性内存(PM)优化的数据结构[4,17,50,53,54]。HiKV[50]是一个混合KV商店。HiKV的重点是改进对PM中存储的散列表执行的请求的延迟。哈希表用于快速查找,存储在RAM中的全局B+树用于加速范围查询。

Kvell面临在块设备上存在不同的延迟挑战,在磁盘上无序存储数据以避免昂贵的迁移,并使用perthread结构来避免系统中的争用点。Bullet[17]使用交叉引用日志在KV存储库中创建DRAM和PM访问之间的无缝过渡。Zuo等人[54]提出了一种针对PM优化的写优化哈希索引方案。该技术优化了点查询,但不支持有效的范围查询。各种树型算法也已被调整,可以直接在PM中持久化数据,而不需要DRAM结构[3,9,25,38,46,52]。相比之下,KVell关注的是快速块寻址ssd,我们相信这对于大型数据存储来说仍然是更划算的选择。SLM-DB[20]结合了LSM设计和B树来利用PM。与KVell一样,它维护B树索引快速查找(SLM-DB的索引在PM中,而KVell在DRAM中维护)。SLM-DB依赖压实操作对持久化数据进行排序。

7.3用于内存数据存储的KVs

与KVell类似,MICA[29]和Minos[12]采用分区设计,将KV项目的不重叠碎片分配给每个系统线程。但是,Minos不是按键范围分区,而是按KV项大小分区。Masstree[31]使用并发B+树的组合,强调缓存的有效使用。与KVell不同,Masstree假设整个工作集适合内存。RAMCloud[37]是一个基于戏剧的KV存储,强调快速并行恢复。Li等人[27]开发了一个全栈内存KV存储,实现高吞吐量,考虑到硬件特性来创建一个高效的设计——硬件特性也被KVell考虑在内。

8.结论

现有的KV存储设计在老一代ssd上是有效的,但在现代ssd上执行不是最优的。我们已经展示了一种依赖于sharenothing架构、磁盘上未排序的数据和随机I/Os批的流线化方法在快速驱动器上的性能优于现有的KVs,甚至对于扫描工作负载也是如此。我们已经在KVell中建立了这些想法的原型。KVell提供了高且可预测的性能和强大的延迟保证。

致谢。我们要感谢我们的牧者迈克尔·卡明斯基(Michael Kaminsky)和匿名审稿人提供的所有有帮助的评论和建议。这项工作部分得到了瑞士国家科学基金会第513954和514009号拨款以及Nutanix公司的馈赠。

posted @ 2020-11-01 15:30  Hulab-StoSys  阅读(898)  评论(0编辑  收藏  举报