RocksDB: Evolution of Development Priorities in a Key-value Store Serving Large-scale Applications

摘要

本文是对RocksDB的开发优先级的八年回顾。RocksDB是Facebook开发的一个KV存储,针对大型分布式系统,并针对固态硬盘(ssd)进行了优化。我们描述了由于硬件趋势和在许多组织中大规模运行RocksDB的广泛经验,优先级是如何随时间演变的:从优化写放大,到空间放大,再到CPU利用率。我们描述了运行大规模应用程序的经验教训,包括需要在不同的RocksDB实例之间管理资源分配,数据格式需要保持向后和前向兼容,以允许增量软件推出,以及需要对数据库复制和备份的适当支持。来自故障处理的经验教训告诉我们,需要更早地检测到数据损坏错误,并且在系统的每一层都需要建立数据完整性保护机制。我们描述了对KV接口的改进。我们描述了一些在回顾起来被证明是被误导的努力。最后,我们描述了一些可能从未来的研究中受益的开放问题。

1、介绍

RocksDB是一个高性能、持久的KV存储引擎,由Facebook于2012年创建,基于Google的LevelDB代码库。它对固态硬盘(ssd)的特定特性进行了优化,针对大规模(分布式)应用程序,并被设计为嵌入在更高级应用程序中的库组件。因此,每个RocksDB实例只管理单个服务器节点的存储设备上的数据;它不处理任何主机间操作,如复制和负载平衡,也不执行高级操作,例如checkpoints——它将这些操作的实现留给了应用程序,但提供了适当的支持,以便它们能够有效地做到这一点。

RocksDB及其各种组件具有高度可定制的能力,允许存储引擎针对广泛的需求和工作负载进行定制;定制可以包括预写日志(WAL)处理、压缩策略和压缩策略(如第2节所述的删除死数据和优化LSM-trees的过程)。RocksDB可以调整为高写吞吐量、高读吞吐量、空间效率或介于两者之间。由于其可配置性,RocksDB被许多应用程序使用,代表了广泛的用例。仅在Facebook上,RocksDB就被30多个不同的应用程序使用,总共存储了数百pb的生产数据。除了用作数据库(如MySQL[61])的存储引擎外,RocksDB还用于以下具有高度不同特征的服务类型,如表1所示:

流处理:RocksDB用于在ApacheFlink[10]、Kafka[51]、Samza[69]中存储流数据。这些系统以独特的方式使用RocksDB。例如,有些人需要在检查流处理系统时检查RocksDB状态的能力。其他一些则需要时间窗函数,以便它们能够以一种能够有效地查询和回收时间窗的方式布局数据。

日志/排队服务:RocksDB被优步的Cherami[68],Iron.io [49],和Facebook的日志设备(同时使用ssd和hdd)[59]。这些服务需要高写吞吐量和低写放大量。通过利用RocksDB的可定制压缩方案,这些服务的写速度能够和追加到单个文件的效率一样,同时仍然受益于索引等特性。

索引服务:Rockset[87]、Airbnb的个性化搜索[98]和Facebook的Dragon[86]。这些服务需要良好的读取性能和将离线生成的数据大规模加载到索引服务中的能力,而RocksDB的批量加载特性最初是为此而创建的。

SSD上的缓存:一些内存缓存服务使用RocksDB存储从DRAM排出的数据到SSD上;这些数据包括网飞的EVCache[57]、奇虎的Pika[80]和Redis[74]。这些服务往往具有较高的写率和问题点的查找。有些实现了允许控制,因此并不是所有从DRAM缓存中回收的数据都被写入RocksDB。

还有其他类型的用例,比如在Ceph的blue存储库中,它使用RocksDB来存储元数据,以及WAL[2]和DNANexus的DNA测序[55]。之前的一篇论文分析了使用RocksDB[9]的几个应用。表2总结了从生产工作负载中获得的一些系统指标。

拥有一个可以支持许多不同用例的存储引擎,就可以跨不同的应用程序使用相同的存储引擎。实际上,让每个应用程序构建自己的存储子系统是有问题的,因为这样做很具有挑战性。即使是简单的应用程序,也需要使用校验和来防止媒体损坏,保证崩溃后的数据一致性,以正确的顺序发出正确的系统调用,以保证写操作的持久性,并以正确的方式处理从文件系统返回的错误。一个成熟的通用存储引擎可以在所有这些领域中提供复杂的功能。

当客户机应用程序在公共基础设施中运行时,拥有公共存储引擎带来的额外好处是:监视框架、性能分析工具和调试工具都可以共享。例如,公司内部的不同应用程序所有者可以利用相同的内部框架,该框架向相同的仪表板报告统计数据,使用相同的工具监控系统,并使用相同的嵌入式管理服务管理RocksDB。这种整合不仅允许在不同的团队之间轻松地重用专业知识,而且还允许将信息聚合到公共门户中,并鼓励开发工具来管理它们。

鉴于已经采用了RocksDB的各种应用程序集,RocksDB的开发优先级随着时间的推移而发展是很自然的。本文描述了在过去的8年里,我们从现实应用程序(在Facebook和其他组织中)中学到实际的教训,并观察到硬件趋势的变化,使我们重新审视一些早期的假设。我们还描述了我们的RocksDB在不久的将来的开发优先事项。

第2节提供了ssd和(LSM)树[72]的背景知识。从一开始,RocksDB就选择LSM-tree作为其主要数据结构,来解决读写性能的不对称性和基于闪存的ssd的有限耐受性。我们相信LSM-trees对RocksDB很有用,并认为它能够很好地适应即将到来的硬件趋势(第3节)。LSM-tree数据结构是RocksDB能够适应具有不同需求的不同类型的应用程序的原因之一。但是第3节描述了我们的主要优化目标如何从最小化写放大到最小化空间放大,以及从优化性能到优化效率。

第4-8节描述了我们多年来所吸取的一些经验和经验教训。例如,从服务大规模分布式系统(第4节)中获得的经验教训包括(i)必须跨多个RocksDB实例管理资源分配,因为单个服务器可能托管多个实例;(ii)所使用的数据格式必须向后和向前兼容,因为RocksDB软件更新会逐步部署/回滚;以及(iiii)对数据库复制和备份的适当支持很重要。从处理故障(第5节)获得的教训包括(i)需要及早检测数据损坏,以减少数据不可用性和丢失;(ii)完整性保护必须覆盖整个系统,以防止无声损坏传播到副本和客户端;(iii)错误需要以不同的方式处理。与配置管理相关的课程(第6节)表明,使RocksDB具有高度的可配置性,已经启用了许多不同类型的应用程序,并且合适的配置可以对性能有巨大的积极影响,但是配置管理可能太具有挑战性了,需要进行简化和自动化。关于RocksDBAPI的经验(第7节)的经验表明,核心接口的灵活性简单而强大,但限制了一些重要用例的性能;我们提出了通过支持应用程序定义的时间戳和列来改进接口的想法。最后,我们提出了一些发展计划,回顾起来被证明被误导了(第8节)。

第10节列出了RocksDB将从未来的研究中受益的几个领域。最后是第11节的结束语。

2、背景

基于闪存的ssd的特性深刻地影响了RocksDB的设计。读写性能的不对称性和有限的耐受性给数据结构和系统架构的设计带来了挑战和机遇。因此,RocksDB采用了闪存友好的数据结构,并针对现代硬件进行了优化。

2.1基于闪存的ssd上的嵌入式存储

在过去的十年里,我们见证了提供在线数据的ssd的激增。低延迟和高吞吐量设备不仅挑战软件充分利用其功能,而且改变了有状态服务的数量。SSD每秒提供成千上万的输入/输出操作(IOPS),这比旋转硬盘驱动器(HDD)快数千倍。它还可以支持数百个mb的带宽。然而,由于程序/擦除周期的数量有限,高写带宽无法持续下去。这些因素为重新考虑存储引擎的数据结构提供了机会,以优化该硬件。

在许多情况下,SSD的高性能也将性能瓶颈从设备I/O转移到网络上,包括延迟和吞吐量。对于应用程序来说,设计其架构以在本地ssd上存储数据,而不是使用远程数据存储服务变得更具吸引力。这增加了对可以嵌入到应用程序中的键值存储引擎的需求。

创建RocksDB是为了满足这些需求。我们希望创建一个灵活的键值存储,以使用本地SSD驱动器服务于广泛的应用程序,同时对SSD的特性进行优化。LSM-trees在实现这些目标中发挥了重要作用。

2.2RocksDB架构及其对LSM-trees的使用

RocksDB使用LSMtrees[72]作为其主要数据结构,通过以下关键操作来存储数据:

写。每当数据被写入RocksDB时,写入的数据都会被添加到一个名为MemTable的内存写缓冲区中,以及一个磁盘上的预写日志(WAL)中。MemTable被实现为一个跳过列表,通过O(logn)插入和搜索开销来保持数据排序。WAL用于故障后的恢复,但不是强制性的。一旦MemTable的大小达到配置的大小,则(i)MemTable和WAL变得不可变,(ii)为后续写入分配新的MemTable和WAL,(iii)MemTable的内容被刷新到磁盘上的排序字符串表(SSTable)数据文件中,(iv)回收刷新的MemTable和关联的WAL。每个SSTable按排序的顺序存储数据,并划分为统一大小的块。一旦写好了,每个SSTable都是不可变的。每个SSTable都有一个索引块,每个SSTable块都有一个索引项,用于二进制搜索。

压缩。LSM-树有多个层次,如图1所示。最新的SSTable由MemTable刷新创建,如上所述,并放置在0层。其他层则由一个称为压缩的进程创建。每个层的最大大小都受到配置参数的限制。当超过层L的大小目标时,选择层L中的一些SSTable并与(L+1)层中重叠的SSTABLE进行合并,以在(L+1)层中创建新的SSTable。这样,删除和覆盖的数据,新的SSTable为读取性能和空间效率进行了优化。此过程逐渐将写入的数据从0层迁移到最后一级。压缩I/O是有效的,因为它可以并行化,并且只涉及对整个文件的批量读取和写。(为了避免与术语“高”和“低”混淆,因为在描绘多个层的图像中,高的层通常较低,我们将高的层称为较老的层。)

MemTable和0级SSTable有重叠的键范围,因为它们包含密钥空间中的任何地方的键。每个较老的层,即第1层或较老的层,都由覆盖密钥空间不重叠分区的SSTBables组成。为了节省磁盘空间,可以选择压缩旧层的SSTbable块。

读取。在读取路径中,首先查找所有MemTable,然后搜索所有0级SSTBable,然后搜索分区覆盖查找键的连续较旧级别的SSTBables。在每种情况下都使用了二进制搜索。搜索继续,直到找到键,或者确定该键不在最老的级别中。1热SSTable块缓存在基于内存的块缓存中,以减少I/O和解压缩开销。Bloom过滤器用于消除SSTables中大多数不必要的搜索。

RocksDB支持多种不同类型的压缩[23]。水平压缩采用LevelDB,然后改进[19]。在这种压缩风格中,级别被分配为指数增长的大小目标,如图1中的虚线框所示。会主动启动压缩,以确保不超过目标大小。分层压缩(在RocksDB[26]中称为通用压缩)类似于Apache卡桑德拉或HBase[36,37,58]所使用的压缩。当0级文件数量和非零级别数量的总和超过可配置的阈值,或者当总数据库的大小超过最大级别的大小超过阈值时,多个SSTables被延迟地压缩在一起。实际上,压缩会延迟到读取性能或空间效率下降,因此可以完全压缩更多的数据。最后,FIFO压缩一旦数据库达到大小限制,就只丢弃旧的SSTbables,并且只执行轻量级压缩。它的目标是内存中的缓存应用程序。

能够配置压缩类型使RocksDB能够提供广泛的用例。通过使用不同的压缩样式,RocksDB可以配置为读友好、写友好或非常写友好(用于特殊的缓存工作负载)。然而,应用程序的拥有者将需要考虑在其特定用例[4]的不同指标之间的权衡。一种更懒的压缩算法提高了写放大和写吞吐量,但读取性能受到影响。相比之下,更激进的压缩牺牲了写放大,但允许更快的读取。日志记录或流处理等服务可以使用重写的设置,而数据库服务需要一种平衡的方法。表3通过微观基准测试结果描述了这种灵活性。

在2014年,我们添加了一个名为列族2[22]的特性,它允许不同的独立密钥空间在一个DB中共存。每个KV对只与一个列族相关联(默认为默认的列族),而不同的列族可以包含具有相同键的KV对。每个列族都有自己的表和存储表,但是它们共享WAL。列族的好处包括:

(1)每个列族都可以独立配置,也就是说,它们都可以具有不同的压缩、压缩、合并运算符(第6.2节)和压缩过滤器(第6.2节);

(2)共享的WAL允许原子写入不同的列族3;并且

(3)可以删除现有的列族,并且可以动态和有效地创建新的列族。

列族被广泛使用。使用它们的一种方法是允许对同一数据库中不同类别的数据使用不同的压缩策略;例如,在数据库中,一些数据范围可能重写,其他范围可能重读,在这种情况下,通过将两个不同类别的数据放置为配置为使用不同压缩策略的两个不同列族,压缩可以更加有效。另一种方式列家庭使用是利用一个列家族可以有效地删除:如果已知数据成为过时4在一段时间内放置在同一个列家族,那么列家族可以在适当的时间删除无需显式地删除其中包含的KV对。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值