FAST2022 DEPART: Replica Decoupling for Distributed Key-Value Storage Qiang(翻译分析)

DEPART: Replica Decoupling for Distributed Key-Value Storage Qiang



摘要

现代分布式键值 (KV) 存储通过跨节点分布 KV 对的副本来采用复制以实现容错。然而,现有的分布式 KV 存储通常在同一索引结构中管理所有副本,从而导致超出复制冗余的大量 I/O 成本。
我们提出了一个称为副本解耦的概念,它将副本的主副本和冗余副本的存储管理解耦,从而不仅可以减轻索引中的 I/O 成本,还可以提供可调整的性能。
特别是,我们设计了一种新颖的两层日志,可以对冗余副本进行可调排序,以实现平衡的读/写性能。
我们在 Cassandra 上实现了一个分布式 KV 存储原型 DEPART。

实验表明,在各种一致性级别和参数设置下,DEPART 在所有性能方面都优于 Cassandra。具体来说,在最终一致性设置下,DEPART 的写入、读取、扫描和更新吞吐量分别高达 1.43 倍、2.43 倍、2.68 倍和 1.44 倍。


一、Introduction

键值(KV)存储是现代数据密集型应用程序存储基础设施的基本构建块,例如网络搜索 [14、31]、社交网络 [57]、照片存储 [10] 和云存储[25,37]。为了支持大规模使用,KV 存储通常以分布式方式部署,将数据对象(以 KV 对的形式)存储在多个节点上。分布式 KV 存储的示例包括 BigTable [14]、HBase [3]、Dynamo [25]、HyperDex [28]、Cassandra [37]、TiKV [50] 和 Riak [54]。
在任何大规模部署中,故障变得普遍,因此为分布式 KV 存储提供容错至关重要。 复制仍然是现代分布式 KV 存储中常用的容错机制(包括上面列出的示例 [3, 14,25,28,37,50,54])。 具体来说,对于用户写入的每个 KV 对,复制会制作多个精确的副本(称为副本)并将副本分布在不同的节点上,以便容忍任何节点故障。
一个微妙之处在于,每个节点内部都将所有副本存储在同一个索引结构中; 我们称这种方法为统一索引。 例如,我们检查了各种开源分布式 KV 存储的代码库,包括 HBase [3]、HyperDex [28]、Cassandra [37]、TiKV [50] 和 ScyllaDB [60],它们都采用 用于副本管理的统一索引。

统一索引很容易实现副本管理,但它也会显着降低写入和读取性能。
统一索引,就是所有的数据在同一个LSM-tree里面

首先,LSM-tree 执行频繁的压缩操作,重写当前存储的 KV 对以保持它们在每个级别中的排序顺序。 将所有副本存储在同一个 LSM-tree 中会加剧超出复制冗余的写入放大。 例如,禁用复制时,Cassandra 和 TiKV 的写入放大分别为 6.5 倍和 13.8 倍; 然而,在三重复制下,Cassandra 和 TiKV 中的写入放大分别达到 25.7 倍和 50.9 倍,导致写入放大超过三倍(第 3.1 节)。 此外,由于读取 KV 对需要在 LSM-tree 中搜索多个级别,因此统一索引会放大搜索空间并加剧读取放大。 例如,在三重复制下,Cassandra 的读取放大达到 34.6 倍(第 3.1 节)。

我们的见解是,如果我们使用不同的索引结构来管理不同类型副本的存储,而不是将所有副本放在同一个索引结构中,我们不仅可以通过减小索引结构的大小来减轻读/写放大对于每种类型的副本,还可以实现灵活的存储管理以适应不同的设计权衡。 我们通过提出副本解耦来做一个案例,即基于主副本(即KV对的主副本)和冗余副本(即KV的剩余副本)对每个KV对的副本的存储管理进行解耦将主副本放在一边)。 我们使用 LSM-tree 仅管理主副本,以保留 LSM-tree 的设计特征,但以更轻量级的方式; 同时,我们对冗余副本使用更简单但可调的索引结构,以根据性能要求平衡读写性能。

在本文中,我们在 DEPART 中设计了副本解耦,这是一种新颖的分布式 KV 存储,它将主副本和冗余副本的存储管理解耦以实现容错。 DEPART 建立在 Cassandra [37] 之上。它支持关键 I/O 路径上副本的主副本和冗余副本的轻量级区分,同时保持 Cassandra 的现有数据组织和可配置的一致性特性。在管理 LSM-tree 中的主副本时,DEPART 提出了一种新颖的两层日志来管理具有可调顺序的冗余副本,以实现平衡的读写性能。它的想法是将冗余副本的批量写入发布到仅附加的全局日志中,以实现高写入性能。它进一步将全局日志拆分为多个本地日志。特别是,每个本地日志中 KV 对的顺序可以通过单个参数进行调整,以平衡冗余副本的读写性能;
例如,给定高读(或写)一致性级别(即,在成功操作中要读(或写)的副本数;参见 §2.3),可以调整两层日志以有利于高读(或写)表现。两层日志还通过按不同的键范围组织 KV 对并将恢复操作限制为仅访问相关范围的 KV 对来提高故障恢复性能。

*贡献:

第一:
提出问题,用先有的cassandra 和 Tikv来说明目前的分布式KV系统存在的问题。大量的写放大、读放大
我们分析了两个最先进的分布式 KV 存储,Cassandra 和 TiKV,并揭示了由于副本的统一索引而导致的性能限制。
第二:
因此开始设计,两个副本其中一个进行结构调整。并行化快速实现故障恢复。
• 我们设计了DEPART,它实现了副本解耦,并具有几个关键设计特点:(i)主副本和冗余副本的轻量级区分,(ii)冗余副本的可调排序的两层日志设计,以及(iii) 通过并行化实现快速故障恢复。
• 我们在 Cassandra v3.11.4 [2] 上实施 DEPART。 实验表明,DEPART 在各种设置中都优于 Cassandra。 例如,对于最终一致性的情况,DEPART 在写入、读取、扫描和更新方面分别比 Cassandra 实现了 1.43 倍、2.43 倍、2.68 倍和 1.44 倍的吞吐量增益。 DEPART 还在各种一致性级别配置下保持其读写性能增益。


二、背景

我们以 Cassandra [37](作为我们 DEPART 设计的基线)为例来描述分布式 KV 存储的背景,包括其存储架构、I/O 工作流和一致性管理。

2.1存储架构

**数据组织。**分布式 KV 存储跨节点集群划分 KV 对。在 Cassandra 中,KV 对基于一致性哈希 [33] 进行分区,其他生产分布式 KV 存储 [25、41、54、60] 也采用了这种方法。一致性哈希将所有节点的位置与哈希环相关联,并将每个 KV 对确定性地映射到一个节点。具体来说,我们考虑一个具有 n 个物理节点的分布式 KV 存储,每个物理节点都与 v 个虚拟节点相关联。它将哈希环划分为 n×v 个范围,每个范围覆盖一个虚拟节点。例如,如图 1 所示,有 n = 5 个物理节点(即 N0 到 N4),每个物理节点 v = 2 个虚拟节点。哈希环包含 2×5 = 10 个范围,例如 (0-10)、(11-20)、···、(91-100)。每个范围都与哈希环中顺时针方向最近的虚拟节点和对应的物理节点相关联;例如,范围 (0-10) 和 (51-60) 都分配给 N0。对于每个 KV 对,一致性哈希将密钥哈希到哈希环中的某个位置(例如,在 Cassandra 中使用 MurmurHash [6])。然后将 KV 对存储在与范围关联的相应物理节点中。
问题:是每个node写一个lsm-tree?
复制通常用于现代分布式 KV 存储 [3, 14, 25, 28, 37, 50] 以实现容错,通过将每个 KV 对的副本分布在不同节点上以防止节点故障。 在 Cassandra 中,副本存储在哈希环中顺时针方向的节点序列中,记为 Ni,N(i+1) mod n, N(i+2) mod n, ···,其中 0 ≤ i ≤ n-1 和 Ni(即节点序列中的第一个节点)是 KV 对基于一致性哈希散列到的节点。 我们将存储在 Ni 中的副本称为主副本,而将在哈希环中沿顺时针方向存储在连续物理节点中的剩余副本称为冗余副本。

带有 LSM 树的内部存储。 每个节点在内部管理具有某种索引结构的 KV 对。 特别是,LSM-tree [48] 是分布式 KV 存储中最常用的索引结构之一,包括 Cassandra 等 [3, 20, 28, 41, 50, 60]。 LSM-tree KV store 将 KV 对组织在多级树中,并保持 KV 对在每一级中按键排序,以支持高效的读取、写入和扫描。 如图1所示,LSM-tree KV存储维护了一个基于树的索引结构,多级(记为L0,L1,···),容量不断增加,每一级以文件为单位存储KV对 称为SSTables。 它首先将写入的 KV 对附加到磁盘上预写日志 (WAL) 中,然后将它们插入到内存中的 MemTable 中。 当 MemTable 已满时,LSM-tree KV 存储将 MemTable 转换为不可变的 MemTable,即作为 SSTable 刷新到最低级别 L0。当较低级别达到容量限制时,LSM-tree KV 存储通过压缩将较低级别的 KV 对合并到下一个较高级别。为了保持每个级别中的 KV 对排序,压缩操作首先从两个级别读取 KV 对,合并排序的 KV 对,然后写回排序的 KV 对。因此,压缩在写入期间会产生额外的 I/O,从而导致写入放大。另外,由于 KV 对没有跨层排序,读取一个 KV 对需要从最低层 L0 向上搜索,导致读取放大。写入和读取放大都被证明会导致 LSM-tree KV 存储中的性能显着下降 [12, 43, 51]。

2.2 I/Oworkflows

编写工作流程。 在 Cassandra 中编写 KV 对的工作原理如下。 客户端首先随机选择并连接到其中一个节点,称为协调器,并将 KV 对发送给它。 协调器根据一致的散列确定存储主副本和冗余副本的节点。 然后它将 KV 对转发到节点。
阅读工作流程。 在 Cassandra 中读取 KV 对类似于编写 KV 对,工作原理如下。 客户首先选择并联系协调员。 它向协调器发出读取请求,协调器查找存储 KV 对的副本(无论主副本和冗余副本)的节点。 对于负载平衡,协调器更喜欢从具有低延迟的节点读取 KV 对,由动态 snitching 模块 [5] 确定。 然后它将 KV 对返回给客户端.

2.3 一致性管理

一致性管理
Cassandra 支持不同的一致性模式,例如强一致性和最终一致性。 它们是通过调整复制因子(由 k 表示)以及读取一致性级别 (RCL) 和写入一致性级别 (WCL) 来配置的。 复制因子 k 定义为容错的副本总数。 RCL 和 WCL 被定义为由协调器读取和写入的最小副本数(无论是主副本还是冗余副本),以分别确认成功的读取和写入操作。 RCL 和 WCL 都设置为从 1 到 k 的整数。 如果WCL+RCL>k,则提供强一致性; 如果 WCL+RCL ≤ k,则提供最终一致性。 默认情况下,Cassandra 中的 WCL 和 RCL 都设置为 1。
k设置多少???

3副本解耦

为了激发副本解耦,我们描述了统一索引在内部存储管理中管理所有副本的局限性(第 3.1 节)。 我们还描述了用于激发我们的 DEPART 设计(第 3.2 节)的朴素副本解耦设计。

3.1 统一索引及其局限性

回顾§1,现有的分布式 KV 存储(例如 [3,28,37,50])主要采用统一索引,其中为每个节点指定的所有副本(包括所有主副本和冗余副本)都在相同的索引结构下管理 . 我们表明,统一索引是显着加剧 LSM 树的写入和读取放大的主要原因,而不是来自复制的额外写入。
如何证明的?

限制#1: 写放大加重。通过统一索引,每个节点将所有副本视为常规 KV 对,并将它们无区别地存储在同一 LSM 树中(图 1)。为了展示它如何加剧写入放大,我们评估了两个开源分布式 KV 存储 Cassandra (v3.11.4) [2] 和 TiKV (release 4.0) [50] 的写入放大。具体来说,我们将 Cassandra 和 TiKV 部署在具有默认设置的 5 节点集群上(详见第 5 节)。我们配置一个客户端机器向最初有空存储的集群发出 300M KV 对,每个大小为 1 KiB 的写入。我们考虑无复制 (k = 1)、双重复制 (k = 2) 和三重复制 (k = 3)。图 2(a) 显示,由于 LSM-tree 导致的压缩开销,无复制导致 Cassandra 的写入放大为 6.5 倍。然而,对于三重复制,写入放大增加到 25.7 倍,大约是无复制写入放大的 4 倍。我们还观察到 TiKV 的类似趋势,其中写入放大从 13.8 倍增加到 50.9 倍(即增加 3.7 倍)。
不会因为相同而被更新掉吗?复制的key和key本身有什么不同吗?复制的流程是如何走的

此外,随着 KV 存储大小的增加,写入放大增加更加显着,并呈现超线性趋势。 原因是较大的 KV 存储大小会增加 LSM 树中的层数,从而导致更高的压缩开销和更大的写入放大。 我们配置一个客户端机器来向最初的空集群发出 100 M、300 M 和 600M KV 对的写入,每个大小为 1 KiB。

控制了每个kv对的大小保持不变,来做对比。改变总的数据量

在这里,我们专注于 Cassandra。 图 2(b) 显示了 Cassandra 对不同 KV 存储大小的写入放大。 对于更大的数据存储,三重复制下的写入放大相对于无复制的增加也变得更大。 例如,三重复制在 100M KV 对下与没有复制相比具有 3.4 倍的写入放大,在 600M KV 对下变为 4.5 倍。 这种超线性趋势也意味着更大的 KV 存储大小会限制统一索引的可扩展性。

限制#2: 阅读放大加重。 统一的索引也严重加剧了读取放大。 主要原因是所有副本都存储在同一个 LSM-tree 中,从而扩大了 KV 对的搜索空间。 我们按照上述设置评估 Cassandra 和 TiKV 的读取放大,而客户端机器向现有的 300M KV 对发出 30M 读取,每个已存储的大小为 1 KiB。 图 3(a) 显示,对于 Cassandra,读取扩增从无复制的 7.8 倍增加到三重复制的 34.6 倍(即 4.4 倍增加)。 我们观察到 TiKV 的类似趋势。

此外,我们研究了 KV 存储大小对读取放大的影响。 在这里,我们专注于 Cassandra。 我们首先向最初的空集群发出 100M、300M 和 600M 大小为 1 KiB 的 KV 对的写入,然后向现有的 KV 对发出 30M 读取。 图 3(b) 显示了随着 Cassandra 的 KV 存储大小增加,读取放大的超线性增加。

综上所述,就是开了副本的情况下,读写的开销都变大了,且测试了数据进行证明,这个很好理解~就是总的数据量变多了,当然会出现该问题唠????
写放大是如何测试的?那个数据是写放大?

3.2motivation

我们在 §3.1 中的分析表明,统一索引会加剧写入和读取放大,因为在单个 LSM-tree 中管理所有副本的成本很高。 这促使我们探索副本解耦的潜力,它将副本的主副本和冗余副本解耦并在单独的索引结构中管理它们。 我们首先考虑两种简单的复制解耦方法,然后激发我们的设计。

天真的方法。 一种简单的副本解耦方法是部署两棵 LSM-tree,一个用于主副本,一个用于所有冗余副本。 然而,冗余副本的 LSM-tree 仍然有很大的尺寸(特别是对于一个大的复制因子),而并不是所有的冗余副本都在每个 I/O 操作中被访问。 例如,要在三重复制下恢复单节点故障,平均只有一半的冗余副本被访问。 因此,有额外的 I/O 用于在整个 LSM 树中搜索冗余副本的子集。
问题是剩下的两个冗余副本直接放在一个LSM-Tree里面,依然会存在搜索过多的问题。

另一种简单的副本解耦方法是为从每个 KV 对派生的 k 个副本管理 k 个 LSM 树(k 是复制因子)。 例如,对于具有三重复制的 Cassandra,节点 Ni 接收冗余副本,其对应的主副本存储在节点 N(i-1) mod n 和 N(i-2) mod n 中(其中 0 ≤ i ≤ n-1 和 n 是物理节点的数量)。 然后我们在节点 Ni 中使用三个 LSM-tree,其中一个存储主副本,另外两个分别存储来自节点 N(i-1) mod n 和 N(i-2) mod n 的冗余副本。
每一个副本一个LSM-Tree,本身不符合纠删编码的条带话管理策略,当然这个也是可以调整的,就是如何减少总的数据量小的情况下,确保检索。

但是,维护多个 LSM 树会导致大量内存和 I/O 开销。 由于每个 LSM-tree 都有自己的 MemTable 和不可变的 MemTable,因此复制因子 k 的内存开销会放大 k 倍。 具体来说,如果 MemTable 大小为 mMiB,集群大小为 n,则 Cassandra 的内存成本为 m×nMiB,因为每个节点都维护一个 LSM-tree。 但是,当在每个节点中使用 k 个 LSM-tree 时,内存成本变为 k×m×n MiB,这是 Cassandra 中的 k 倍。 请注意,如果我们减少每个 LSM-tree 的 MemTable 大小以限制内存开销,则会降低将 MemTable 刷新到磁盘的效率,从而降低用户写入性能 [7, 8]。
内存开销的增加!!!
此外,每个 LSM-tree 都会产生自己的压缩开销,以维护每个级别中的完全排序。 因此,压缩开销仍然很大,并且同一节点中多个 LSM 树的压缩操作会竞争磁盘带宽,因此会损害整体 I/O 性能。 我们的评估(第 5.2 节中的 Exp#1)表明,即使副本由不同的 LSM-tree 管理,与多个 LSM-tree 的副本解耦只能带来有限的性能提升。
compaction的时候IO的竞争,导致性能的提升有限

我们的方法。 回想一下,LSM-tree 在每一层中总是保持完全排序的顺序。 对统一索引中的所有副本使用单个 LSM-tree,或使用多个 LSM-tree 进行副本解耦,可能有利于提高读取性能,但它们都会产生相当高的压缩开销,从而降低写入性能。 特别是,不同的一致性级别意味着对副本的读取和写入的性能要求不同,因此高读(或写)一致性级别需要副本的高读(或写)性能。 这促使我们设计一种新的存储管理解决方案,该解决方案支持副本解耦的可调排序,以平衡读写性能。

所以就是把读写分开用不同的索引方式进行解决问题。

4 DEPART 设计

我们提出了DEPART,这是一个基于Cassandra 构建的分布式KV 存储,通过分离主副本和冗余副本的存储管理来实现副本解耦。 我们介绍了它的架构(§4.1)并阐述了它的设计技术(§4.2-§4.5)

4.1 整体架构

DEPART 将主副本和冗余副本的存储管理解耦以实现高性能。 它管理 LSM 树中的主副本,同时管理新颖的两层日志中的冗余副本,其冗余副本的顺序可根据性能要求进行调整。 仅在 LSM-tree 中保留主副本保留了 LSM-tree 的读取、写入和扫描的设计特征,但以更轻量级的方式,因为 LSM-tree 的大小现在明显更小,没有冗余副本。 此外,两层日志的可调排序允许在不同的一致性级别设置下平衡读写性能。

图 4 描述了 DEPART 的体系结构。 请注意,DEPART 只修改了每个 Cassandra 节点的内部存储模块,但保留了 Cassandra 中的节点间管理(例如,用于数据组织和一致性管理的一致性哈希)。 总之,DEPART 通过多种技术解决了几个设计挑战。

• 轻量级复制差异化。 DEPART 将每个节点的存储模块中的主副本和冗余副本区分开来进行单独管理。它的副本区分是基于简单哈希计算的轻量级的,并且在关键 I/O 路径上产生有限的开销(第 4.2 节)。

• 两层原木设计。 DEPART通过两层日志管理冗余副本,实现快速写入和高效恢复。它首先将冗余副本作为顺序批量写入附加到全局日志。然后它在后台将全局日志拆分为多个本地日志(第 4.3 节)。

• 可调排序。 DEPART 还为两层日志设计提供了一种可调排序方案,通过单个参数调整冗余副本的排序程度,从而平衡访问冗余副本的读写性能(第 4.4 节)。

• 并行恢复。 DEPART 采用并行恢复方案,在恢复过程中并行读写主副本和冗余副本,从而实现高恢复性能(§4.5)。

4.2 副本分化

DEPART 将每个节点的存储模块中写入的 KV 对区分为主副本或冗余副本。 图 5 描述了副本差异化工作流程。 回想一下,协调器将 KV 对的 k 个副本转发到散列环中沿顺时针方向的 k 个节点序列,其中 KV 对的密钥被散列到节点序列中的第一个节点(第 2 节)。 当一个节点,比如 N,从协调器接收到 KV 对的副本之一时,它对副本的密钥执行相同的哈希计算(即 Cassandra 中的 MurmurHash [6])并确定密钥到的节点是散列的。 如果结果节点与 N 本身相同,则 N 是节点序列中的第一个节点,我们将副本称为主副本; 否则,我们将副本称为冗余副本。

每个节点维护一个预写日志(WAL)和一个用于 LSM 树(用于主副本)和两层日志(用于冗余副本)的 MemTable。 节点区分 KV 对是主副本还是冗余副本后,将 KV 对写入相应的 WAL 和 MemTable 并确认协调器。 当 MemTable 已满并变为不可变时,节点会将不可变的 MemTable 刷新到 LSM-tree 或两层日志。 副本差异化的逻辑是轻量级的,因为它需要在关键 I/O 路径中的每个存储节点中进行一次额外的哈希计算(复制因子 k 总共需要 k 次额外计算)。 我们的实验表明,微分时间小于总写入时间的 0.4%(第 5.2 节中的 Exp#5)。
这里的额外的计算是什么?为啥hash,hash后为啥就少了?

4.3 两层日志设计

每个节点维护一个两层的日志,它是为管理冗余副本而设计的,具有以下设计特点。 首先,它支持冗余副本的快速写入,即使冗余副本的数量远大于主副本的数量并且随着复制因子的增加而增加。 其次,它支持可调排序以适应不同的一致性级别(第 4.4 节)。 第三,它通过允许并行快速读取冗余副本来支持任何故障节点的有效并行恢复。

图 6 显示了每个节点中的两层日志的架构。 收到副本后,节点首先将冗余副本的顺序批量写入全局日志。 后台线程不断从全局日志中检索冗余副本,并将它们拆分为多个本地日志。 我们在下面详细阐述全局日志和本地日志设计。

仅附加全局日志。 为了实现快速写入,每个节点将 KV 对的所有冗余副本(从不可变的 MemTable 中刷新)写入仅附加的全局日志。 所有冗余副本都以段为单位进行分组,并作为顺序批量写入附加到全局日志的头部。 请注意,全局日志仅存储所有冗余副本,而无需维护任何额外的索引结构。 因此,它实现了冗余副本的高写入性能。

将所有冗余副本保留在全局日志中可以实现高写入性能,但会带来两个问题。首先,恢复性能下降。对于全局日志中的冗余副本,其对应的主副本可能位于不同的节点中。当节点发生故障时,只需要全局日志中的部分冗余副本(即对应的主副本驻留在故障节点中的冗余副本)进行恢复。因此,恢复只会导致对全局日志的部分访问,从而导致大量随机 I/O。其次,垃圾收集成本增加。由于新的 KV 对被附加到日志头,无效(或陈旧)的 KV 对不能被覆盖,因此它们占用了大量空间。这会导致大量存储开销,尤其是在更新密集型工作负载中。垃圾回收可以通过不断从日志尾中回收无效KV对的空闲空间来降低存储成本,但不可避免地会引入大量额外的I/O来从日志尾读取段并写回有效KV对到日志头。

拆分为本地日志。 为了实现快速恢复,DEPART 维护了一个后台线程,不断地将全局日志拆分为多个本地日志,每个本地日志只保留其对应的主副本存储在同一节点中的冗余副本。 这允许任何故障节点的恢复只访问与故障节点关联的本地日志。 请注意,每个节点只需要维护 k-1 个本地日志(回想一下,k 是复制因子),因为一致性哈希将副本分布在哈希环中沿顺时针方向的节点序列中,并且每个节点只存储一个冗余副本 从最多 k-1 个节点。

拆分操作的工作原理如下。 它首先从全局日志的尾部检索可配置数量的段,统称为拆分。 然后它将冗余副本的拆分重新组织为多个子拆分,每个子拆分仅包含冗余副本,其对应的主副本位于同一节点中。 最后,它以仅追加的方式将每个子拆分写回一个单独的本地日志,并将写入并行发布到不同的本地日志。

在拆分过程中,DEPART 还会丢弃所选段中的任何无效 KV 对。 因此,它不会显式触发垃圾收集; 相反,它在拆分操作中实现垃圾收集以节省额外的 I/O。

对于每个冗余副本,每个节点都需要确定其对应的主副本所在的节点。 它可以在基于副本区分的节点内本地完成(第 4.2 节)。
拆分操作和GC同步进行减少IO

本地日志中基于范围的分组。 虽然将全局日志分成多个本地日志可以减轻恢复和垃圾收集的开销,但好处仍然有限,因为存储在每个节点中的哈希环的范围不一定是连续的(例如,在图 1 中,节点 N0 存储范围 [ 0,10] 和 [51,60])。 恢复任意范围的 KV 对只需要访问该范围的冗余副本,因此仍然会导致对本地日志的部分访问并发出随机 I/O。

我们通过使用基于范围的分组管理 KV 对来增强每个本地日志。图 7 显示了基于范围的分组的想法。每个本地日志又进一步划分为多个范围组,每个范围组对应于哈希环中的一个范围。请注意,每个本地日志中的不同范围组在键上没有重叠,因此可以独立管理它们。例如,对于图 7 中的节点 N2,本地日志 LOG0 存储冗余副本,其对应的主副本驻留在节点 N0 中。由于节点 N0 有两个范围,[0,10] 和 [51,60],LOG0 现在包含两个范围组,每个范围组分别保存 [0,10] 和 [51,60] 的冗余副本。基于范围的分组可以通过基于一致性哈希(第 2.1 节)将键(或其哈希)与哈希环中每个范围的边界进行比较来实现。它仍然确保对本地日志中每个范围组的写入以仅追加的批处理方式执行。本地日志中范围组的数量,以及 Cassandra 中的范围数量,可以通过参数 num tokens [2] 进行配置。基于范围的分组通过只访问相应范围组中的 KV 对而不访问整个本地日志中的所有 KV 对来提高恢复性能。

在基于范围的分组下,在拆分操作时将全局日志中的 KV 对写入本地日志时,DEPART 进一步对每个范围的所有 KV 对进行键排序,然后将 KV 对存储在本地日志中的范围组中; 我们将拆分操作中范围的排序 KV 对称为排序运行。 因此,每个范围组可以存储多个排序运行,而同一范围组中的不同排序运行可能在键上有重叠。 通过排序运行管理范围组使可调排序成为可能(第 4.4 节)。

从两层日志中读取。 为了从两层日志中读取一个 KV 对,DEPART 首先从最新的一个开始,逐个检查全局日志中的段。 注意每个segment的内部结构类似于LSM-tree中的SSTables,所以DEPART首先从segment中读取元数据,根据元数据中的偏移量读取对应的KV对。 如果在全局日志中没有找到 KV 对,则 DEPART 搜索相应的范围组,通过将键与范围组的边界键进行比较来定位。 由于每个范围组包含多个排序运行,并且排序运行中的 KV 对是完全排序的,因此 DEPART 从最新到最旧的排序运行进行搜索,并使用二进制搜索来查找排序运行中的键。

4.4 可调排序

回想一下,本地日志中的每个范围组可能包含多个排序运行,并且范围组内排序运行的 KV 对不是完全排序的。 如果一个范围组包含太多的排序运行,冗余副本的读取性能将会下降,特别是对于在读取操作中访问主副本和冗余副本的高读取一致性级别(第 2.3 节)。 因此,我们使用可调排序方案扩展了两层日志,其中用户可以配置单个参数来调整每个范围组的排序程度,以满足不同的一致性要求。

DEPART 使用用户可配置的阈值 S 调整多个排序运行的排序程度,S 是一个正整数,用于控制每个范围组中允许存在的排序运行的最大数量。图 8 显示了可调排序方案的概念。对于从拆分操作生成的每个新排序运行,DEPART 首先检查范围组中现有排序运行的数量是否达到阈值。如果不是,它将新的排序运行从拆分操作直接附加到范围组;否则,它将新排序的运行与现有的运行合并排序为单个排序运行。合并排序操作类似于 LSM-tree 中的压缩操作,包括三个步骤:(i)它从范围组中读取所有现有的排序运行; (ii) 将新排序运行中的所有 KV 对与现有排序运行中的所有 KV 对合并,如果在不同排序运行中存在多个具有相同键的 KV 对,则仅保留最新排序运行中的 KV 对并丢弃年长的; (iii) 它将所有合并的 KV 对写回范围组。我们考虑不同 S 值的两种特殊情况。

• 案例 1:S = 1。在这种情况下,每个范围组总是将传入的已排序运行与当前存储的已排序运行进行排序,因此所有 KV 对都已排序。 因此,每个本地日志都类似于单级 LSM 树。
• 案例2:S 接近无穷大。 在这种情况下,DEPART 总是将新的排序运行附加到一个范围组中,而不与任何现有的排序运行进行任何合并排序。

为了设置合适的 S 值,我们注意到 S 决定了读写性能之间的权衡。 小 S 通过在一个范围组中保留少量排序运行来提高读取性能。 它还通过在合并排序操作中丢弃无效的 KV 对来保持存储效率。 但是,它会产生较大的合并排序开销,从而降低写入性能。 因此,为了支持在读取主导工作负载下具有高读取一致性级别的系统设置,我们应该设置具有小 S 的高度排序以有利于读取; 否则,我们应该通过增加 S 的值来降低排序程度以使写入受益。 我们还通过实验评估了不同 S 值的影响,我们推荐一个默认设置 S=20,可以有效平衡不同一致性配置下的读写性能(参见 §5.2 中的 Exp#8)。

4.5 并行恢复

对于任何故障节点的快速恢复,DEPART 提出了一种并行恢复方案,该方案利用了主副本和冗余副本的存储管理解耦的好处。 我们首先回顾当前 Cassandra 实现中的恢复过程。 Cassandra 目前没有集中节点来监控数据丢失和协调数据恢复。 相反,它在每个节点中维护一个 Merkle 树 [4, 47] 以检测多个副本之间的数据不一致。 请注意,其他基于一致性哈希的分布式 KV 存储也使用 Merkle 树,例如 Dynamo [25] 和 Riak [54]。

Merkle 树是一种二叉哈希树,其中每个叶子节点存储一系列 KV 对的哈希值,而每个非叶子节点存储其子节点的哈希值。 如果丢失了 KV 对,则 KV 对的副本会变得不一致,因为存储副本的节点上的默克尔树检测到,因此 Cassandra 会触发恢复过程。 具体来说,恢复过程分为三个步骤:(i)为每个节点中的每个 KV 对范围构建 Merkle 树; (ii) 比较不同节点中相同 KV 对范围的 Merkle 树,以识别任何不一致的 KV 对范围(这意味着数据丢失); (iii) 通过从非故障节点检索 KV 对的范围并将 KV 对的范围发送到恢复的节点来重建任何不一致的范围。

DEPART 并行化读取和写入过程以恢复多个 KV 对范围以实现快速恢复。 它的并行恢复过程基于 Cassandra 中的恢复工作流程,如图 9 所示。假设我们在节点 N0 恢复故障节点的丢失数据。 首先,DEPART 中的每个节点从 LSM-tree 和两层日志中检索 KV 对,并构建自己的 Merkle 树(注意 N0 中的 Merkle 树最初是空的)(步骤 1)。 N0 比较 Merkle 树并识别缺失的 KV 对(步骤 2)。 为了恢复丢失的 KV 对,每个幸存节点(例如,图 9 中的节点 N1)使用两个线程向主副本和冗余副本发出并行读取,类似地,新节点(即 N0)从其他幸存节点检索 KV 对 并使用两个线程为主副本和冗余副本发出并行写入。 这种多线程是可行的,因为主副本和冗余副本存储在不同的索引结构中。

5 评价

DEPART 建立在 Cassandra v3.11.4 [2] 的代码库之上,通过在每个节点的存储模块中实现副本解耦。 我们的 DEPART 原型本身包含 6.9K LoC,而对 Cassandra 的修改包含 1.9K LoC。 请注意,Cassandra v3.11.4 包含大约 206.2K LoC。 为了展示 DEPART 中两层日志设计的好处,我们还实现了简单的将副本存储在多个 LSM 树中的朴素副本解耦方法,我们将其称为 mLSM(第 3.2 节)。

我们进行测试台实验来证明 DEPART 的效率。 我们将我们的 DEPART 原型与 Cassandra (v3.11.4) 进行比较,后者对所有副本执行统一索引,mLSM。 我们解决以下问题。

----DEPART在不同设置下的整体性能与Cassandra和mLSM相比如何,例如不同类型KV操作下的微基准性能,不同一致性配置和不同复制因子下的性能,以及YCSB核心工作负载下的性能[21 , 22]? (实验1-4)
—DEPART 和Cassandra 的性能故障是什么? (实验 5)

-----发生节点故障时,DEPART 的表现如何? (实验6-7)
-----DEPART 的性能如何随参数设置而变化,包括排序度 S、存储大小和存储节点的数量? (实验 8-10)

5.1 设置

试验台。 我们在多台机器的本地集群上进行所有实验,每台机器都有两个 12 核 Intel® Xeon® CPU E5-2650 v4 @ 2.20 GHz、32 GiB RAM 和 500 GiB Samsung 860 EVO SATA SSD . 所有机器都通过 10 Gb/s 以太网交换机互连。 每台机器运行 CentOS 7.6.1810,带有 64 位 Linux 内核 3.10.0 和 Ext4 文件系统。 我们使用一台机器通过线程池模拟多个客户端,其余机器作为存储节点。
工作负载。 我们使用通用云系统基准测试工具 YCSB [21, 22] 生成工作负载。 默认情况下,我们关注具有 24 字节密钥的 1 KiB KV 对,并基于默认 Zipfian 常数 0.99 的 Zipf 分布生成请求。 我们将 YCSB 部署在客户端机器上,并将客户端线程数设置为 50,而每个客户端线程从 YCSB 发出工作负载。

默认设置。 我们在集群中配置五个存储节点并进行三次复制以部署 Cassandra 和 DEPART。 在每次实验之前,集群都有空存储。 默认情况下,我们设置 (WCL=1, RCL=1)(即 Cassandra 中的默认设置),这对应于最终一致性。 我们还研究了不同一致性级别的影响(实验 1 和 2)。 Cassandra 和 DEPART 都使用默认的动态 snitching 模块 [5] 来选择最快的节点来提供读取服务,以便在不同副本之间实现负载平衡读取。 对于决定范围组数量的参数 num tokens [2],我们使用 Cassandra 中的默认值 256。

对于 DEPART,我们将 MemTable 大小设置为与 Cassandra 相同(默认为 160 MiB),并将全局日志中的段大小设置为与 MemTable 大小相同。 由于 DEPART 为两层日志保留了一个额外的 MemTable,为了公平比较,我们将 Cassandra 的行缓存大小增加了 160MiB。 对于两层日志,我们将每个拆分操作的数据大小设置为 20 个段(约 3 GiB),并将 S 设置为 20,以实现均衡的读写性能。 我们保持 Cassandra 中的其他参数设置不变。 我们绘制了五次运行的平均结果,误差线显示标准偏差。

5.2 结果

实验 1(KV 操作中的性能)。 我们首先比较 Cassandra、mLSM 和 DEPART 在不同 KV 操作中的性能,包括写入(即写入新的 KV 对)、读取(即读取现有的 KV 对)、扫描(即读取现有的连续 KV 对)和 更新(即更新现有的 KV 对)。 我们将客户端机器配置为首先随机写入 200M KV 对。 然后它按顺序发出以下请求:(i) 20M 读取,(ii) 2M 扫描(每次扫描包含一个 seek() 以定位第一个键,然后使用 100 个 next() 进行迭代),以及 (iii) 200M 更新。 我们还考虑了两种一致性级别设置:(i)(WCL=3,RCL=1)(即强一致性)和(ii)(WCL=1,RCL=1)(即最终一致性)。

图 10 显示了吞吐量和延迟结果。首先,DEPART 在所有情况下都提高了 Cassandra 的整体性能。对于(WCL=3,RCL=1),DEPART将写入、读取、扫描和更新的吞吐量分别提高到1.42×、2.29×、2.22×和1.45×;它将平均写入延迟、平均读取延迟、99% 写入延迟和 99% 读取延迟分别降低了 29%、58%、39% 和 41%。对于(WCL=1,RCL=1),DEPART 将写入、读取、扫描和更新的吞吐量分别提高到 1.43×、2.43×、2.68× 和 1.44×;它将平均写入延迟、平均读取延迟、99% 写入延迟和 99% 读取延迟分别降低了 30%、59%、41% 和 48%。扫描和更新的延迟结果相似,我们在此省略结果。 DEPART 性能改进的主要原因有两个。首先,对于reads,DEPART只在一个节点内的二层日志中搜索LSM-tree或特定范围组,从而大大减少了搜索空间。其次,对于写入,DEPART 减轻了 LSM 树中的压缩开销,现在它只保留主副本。两层日志还通过具有较大的排序度 S 值而具有有限的合并排序开销。

其次,mLSM 显着提高了读取性能,但仅在写入方面略有改善。 具体来说,与 Cassandra 相比,它将写入、读取、扫描和更新的吞吐量分别提高到 1.18-1.20 倍、2.11-2.20 倍、1.85-2.0 倍和 1.15-1.20 倍。 它还将平均写入延迟、平均读取延迟、99% 写入延迟和 99% 读取延迟分别降低了 15-17%、53-56%、16-18% 和 34-41%。 原因是 mLSM 仍然会触发频繁的压缩操作,这会竞争磁盘带宽并降低写入性能。 例如,Cassandra 和 mLSM 的总压缩大小分别为 3.46 TiB 和 2.72 TiB,DEPART 的总压缩和合并排序大小为 1.65 TiB(即与 Cassandra 相比,mLSM 仅将总压缩大小减少了 21 %,但 DEPART 将其降低了 52%)。

接下来我们比较 Cassandra、mLSM 和 DEPART 的存储和内存成本。更新阶段结束后,Cassandra 的 KV 存储大小为 613.5 GiB,mLSM 为 611.3 GiB,DEPART 为 654.8 GiB。与 Cassandra 相比,DEPART 产生了 6.7% 的额外存储开销,因为在我们的默认设置中,每个范围组最多允许 S = 20 次排序运行,并且在合并排序之前包含无效的 KV 对。为了测量内存开销,我们注意到 MemTable 大小随时间而变化(最高可达 160 MiB 限制),因为 KV 对不断插入 MemTable 并在 MemTable 已满时刷新到磁盘。因此,我们每五秒测量一次 MemTables 的总内存使用量,并获得平均结果。 mLSM 的总内存使用量为 335.7 MiB,是 Cassandra (90.4 MiB) 的 3.7 倍,因为每个 LSM-tree 都维护一个 MemTable。然而,DEPART 仅花费 183.9 MiB,是 Cassandra 的 2.0 倍,因为 DEPART 只为两层日志维护了一个额外的 MemTable。请注意,mLSM 的总内存使用量随着复制因子的增加而增加,而 DEPART 的总内存使用量不受影响。

最后,我们比较了 Cassandra、mLSM 和 DEPART 的读/写放大(即系统读/写量与用户读/写量之间的比率)。 图 11 显示了结果。 与 Cassandra 相比,mLSM 将读写放大分别降低了 40% 和 24%,而 DEPART 分别将读写放大降低了 53% 和 52%。 请注意,DEPART 的性能增益在两个一致性级别下是相似的,因此我们在以下实验中关注(WCL=1,RCL=1)(除了 Exp#2 和 8)。

实验 2(不同一致性配置下的性能)。 我们评估不同一致性配置下的性能。 特别是,为了强一致性,我们考虑了满足条件 WCL+RCL>3 的三重复制下的 WCL 和 RCL 的附加配置,包括 (WCL=2, RCL=2) 和 (WCL=1, RCL=3)。

图 12 显示了结果。 DEPART 在不同的一致性配置下不断提高 Cassandra 上的写入、读取、扫描和更新的吞吐量。 具体来说,对于 (WCL=2, RCL=2),DEPART 将写入、读取、扫描和更新的吞吐量分别提高到 1.43×、1.70×、1.38× 和 1.44×。 对于 (WCL=1, RCL=3),DE-PART 将写入、读取、扫描和更新的吞吐量分别提高到 1.44×、1.72×、1.62×、1.45×。 与 mLSM 相比,DE-PART 还不断提高写入和更新的吞吐量。 请注意,在不同的一致性配置下,DEPART 相对于 Cassandra 的写入性能增益几乎保持不变,因为 Cassandra 和 DEPART 的索引结构在三重复制下保持不变。

但是,DEPART 相对于 Cassandra 的读取性能提升变得更小,并且对于 RCL≥2,DEPART 的读取性能比 mLSM 差。在这种情况下,每个读请求至少需要成功访问两个副本,因此必须查找二层日志中的冗余副本。由于两层日志中的冗余副本没有完全排序,因此性能比读取 LSM-tree 中的主副本要慢。尽管如此,DEPART 仍然比 Cassandra 实现更快的读取,因为它搜索的数据比 Cassandra 少。此外,mLSM 保持冗余副本在每个级别中完全排序,因此它实现了比 DEPART 更高的读取性能。另一方面,对于 RCL=1,每次读取只需访问一个副本即可成功操作。大多数读取被路由到它们的主副本,其读取延迟小于由动态 snitching 模块(第 2.2 节)确定的冗余副本的读取延迟。因此,RCL=1 下的读取性能增益总体上高于 RCL≥2 下的读取性能增益。

实验 3(不同复制因子下的性能)。 我们通过将复制因子 k 从 3 更改为 5 来评估 DEPART 的性能。我们将客户端机器配置为首先随机写入 200M KV 对,然后发出 20M 读取。

图 13 显示了写入和读取的吞吐量结果与复制因子的关系。与 Cassandra 相比,DE-PART 将写入和读取的吞吐量分别提高到 1.43-1.59 倍和 2.43-3.61 倍。此外,DEPART 为更大的复制因子实现了更高的吞吐量增益。主要原因有两个。首先,对于读取,DEPART 要么从 LSM-tree 中读取主副本,要么从两层日志中读取冗余副本;对于后者,它只在两层日志中搜索全局日志和对应的范围组。因此,DEPART 的读取性能受副本数量的影响较小。但是,Cassandra 将所有副本存储在单个 LSM-tree 中,并且它的读取需要遍历整个 LSM-tree。随着复制因子的增加,它的读取性能显着下降。其次,对于写入,DE-PART 实现副本解耦并管理范围组中的冗余副本。当副本数量增加时,LSM-tree 中的压缩成本保持不变,两层日志中的合并排序成本仅略有增加。然而,Cassandra 将所有副本存储在单个 LSM 树中,并且随着副本数量的增加,压缩成本显着增加。结合这两个原因,DEPART 的性能增益随着复制因子的增加而变得更大。

与 DEPART 类似,mLSM 也不断提高在不同复制因子下 Cassandra 上的写入和读取吞吐量。 当复制因子增加到 k = 4 和 k = 5 时,其读取性能甚至优于 DEPART。 主要原因是mLSM只搜索在每一层完全排序的对应LSM-tree,而不考虑复制因子,但DEPART在默认设置下未完全排序的两层日志中保留了冗余副本。 请注意,我们可以调整两层日志的排序程度,以进一步提高读取性能。 此外,DEPART 的内存使用量仍然是 Cassandra 的 2 倍,但是当复制因子增加到 k = 5 时,mLSM 的内存使用量增加到 5 倍。

实验 4(YCSB 性能)。 我们使用六个 YCSB 核心工作负载 [21, 22] 比较 Cassandra、mLSM 和 DEPART,即 A(50% 读取,50% 写入),B(95% 读取,5% 写入),C(100 % 读取),D(95% 读取,5% 写入),E(95% 扫描,5% 写入)和 F(50% 读取,50% 读取-修改-写入)。 在运行六个 YCSB 核心工作负载中的每一个之前,客户端机器首先将 200M KV 对随机写入集群。 每个工作负载由 100M 操作组成,除了 Workload E,它包含 10M 操作,每次扫描涉及 100 个 next()。
图 14 显示了结果。 DEPART 在所有工作负载下都优于 Cassandra。 具体来说,它在以读取为主的工作负载 B-D 下将吞吐量提高到 1.4-2.1 倍,在以写入为主的工作负载 A 和 F 下提高了 1.6-2.2 倍,在以扫描为主的工作负载 E 下提高了 2.4 倍。 层日志设计,DEPART 减少了 LSM-tree 在写入期间的压缩开销,并减少了读取期间的搜索空间,因此它同时提高了读取和写入性能。 另一方面,由于副本解耦,mLSM 在所有工作负载下也优于 Cassandra。 然而,DEPART 进一步提高了 mLSM 的性能,因为后者会产生较大的压缩开销。

实验 5(读/写的时间分解)。 我们展示了 Cassandra 和 DEPART 中读取和写入进程的时间分解。 我们将客户端机器配置为首先加载 200M KV 对,然后发出 20M 读取。 读取过程包括读取 MemTable、缓存(包括行缓存和键缓存)、SSTable 的索引块(例如,Bloom 过滤器和偏移量),以及SSTable 中的数据块。 注意,这里将两层日志中的每个段都视为一个 SSTable。 写入过程包括写入 WAL、写入 MemTable、刷新 MemTable、压缩 LSM 树和两层日志的合并排序(仅在 DEPART 中)。
图 15 显示了读取和写入的时间分解。对于读取,大部分读取时间用于读取 Cassandra 和 DEPART 中 SSTables 的索引块,因为读取 KV 对需要检查每个索引块中的布隆过滤器 LSM-tree级别判断KV对是否存在,如果存在则根据索引块中的偏移量从SSTable中读取数据块。 总体而言,DEPART 将读取 SSTables 的索引块和数据块的时间成本分别降低了 56% 和 45%。 原因有两个。 首先,DEPART 只存储 LSM-tree 中的主副本,因此 LSM-tree 中的 SSTable 数量大大减少。 此外,DEPART 管理范围组中的冗余副本,因此用于定位 KV 对的读取次数也减少了。
对于写入,DEPART 将写入 WAL 和写入 MemTable 的时间成本分别降低了 28% 和 37%,因为 DEPART 并行写入主副本和冗余副本。 DEPART 还通过减小 LSM-tree 大小大大降低了压缩开销; 例如,它的压缩时间只有 Cassandra 的 30.7%。 此外,DEPART 中两层日志的合并排序时间仅为 Cassandra 中压缩时间的 21.4%。 因此,DEPART 中压缩和合并排序的总时间仅为 Cassandra 中压缩时间的 52.1%。

实验 6(恢复性能)。 我们评估恢复故障节点的恢复性能。 我们考虑不同的写入大小,通过将客户端机器配置为将 20 M、50 M 和 100M KV 对随机写入集群。 然后,我们使用“kill -s processID”命令终止 KV 存储进程并使用“rm -r data”命令删除其所有数据,从而使一个节点崩溃。 最后,我们在同一个节点上重新启动 KV 存储进程,并调用“nodetool repair -full keyspacename”命令进行恢复。
图 16(b) 还显示了修复 50M 和 100MKV 对的恢复时间分解结果。 我们考虑不同的步骤:构建 MT(即为所有节点构建 Merkle 树)、Compare MT(即比较所有 Merkle 树)、Receive&Write(即从其他节点接收修复的数据并将数据写入磁盘)和 Others(即 ,恢复中的其他操作)。 与 Cassandra 相比,DEPART 通过将读/写过程并行到主副本和冗余副本,将 Build MT 和 Receive&Write 的时间成本降低了近一半。

实验 7(节点崩溃时的性能)。 我们评估节点崩溃时和修复之前的读取、写入和更新性能。 客户端机器首先随机向集群写入 100M KV 对,然后我们手动使一个节点崩溃,如实验 6 所示。然后我们发出 20M 读取、100M 写入和 100M 更新。

图 17(a) 显示了节点故障下的吞吐量。 与 Cassandra 相比,DEPART 将读取、写入和更新的吞吐量分别提高到 1.69 倍、1.59 倍和 1.55 倍。 主要原因是 DEPART 搜索的数据总是比 Cassandra 少得多,即使它从两层日志中读取冗余副本。 此外,DEPART 始终可以提高正常模式和故障模式下的写入性能。

我们还评估了不同情况下的读取性能下降:(i)节点修复尚未触发,(ii)节点修复正在进行中,(iii)每个节点中冗余副本的两层日志支持更高的 通过将阈值 S(第 4.4 节)从默认值 20 降低到 5 来提高排序程度。图 17(b)显示了降低的读取吞吐量。 当节点修复未触发或正在进行时,DEPART 将降级读取的吞吐量分别提高到 1.69 倍和 1.75 倍,因为与 Cassandra 中的统一索引相比,DEPART 总是搜索更少的数据。 另外,当二层日志的有序度较高时(例如,阈值 S 较小),DEPART 的降级读取性能增益变大,因为读取二层日志中的 KV 对效率更高 具有高度的排序。

实验8(排序度S的影响)。 我们在 DEPART 中评估不同排序度 S 设置下的读写性能,以展示 DEPART 如何通过调整 S 的值来平衡冗余副本的读写性能增益。客户端机器首先随机写入 200M KV 对到集群,然后发出 20M 读取。 在这里,我们使用一致性配置(WCL=2,RCL=2),以便每次成功读取都必须访问冗余副本。

表1显示了结果。 对于 DEPART,如果 S = 1,则两层日志缩减为两级 LSM-tree,因此在 KV 对完全排序时达到最高的读取吞吐量,但由于频繁合并,写入吞吐量最低 - 用于在本地日志中的每个范围组中维护单个排序运行的排序。 随着我们增加 S(例如,S 是 10 或 20),两层日志的排序会放松,因此合并排序的开销会变小,因此写入吞吐量会增加。 由于我们将 S 设置为足够大的值,所以两层日志减少到仅附加日志,因此写入吞吐量最高,但读取吞吐量最低。 请注意,当 S 为 1、10 或 20 时,DEPART 在写入和读取方面仍然保持比 Cassandra 更高的吞吐量,即使在 DEPART 中的写入和读取之间存在性能权衡。

实验 9(不同 KV 存储大小的影响)。 我们现在评估不同 KV 存储大小的影响。 我们将客户端写入的数据大小从 200M 变为 400M KV 对(即,在三重复制下,主副本和冗余副本的总量从 600 GiB 增加到 1200 GiB)。 图 18 显示了不同 KV 存储大小下的写入和读取吞吐量。 与 Cassandra 相比,DEPART 的写入和读取吞吐量分别提高了 1.43-1.52 倍和 2.43-2.95 倍。 此外,DEPART 的性能增益随着 KV 存储大小的增加而增加,因为 DEPART 通过副本解耦减轻了写入和读取放大,但 Cassandra 通过统一索引加剧了写入和读取放大。

为了更好地展示两层日志设计在不同 KV 存储大小下的可扩展性,我们还评估了 LSM-tree 的压缩时间和 DEPART 中两层日志的合并排序时间,并将它们与 com - 卡桑德拉的交易时间。 当 KV 存储大小从 200M 增加到 400M KV 对时,Cassandra 中的压缩时间增加了 2.3 倍,而 DEPART 中的压缩和合并排序操作的总时间仅增加了 1.9 倍。 特别是 DEPART 中的合并排序时间与 Cassandra 中的压缩时间的比率从 21.4% 下降到 18.7%,DEPART 中的压缩时间与 Cassandra 中的压缩时间的比率从 30.7% 下降到 25.2 %。 因此,DEPART 可以随着 KV 存储的增长而很好地扩展。

实验 10(不同节点数量的影响)。 当集群包含更多节点时,我们评估 DEPART 的性能。 我们将节点的数量更改为 5、8 和 10。我们将客户端计算机配置为分别发出 200 M、320 M 和 400MKV 对的写入,以便每个节点包含相同数量的数据。 写入后,我们将客户端机器配置为分别发出 20 M、32 M 和 40M 读取。
图 19 显示了写入和读取操作的吞吐量结果。 与 Cassandra 相比,DEPART 将写入和读取的吞吐量分别提高到 1.35-1.43 倍和 2.26-2.43 倍。 无论集群大小如何,DEPART 通过副本解耦保持其优于 Cassandra 的性能提升。 因此,随着集群大小的增加,DEPART 实现了良好的可扩展性。

6 相关工作

本地 LSM-tree KV 存储。 许多研究优化了在单机上运行的本地 LSM-tree KV 存储的读写性能。 读性能可以通过布隆过滤器优化[23,38]、自适应缓存[65]和使用简洁尝试的扫描优化[68]来提高,而写性能可以通过压缩优化来提高[24,32,55 , 56]、碎片化 LSM-tree [51]、KV 分离 [12、36、43]、I/O 调度优化 [8]、内存结构优化 [9] 以及内存优化技术的混合 磁盘日志组件 [7]。 我们的工作侧重于分布式 KV 存储中的副本管理,并且与上述针对单个节点中本地 LSM-tree KV 存储的优化技术兼容。

分布式KV存储。 分布式 KV 存储可以分为内存中 KV 缓存 [42, 53] 和持久存储 [1-3, 41, 50]。内存中 KV 存储的优化工作包括无锁和缓存友好设计,以实现高并发和吞吐量 [13,30,40],用于内存效率的擦除编码设计 [66,67],自调整数据放置 [ 49]、减少尾部延迟的大小感知分片[26]、自适应负载平衡[16]、二级索引[34]、拉伸Reed-Solomon编码[35]以及热点优化[15]。对于分布式持久 KV 存储,先前的研究提出了批量加载的离线索引构建 [57]、自适应副本选择 [52]、多获取调度 [58]、尾延迟优化的自动调整 [39]、负载平衡 [ 11],通过成本效益分析和工作负载预测进行性能优化[45],以及数据放置和受控迁移的优化[63, 64]。持久化 KV 存储大多采用存储层的 LSM-tree 来存储所有的 KV 对。相比之下,DEPART 建议在分布式 LSM-tree KV 存储中进行副本解耦,以实现有效的副本管理。

副本管理。 先前的研究通过有效的副本放置改进了分布式 KV 存储的复制。 早期的研究包括链式复制[61、62]及其扩展[44]、低成本广域复制[17]和数据网格的动态分层复制[46]。 Copyset [19] 和分层复制 [18] 专注于保持高存储可靠性。 Replex [59] 支持对多个键的高效查询。 我们的工作侧重于每个存储节点内的副本管理,同时与上层副本放置策略兼容。

7 conclusion

我们提出了 DEPART,它基于一种新颖的副本管理方案,副本解耦,用于分布式 KV 存储。 DEPART 使用具有可调顺序的新颖的两层日志设计来有效地管理冗余副本以满足不同的读写性能要求。 我们的 DEPART 原型在不同类型的 KV 操作中明显优于 Cassandra,并在不同的一致性级别和参数设置下保持其性能增益。 致谢

代码如下(示例):

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

2.读入数据

代码如下(示例):

data = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())

该处使用的url网络请求的数据。


总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

猜你喜欢

转载自blog.csdn.net/weixin_41523437/article/details/124148506