2025-05-18
文件系统
0

目录

Motor:为解耦内存上的分布式事务启用多版本
摘要
1 引言
2 背景与动机
2.1 内存解耦
2.2 解耦内存上的事务
2.3 启用多版本
3 Motor 概述
4 Motor 内存存储
4.1 连续版本元组
4.2 分离值区域
4.3 协调器主动垃圾回收
核心机制对比
关键优化点
4.4 锚点辅助读取
锚点辅助读取方案(Anchor-Assisted Read Scheme)
核心规则与流程
写顺序保证(Write Order Guarantee)
实现机制
关键对比与优势
设计权衡
5 Motor 事务协议
5.1 处理阶段
阶段1:执行阶段(Execution)
提前中止(Early Abort)
关键机制解析
与传统方案的对比
设计trade-off
阶段2:验证阶段(Validation)
阶段3:提交阶段(Commit)
只读事务处理(Read-Only Transactions)
关键流程对比
设计优化点
一致性保证的本质
5.2 灵活支持隔离级别
可串行化(SR)的支持
快照隔离(SI)的支持
ACID特性保证
核心设计对比
关键机制解析
5.3 容错机制
6. 实现细节
易于使用的事务接口
执行框架
7. 性能评估
7.1 实验设置
7.2 CVT中的版本数量
7.3版本结构的性能
7.4端到端性能
7.5 内存开销
7.6 调整Motor的内存占用
7.7 不同隔离级别的性能
7.8 在内存池中使用持久内存
7.9 容错性
8. 相关工作
9. 结论
致谢

本文是《第 18 届 USENIX 操作系统设计与实现研讨会论文集》的一部分。

会议时间:2024 年 7 月 10 日至 12 日

会议地点:美国加利福尼亚州圣克拉拉

https://www.usenix.org/conference/osdi24/presentation/zhang-ming

Motor:为解耦内存上的分布式事务启用多版本

原文:https://dl.acm.org/doi/10.5555/3691938.3691981

明张、于华*、杨志军

武汉国家光电实验室,华中科技大学计算机学院

*通讯作者:于华(csyhua@hust.edu.cn

摘要

在现代数据中心,内存解耦将单体服务器拆分为网络连接的分布式计算和内存池,以提高资源利用率并提供高性能。计算池利用分布式事务访问内存池中的远程数据,以提供原子性和强一致性。现有的单版本设计由于系统并发性有限和日志开销大而受到限制。尽管传统单体服务器中的多版本设计有望提供高并发性和减少日志开销,但它无法在解耦内存中工作。为了弥合多版本设计与解耦内存之间的差距,我们提出了 Motor,它全面重新设计了版本结构和事务协议,以在解耦内存上启用快速分布式事务处理的多版本。为了在内存池中高效组织数据的不同版本,Motor 利用一种新的连续版本元组(CVT)结构以连续的方式存储版本,这使得计算池能够在单次网络往返中获取目标版本。在 CVT 的基础上,Motor 利用基于 RDMA 的完全单边多版本并发控制(MVCC)协议,支持具有灵活隔离级别的快速分布式事务。实验结果表明,与最先进的系统相比,Motor 将吞吐量提高了高达 98.1%,并将延迟降低了高达 55.8%。

1 引言

现代数据中心中的内存解耦受到了广泛关注 [2,3,35,46,53,62]。具体来说,内存解耦将计算和内存资源从传统的单体服务器中解耦,构建独立且可扩展的计算和内存池。这些池通过快速网络(例如 RDMA [75] 或 CXL [7])连接。计算池包含许多强大的计算单元来运行任务和少量基于 DRAM 的内存以维护元数据。此外,内存池由大量的内存模块组成,用于存储应用程序数据和少量的弱计算单元,仅用于内存分配和网络连接 [84,86]。借助高效的资源池化,内存解耦显著提高了资源利用率、弹性和故障隔离 [65,72]。为了在解耦内存上为应用程序提供原子性和强一致性保证,计算池利用分布式事务访问内存池中的远程数据。最近的设计,即 FORD [84],能够在解耦内存上运行分布式事务。为了简化内存池中的数据存储,FORD 维护每个数据的最新版本。然而,这种单版本设计限制了并发性,因为读取操作需要等待写入操作在事务提交期间变得可见。此外,为了保证原子性,FORD 写入许多撤销日志以备份旧数据,这消耗了网络带宽并降低了吞吐量。启用多版本有望有效解决上述限制。通过在内存池中存储每个数据的多个版本,读取请求能够获取现有版本的数据,而不是等待写入操作完成,从而提高了并发性。此外,有了多版本,旧版本的数据被保留以提供原子性,从而消除了写入撤销日志的需要。在传统的单体架构中已经提出了基于多版本的分布式事务处理系统 [57,64,76]

不幸的是,由于以下两个挑战,这些系统难以在新的解耦内存架构上工作,如下所述。

1)不兼容的事务协议。以前在单体架构上工作的系统假设每个服务器都有强大的 CPU 来执行事务协议中的计算任务,例如锁定 [64]、验证 [57] 和时间戳计算 [76]。一般来说,单个任务的计算开销并不大。然而,当请求数量增加时,这些任务变得频繁且计算量大。内存池中的 CPU 太弱,无法频繁轮询大量任务并执行它们 [45,46,66,69,75,84,86]。因此,传统的基于多版本的事务协议与解耦内存池不兼容。

2)低效的版本结构。为了存储数据的不同版本,现有方案利用基于指针的结构动态链接版本,本文称为链接链。一般来说,有两种类型的链接链。

(1)旧到新,链从最旧版本链接到最新版本 [10,25,38,76],如图 1a 所示。

(2)新到旧,链从最新版本链接到最旧版本 [9,32,57,64,81],如图 1b 所示。为了读取特定版本,CPU 执行链遍历,利用指针逐个获取版本,直到目标版本。事实上,链接链在单体服务器中工作得很好,因为每个服务器都包含足够的 CPU 来快速在其本地内存中执行链遍历。然而,链接链在解耦内存中变得低效,因为所有应用程序数据都存储在远程内存池中,而该内存池中没有强大的 CPU 来执行链遍历。因此,计算池必须通过消耗多个网络往返来逐个获取远程版本,直到目标版本,导致开销较大。图 1c 显示,当链遍历的步数从 1 增加到 20 时,在我们的测试平台上,RDMA 读取延迟显著增加了 24.8 倍(§7.1)。

image.png

此外,为了防止长链,需要进行垃圾回收(GC)以删除不再被任何事务使用的过时版本 [16]。然而,当使用链接链时,在解耦内存上执行 GC 是困难的,因为计算池需要频繁跟踪最旧事务并回收未使用的版本。这种跟踪消耗了许多往返用于同步,并浪费了计算能力。

为了解决上述挑战,我们提出了 Motor,它全面重新设计了版本结构和事务协议,以在解耦内存上启用分布式事务处理的多版本。与使用链接链不同,Motor 利用一种新的连续版本元组(CVT)结构来高效组织内存池中单个数据的多个版本。CVT 连续存储几个版本,以填充连续的地址空间。这样,计算池能够通过单次往返读取一个 CVT 来获取同一数据的所有版本,而不是逐个获取远程版本,从而减少网络开销以实现低延迟。当 CVT 被填满时,Motor 利用一种轻量级的协调器主动垃圾回收(GC)方案,以抢占式的方式回收旧版本,而无需跟踪事务状态。在 GC 存在的情况下,Motor 还使应用程序能够轻松识别 CVT 中数据值与其版本之间的一致性,以保证正确性。在 CVT 结构的基础上,Motor 设计了一种快速的基于多版本并发控制(MVCC)的事务协议。该协议充分利用单边 RDMA 来绕过内存池中的弱计算单元。我们的协议允许读取操作不被写入操作阻塞,并避免写入日志,从而提高并发性并节省网络带宽。此外,我们的协议支持各种隔离级别(例如,可串行化和快照隔离),以灵活满足不同 OLTP 应用程序的需求。

总之,本文的贡献如下:

  • 我们提出了 Motor,它在解耦内存上为分布式事务启用了多版本。
  • Motor 设计了一种新的连续版本元组(CVT)结构,以高效组织内存池中的多个数据版本。CVT 使计算池能够在一次往返中获取目标版本,并提供无需跟踪开销的轻量级垃圾回收(§4)。
  • Motor 利用快速 MVCC 事务协议,充分利用单边 RDMA 和 CVT 来满足无 CPU 的内存池,并支持各种隔离级别(§5)。
  • 我们实现了 Motor 并将其与两个最先进的系统 [64,84] 进行了比较。实验结果表明,Motor 显著提高了事务吞吐量,最高可达 98.1%,并将延迟降低了高达 55.8%。

2 背景与动机

2.1 内存解耦

传统数据中心由许多单体服务器组成,每个服务器都包含一组计算和内存单元。然而,这种单体架构存在资源利用率低和故障域粗糙的问题 [65,72]。具体来说,即使用户只需要更多的计算能力,我们也必须添加更多的整个服务器,其中内存模块被浪费。此外,如果 CPU 出现故障,整个服务器将无法使用,这扩大了故障域。

为了提高资源利用率和故障隔离,内存解耦 [20,35,46,50,51] 成为一种有前途的解决方案,它将计算和内存资源从单体服务器中解耦,构建独立的资源池。这些池通过快速网络连接,例如 RDMA [29] 或 CXL [7]。计算池包含许多强大的 CPU 来密集执行计算任务。计算池中有少量 DRAM 用于缓存一些元数据。此外,内存池由大量的内存模块组成,用于存储大量的应用程序数据。内存池不包含强大的计算能力 [46,65,69,72,75],但有一些低功耗计算单元仅用于内存分配和网络连接 [84,86]。通过高效的资源池化,数据中心能够以按需的方式为不同的应用程序提供适当数量的计算和内存单元,从而提高资源利用率并降低成本 [48]。此外,即使计算池中的 CPU 出现故障,由于分离的架构,解耦的内存模块在内存池中也不会受到影响,从而缩小了故障域。因此,内存解耦是现代数据中心和云提供商的有前途的解决方案。不失一般性,本文假设计算池利用单边 RDMA 原语(包括 READ、WRITE 和原子操作,如 CAS 和 FAA)访问内存池中的应用程序数据,以绕过远程 CPU,类似于现有研究 [53,66,75,84]

2.2 解耦内存上的事务

系统模型。 为了在解耦内存上为应用程序提供原子性和强一致性,计算池需要利用分布式事务访问内存池中的远程数据 [84]。具体来说,计算池中的 CPU 线程运行许多协调器,这些协调器执行事务协议以读取数据、处理冲突和提交更新。计算池不存储应用程序数据,但包含少量 DRAM 以缓冲一些元数据(例如,远程数据地址)。内存池存储所有应用程序数据,但不运行计算任务。每个数据被复制成多个副本以实现高可用性。实际上,fail-stop 故障 [36] 可能随时发生,导致内存池中的数据无法访问 [27]。为了容忍此类故障,我们采用 (f + 1) 方式主备复制 [42] 为内存池中的每个数据生成 1 个主副本和 f 个备份副本。每个副本可以被多个协调器访问。在事务处理过程中,计算池中的协调器通过网络以字节粒度读写远程副本,而内存池中的计算单元不参与。由于协调器和副本完全通过网络分离,我们系统模型中的所有事务都变成了分布式事务。

单版本的限制。 最近,FORD [84] 支持在解耦内存上运行分布式事务,并在内存池中存储每个数据的最新版本。这种单版本设计简化了内存存储,但带来了两个限制。

(1)低并发性。 在事务提交期间,正在更新的数据无法被读取。FORD 使这些数据在写入完成之前不可见,从而阻塞了读取操作;

(2)高日志开销。 FORD 将undo日志写入所有副本以保证原子性。这些undo日志消耗了网络带宽,协调器需要等待所有日志请求的 ACK 才能将更新提交到远程副本。

2.3 启用多版本

为了解决单版本的限制,我们采用多版本方法在内存池中存储每个数据的多个版本。通过这样做,写入操作不会阻塞读取操作,因为读取请求可以获取现有版本的数据,而不是等待更新操作完成,从而提高了并发性。此外,多版本设计不需要额外写入日志来备份副本中的数据,因为旧版本自然充当“undo日志”以保证原子性。通过这种方式,我们消除了日志开销以加快事务提交。

现有研究存在的挑战。 现有研究已经在事务处理中采用了多版本 [16,43,57,64,76]。然而,正如第 1 节所分析的,这些研究不适合新的解耦内存架构,原因有两个。

(1)它们的事务协议针对传统的单体服务器,要求每个服务器都有强大的 CPU 来执行大量的计算任务 [57,64,76]。然而,在解耦内存架构中,内存池中的计算单元太弱,无法频繁处理计算任务 [75,84,86]

(2)新到旧和旧到新的链接链版本结构导致大量的 RDMA 往返用于链遍历和垃圾回收的高开销。

为了解决上述挑战,我们提出了 Motor,以高效地在解耦内存上启用快速分布式事务处理的多版本。

3 Motor 概述

image.png

图 2 展示了 Motor 的系统概述,它包含两个协同工作的部分。首先,Motor 内存存储(§4)高效地在内存池中组织多个数据版本。其次,Motor 事务协议(§5)在计算池中处理基于多版本的分布式事务。

Motor 的工作流程:

  1. 客户端最初利用内存池中的 CPU 分配内存,将应用程序数据加载到关系数据库(DB)表中。这些表由我们的连续版本元组(CVT)结构组织,如第 4.1 节所述。CVT 可以通过索引(例如,哈希表 [86] 或 B+ 树 [75])快速访问。
  2. 我们在计算池和内存池之间建立 RDMA 连接。此外,内存池将一些元数据(例如,RDMA 内存区域的地址和索引的描述)发送到计算池。这些元数据帮助协调器在运行时定位远程数据。
  3. 客户端向计算池发出事务以执行。
  4. 计算池使用 CPU 线程同时运行许多协调器,这些协调器利用我们的事务协议处理事务。一般来说,协调器获取和锁定远程数据,然后执行事务逻辑。执行后,协调器验证数据版本是否未发生变化。最后,协调器将更新提交到远程内存池并解锁数据。我们的协议使协调器能够在事务处理过程中充分利用单边 RDMA 来绕过内存池中的弱 CPU。

4 Motor 内存存储

4.1 连续版本元组

关键思想。 Motor 提出了一种连续版本元组(CVT)结构,用于在内存池中维护数据的不同版本。与使用指针链接版本的链接链不同,CVT 连续存储版本以填充连续的地址空间。通过使用 CVT,协调器能够在单次 RDMA 读取中获取多个版本,而不是执行链遍历以逐个读取远程版本,直到目标版本。在获取 CVT 后,协调器本地搜索目标版本,这是快速的,因为不涉及任何网络 I/O。

结构。 图 3 展示了内存池中内存存储的结构,它由 CVT 组织而成。所有 CVT 构成一个 CVT 区域。每个 CVT 由一个头部(header)和若干版本单元(Vcell,version cell)组成。

image.png

在头部中,TableID表示该记录所属的数据库表。记录是数据库表中的一行用户数据,包含键(Key)和值(Value)。此外,Key是该记录的唯一标识符,Lock用于事务处理中的并发控制(§5.1)。AttrBarPtr指向值区域中的属性栏(attribute bar)。属性栏存储记录值的不同版本的已修改属性,具体如 §4.2 所述。VpkgPtr指向值区域中的值包(Vpkg,value package)。值包包含实际数据值,其由 VpkgSA 和 VpkgEA 封装,用于指示值是否完全写入,详见 §4.4。

在版本单元中,VcellSA 和 VcellEA 与 VpkgSA 和 VpkgEA 共同作用,用于检查版本与其值之间的一致性(§4.4)。Valid表示该版本的值是否可用,Version表示版本号。另外,Bitmap指示当前版本的已修改属性,StartOffset表示属性存储在属性栏中的偏移量(更多细节见 §4.2)。

术语含义解释
CVT版本控制事务单元(Concurrency Versioning Transaction Unit,假设)
Vcell版本单元(Version Cell),存储记录的特定版本信息
AttrBarPtr属性栏指针,指向存储属性修改记录的区域
Vpkg值包(Value Package),封装实际数据值及其写入状态的结构
VpkgSA/VpkgEA值包状态标识符(Start/End Anchor),标记值的写入完整性
VcellSA/VcellEA版本单元状态标识符,用于校验版本与值的一致性
Bitmap位图,用于标识当前版本中哪些属性被修改(通常以二进制位表示)

CVT 中的版本数量。 Motor 需要配置 CVT 中要持有的版本数量(VNum)。考虑到内存池中没有强大的 CPU 来在事务处理中动态调整 VNum,Motor 将 VNum 设置为固定值,即记录具有固定的最大版本数量。实际上,确定有效的 VNum 是具有挑战性的,因为读取延迟、内存占用和事务中止率之间存在权衡。具体来说,如果 VNum 太小,CVT 大小变小,这降低了 RDMA 传输负载,减少了读取延迟,并且也减少了内存池中的内存占用。然而,由于 CVT 中可用版本有限,可能会频繁触发垃圾回收(§4.3),并且在高竞争时可能会增加事务中止,从而阻碍吞吐量。相比之下,如果 VNum 太大,它有助于减少事务中止,但会在读取密集型工作负载中浪费内存,这些工作负载不需要太多版本的数据。此外,由于一次读取整个 CVT,较大的 CVT 增加了负载,延长了 RDMA 读取延迟。我们在第 7.2 节和第 7.6 节中探讨了这种权衡,并观察到合适的 VNum 大大取决于工作负载的特性(例如,访问竞争和事务中读取的记录数量)。一般来说,对于低竞争工作负载且事务运行时间短(例如 TATP [1]),将 VNum 设置为 2 是足够的。对于高竞争工作负载且事务运行时间长(例如 TPCC [13]),稍微大一点的 VNum(例如 4)可以有效地减少事务中止,而不会带来沉重的内存占用和高读取延迟。

索引支持。 Motor 提供统一接口,使协调器能够通过利用索引(如哈希表 [86] 和 B + 树 [75])快速访问远程 CVT。Motor 将 CVT 存储在索引内部。例如,使用 B + 树索引时,CVT 存储在叶子节点中,而内部指针节点缓存于计算池中,以减少远程树遍历。使用哈希索引时,CVT 通过对键(Key)进行哈希处理存储在哈希表中。因此,写入 CVT 的同时会修改索引。在不失通用性的前提下,本文与现有研究 [26,78,84] 一致,以哈希表为例阐述远程数据索引的细节。为解决哈希冲突,Motor 在哈希桶中预留多个槽位 [86],每个槽位存储一个 CVT。给定记录的键(如 K0),协调器对 K0 进行哈希处理以获取哈希桶 ID,并计算该桶的远程地址。随后,协调器读取该桶并在本地遍历槽位,搜索键等于 K0 的目标 CVT。

CVT 地址缓存。 在实践中,每次读取 CVT 时都获取整个哈希桶的成本较高。为解决这一问题,Motor 允许每个协调器利用计算池中的小型私有 CVT 地址缓存来存储 CVT 的远程地址。当下次读取相同的 CVT 时,协调器可直接使用缓存的地址快速读取 CVT,而无需访问哈希桶。然而,若获取的 CVT 的键与查询的键不匹配,则缓存的地址会失效。协调器通过重新读取哈希桶以确认目标 CVT 的存在,然后更新其地址缓存来解决此问题。存储数百万个地址(每个地址占 8 字节)时,地址缓存仅消耗数兆字节的 DRAM 空间,这对于计算池而言是可接受的 [65, 84]

4.2 分离值区域

一些先前的研究(例如 FORD [84] 和 Silo [71])将值与其版本一起存储,以便协调器可以在一次读取中获取值和版本。然而,这种设计在我们的上下文中变得低效,因为将值与其版本一起存储显著增加了 CVT 大小,导致高读取延迟和网络带宽浪费(所有值都传输,但只需要一个)。当值大小变大时,这种缺点变得更加严重。为了解决上述挑战,Motor 将 CVT 与内存池中的数据值分离。协调器首先读取 CVT 以确定目标版本,然后读取相应的值。这样,CVT 大小不受值大小影响,以实现稳定的低读取延迟,并且只传输一个数据值,以减少带宽浪费。

减少内存开销。 在值区域中,为每个版本存储一个全尺寸的数据值可以简化数据存储,但会浪费内存空间。为了减轻内存开销,我们有以下两个观察结果。

(1)关系 DB 表中的记录遵循相同的模式,该模式定义了值的属性数量和每个属性的大小。

(2)在更新记录时,事务只能修改一个或几个属性。例如,在 TPCC 中,DISTRICT 表中记录的值包含 9 个属性(总共 100B),但在 NEW_ORDER 事务中,只有一个属性被修改,即 D_NEXT_O_ID(4B)。

基于这些观察结果,Motor 存储可变大小的修改属性,而不是全尺寸的值,以维护任何记录的不同版本的值,从而减少内存开销。图 3 显示值区域包含一个全值区和一个增量区。全值区存储最新版本的全尺寸值,增量区存储被事务修改的旧属性(类似于“undo日志”)。因此,更新后的记录只有一个全值和不同版本的可变大小属性,这些属性实际上是被修改的。为了构建旧版本的值,我们只需将旧目标版本中的属性应用于最新全值。

属性栏。 在增量区中,Motor 利用一种新的结构,称为属性栏,以连续且紧凑的方式存储跨事务的记录的修改属性,如图 3 所示。Motor 在 CVT 中使用以下元数据来高效管理属性栏。

1)AttrBarPtr 在头部。当记录首次被更新时,协调器在增量区分配一个属性栏,并在 CVT 的头部保留属性栏的远程地址(即 AttrBarPtr)。

2)Bitmap 在 Vcell 中。协调器使用 Vcell 中的位图表示当前版本中修改的属性。例如,如果值有 8 个属性,而一个事务修改了第 1、2 和 4 个属性,那么协调器将位图 “00001011”(最右边的位表示第一个属性,即小端模式)写入 Vcell。位图的长度取决于属性的数量。

3)StartOffset 在 Vcell 中。此属性用于表示当前版本中修改的一组属性在属性栏中的偏移量。初始 StartOffset 为 0。

协调器通过使用最后写入的 Vcell 的 StartOffset 和 Bitmap 来计算新的 StartOffset。具体来说,根据最后写入的位图中 “1” 的位置,协调器累加最后写入的属性的总大小,并将该总大小与最后写入的 StartOffset 相加,以获得新的 StartOffset。

属性栏大小。 协调器需要分配适当大小的属性栏以存储修改的属性,以减轻内存浪费。通过采样事务执行,我们观察到对于 DB 表中的记录,每事务更新的属性总大小(称为 TotAttrSizes)不同,但以特定频率出现。例如,在 TPCC 的 CUSTOMER 表中,TotAttrSize 可以是 512B、12B 和 4B,分别以 10%、88% 和 2% 的频率出现在事务中。这是因为在 OLTP 场景中,事务逻辑指定了要更新的属性,不同的事务遵循事务混合中的标准执行比例 [1,4,13]。根据不同 TotAttrSizes 的频率,Motor 在属性栏中为 VNum 个版本的这些属性保留相应比例的空间(即,如果某些属性更频繁地被更新,Motor 为这些属性保留更多空间)。因此,Motor 大致估计属性栏大小(ABS)=∑i=1n(max(VNum×Frequencyi,1)×TotAttrSizei),其中 n 是 TotAttrSizes 的数量。例如,当 VNum = 4 时,CUSTOMER 表中记录的 ABS 为:1×512B + 3×12B + 1×4B = 552B,这足以存储不同版本的修改属性,而不会浪费内存。注意,即使某些版本中修改了值的所有属性(即,TotAttrSize = 全值大小),属性栏仍然可以存储所有这些属性,因为在这种情况下,计算的 ABS 保证大于全值大小。

减轻分配属性栏的竞争。 当协调器同时分配属性栏时,它们会争夺增量区域(delta area)中的空闲空间,从而导致高竞争。为避免这一问题,Motor 会根据属性栏大小(ABS,Attribute Bar Size)在增量区域中为每个协调器预先分配一个小 MB 级别的适当大小的增量空间。通过这种方式,协调器可在其专属的增量空间内分配属性栏,而无需与其他协调器竞争。更新操作完成后,属性栏指针(AttrBarPtr)对所有协调器全局可见,因此某个协调器能够向其他协调器创建的属性栏中追加属性。在极少数情况下,若增量空间耗尽,协调器会通知远程 CPU 分配更大的空间。

读写值仅需一次往返时延(RTT)。 尽管完整值和属性是分离存储的,Motor 在读写目标版本的值时仅需消耗一次网络往返时延(RTT)。具体机制如下:

  1. 读操作流程 协调器首先在 CVT 中选择目标版本(例如 V0),选择策略见 §5.1。若 V0 是最新版本,协调器通过 RDMA READ 在一次 RTT 内读取完整值。若 V0 不是最新版本,协调器通过以下步骤构造旧版本值:

    • 利用 CVT 头部的AttrBarPtr、版本号大于 V0 的 Vcell 中的StartOffsetBitmap,计算所需旧属性的远程地址;
    • 通过批量 RDMA READ 操作,在一次 RTT 内同时读取完整值和旧属性,并在本地组装出目标版本的值。
  2. 写操作流程 协调器通过批量 RDMA WRITE 操作,在一次 RTT 内同时完成以下任务:

    • 更新完整值;
    • 将旧属性追加到属性栏(Attribute Bar)中。

4.3 协调器主动垃圾回收

当更新数据时若没有空的 Vcell(版本单元),则需要垃圾回收(GC)机制来回收过时版本。传统 GC 方案会跟踪最老的活跃事务,并删除不再使用的版本 [16,64]。然而,由于内存池中的计算单元无法感知事务状态,难以在内存池中应用这种跟踪机制。另一方面,若由计算池执行跟踪,协调器则需确认所有进行中的事务里哪些版本未被使用,这会增加同步所需的网络往返次数并浪费计算资源。为避免跟踪带来的开销,Motor 提出了一种协调器主动式 GC 方案。其核心思想是:当没有空的 Vcell 时,允许协调器主动选择一个牺牲品版本(victim version),用新版本覆盖它以完成 GC。该方案因无需跟踪最老的活跃事务而轻量高效。

牺牲品版本的选择策略

  • 基线方案(图 4a) :跳过当前正在被读取的版本,从剩余版本中选择最老的版本。每个 CVT 保留一个读取队列(read queue),存储正在读取该 CVT 的事务时间戳。其他协调器检查此队列并跳过正在使用的版本。但对于读操作,协调器需通过 RDMA 的 FetchAndAdd 原子操作移动队列尾指针,再用 RDMA WRITE 插入时间戳,每次读操作增加的额外 RTT 显著提高了延迟。
  • Motor 优化方案(图 4b) :鉴于 RDMA 显著加速了事务处理 [26,78],最老版本被使用的概率极低,因此协调器可直接预占性地选择 CVT 中的最老版本作为牺牲品。该方案避免了基线方法的 RTT 开销,代价是某些长时间运行的事务若其之前读取的数据被快速回收,可能会被中止。但 §7.2 的实验结果表明,在 CVT 中保留适当数量的版本可有效减少此类中止。覆盖旧版本会使 CVT 中的版本不再按序排列,但不影响正确性,因为协调器会本地遍历 CVT 中的所有版本来定位目标版本。

属性栏的空间回收:若属性栏空间不足,协调器从属性栏起始位置回收旧属性,以写入新修改的属性。在此过程中,协调器会检查哪些 Vcell 对应被回收的属性,并将这些 Vcell 的 Valid 标志置为 0 以删除相应版本。由于 Motor 合理配置了属性栏大小以存储多个版本的属性,回收旧属性不会使大量 Vcell 失效。

核心机制对比

方案版本选择策略优点代价
传统 GC跟踪最老活跃事务精确回收高同步开销
基线方案跳过读队列中的版本,选最老版本避免误删活跃版本每次读操作增加 RTT
Motor 方案直接选最老版本无额外 RTT可能中止长事务

关键优化点

  1. 预占式回收:利用 RDMA 性能优势,假设最老版本不再使用,直接覆盖以避免同步开销。
  2. 属性栏空间管理:通过合理配置属性栏大小,平衡空间利用率与版本有效性。
  3. 异步标记删除:删除版本时仅置 Valid 标志,实际数据回收可异步进行,降低即时开销。

(注:RDMA FetchAndAdd 是一种原子操作,允许远程内存的原子递增,常用于实现分布式锁或队列操作。)

image.png

4.4 锚点辅助读取

为获取数据值,协调器需读取CVT以选择目标版本,然后读取完整值和必要属性。如图5a所示,协调器C1读取一个CVT并需要版本V1的值(ValueV1)。C1读取最新完整值(ValueV7)和旧属性以重建ValueV1。此时,另一个协调器C2正在执行GC回收版本V1并写入ValueV9。这会导致C1出现两种错误结果:

(1) 因C2的部分更新导致C1读取到损坏的完整值;

(2) C1读取到ValueV9但误认为是ValueV7,从而重建错误的ValueV1。

问题的根源在于版本与数据值分离存储,导致协调器无法“原子性”读取版本及其对应值。

image.png

锚点辅助读取方案(Anchor-Assisted Read Scheme)

为解决上述挑战,Motor提出锚点辅助读取方案,帮助协调器识别版本与值的一致性。如图5b所示,该方案在Vcell的起始和结束位置使用两个锚点,分别称为VcellSA(Vcell起始锚点)和VcellEA(Vcell结束锚点)。类似地,值包(Vpkg)中使用两个锚点(VpkgSA和VpkgEA)封装完整值。每个锚点占1字节,一对SA/EA及其包裹的内容通过C++结构体实现,允许协调器通过单次RDMA READ/WRITE操作访问。

核心规则与流程

  1. 写操作规则 协调器将四个锚点(VpkgSA、VpkgEA、VcellSA、VcellEA)的值原子性递增至相等。写入顺序为:

    plaintext
    Vpkg → 修改后的属性 → Vcell

    先写入值包,再写入属性,最后更新Vcell元数据。

  2. 读操作校验

    • 协调器读取CVT后获取Vpkg和必要属性。由于完整值区域存储最新值,其VpkgSA/VpkgEA也为最新值。

    • 协调器校验CVT中最新的VcellSA/VcellEA是否与VpkgSA/VpkgEA相等:

      • 相等:表明自上次写入后值和版本未被修改,可安全通过将旧属性合并到最新完整值中重建目标版本值。
      • 不等:检测到部分更新或冲突的GC操作,事务中止。

该方案通过四锚点机制实现版本与值的“类原子性”读取,相比Silo[71]需两次读取版本确认一致性的方案,仅需一次读取和锚点比对。

写顺序保证(Write Order Guarantee)

锚点方案的正确性依赖于数据按正确顺序写入内存池,需满足两点要求:

  • [R1] 写入顺序:Vpkg → 修改后的属性 → Vcell。
  • [R2] 单结构内顺序:起始锚点 → 内容 → 结束锚点。

实现机制

  1. RDMA可靠性保障

    • 单边RDMA的可靠连接模式确保消息不丢失、不重序[6]
    • 远程RDMA网卡(RNIC)保证RDMA WRITE请求按顺序发送至片上内存控制器(iMC)[61]
  2. 禁用DDIO规避乱序

    • 若启用数据直接I/O(DDIO),iMC会将数据写入L3缓存,缓存逐出可能导致R1/R2被违反(如属性先于Vpkg写入内存)。
    • Motor在内存池中禁用DDIO,使iMC通过内部先进先出队列将数据直接写入主存,确保写入顺序严格满足R1/R2。

关键对比与优势

方案一致性校验方式读写开销顺序依赖保障
传统分离存储无原子性保障高(可能多读)依赖缓存一致性
Silo[71]两次版本读取比对高(额外RTT)依赖锁或时间戳
Motor方案单RDMA读取+四锚点比对低(单次RTT)禁用DDIO+RDMA顺序

设计权衡

  • 空间代价:每个Vcell和Vpkg增加2字节锚点,总开销可忽略(如百万级版本仅增加约2MB)。
  • 性能收益:避免GC与读操作的竞态条件,消除传统方案中因部分更新导致的重试或数据错误。

(注:DDIO旨在提升传统服务器的缓存局部性,但在计算存储分离架构中,内存池的弱CPU不参与事务处理,故禁用DDIO对性能无负面影响。)

5 Motor 事务协议

我们介绍了 Motor 事务协议。我们的协议在广泛认可的事务处理框架中工作,该框架包括读取数据、处理冲突以及将数据写回。与现有研究 [27,39,64,77,78,84] 的主要区别在于,我们的协议充分利用了 CVT 结构和纯单边 RDMA,以支持解耦内存上的基于 MVCC 的分布式事务。

时间戳生成。 Motor 使用顺序数字作为事务时间戳(即,1、2、3...),这些数字也被用作数据版本。实际上,时间戳生成与我们的设计是正交的。现有研究提出了可扩展的时间戳生成方案 [24,38,64,76],这些方案可以应用于计算池中的时间戳服务,以为所有协调器分配严格且单调递增的时间戳。本文不关注优化时间戳生成,我们假设计算池中高效利用了一个可扩展的时间戳服务,以为所有协调器提供服务。

概述。 在内存池中,每个表被复制成 1 个主副本和 f 个备份副本,并且在事务处理过程中,弱 CPU 不参与。在计算池中,协调器利用我们的协议执行事务并利用单边 RDMA 访问远程数据。

5.1 处理阶段

图6展示了处理具有可串行化保证的读写事务(如T0)的流程。同一RTT内的所有请求并行发出。读写集合为{A, B},只读集合为{C}。在Motor中,写集合包含在读集合中,原因如下:

(1) 对于更新(Updates)和删除(Deletions)操作,协调器在写回数据前需读取远程CVT;

(2) 对于插入(Insertions)操作,协调器在插入数据前需读取远程桶以获取空CVT。

image.png

具体处理阶段如下:

阶段1:执行阶段(Execution)

协调器从时间戳服务获取开始时间戳(Tstart)。对于每个只读(RO)或读写(RW)数据,协调器查询本地CVT地址缓存:

  1. 缓存命中场景(如A和C)

    • 只读数据(如C) :协调器通过RDMA READ从主节点获取其CVT。
    • 读写数据(如A) :协调器使用门铃批处理的RDMA CAS+READ操作,分别对CVT加锁并读取。加锁请求可防止其他冲突事务同时修改同一CVT。若加锁失败,协调器直接中止事务(而非等待)以避免死锁。
  2. 缓存未命中场景(如B)

    • 协调器通过RDMA READ获取哈希桶,本地搜索匹配Key的CVT。获取CVT后,选择目标版本V0,即所有小于Tstart的版本中最大的版本。

提前中止(Early Abort)

若协调器在CVT中发现版本V1 > Tstart,表明另一事务T1在T0的Tstart之后提交。此时协调器可提前中止T0以保证可串行化,原因如下:

  • 即使T0使用Tstart选择V0执行,在后续验证阶段(Validation)中,T0的提交时间戳将大于T1,导致T0需使用T1的更新(V1),但T0已基于V0执行,因此必然冲突。提前中止可避免无效计算。
  • 注:快照隔离(Snapshot Isolation)无需提前中止,因T0只需读取Tstart时刻的快照(即使稍旧也可接受)[76]。

版本选择后,协调器通过批量RDMA READ读取Vpkg和所需旧属性,构建目标版本值(§4.2)。对于未加锁的读写数据(如B),协调器在读取Vpkg时额外批量执行RDMA CAS+READ操作,对CVT加锁并重新读取。获取所有数据后,协调器执行三项正确性检查:

  1. 加锁失败 → 中止T0;
  2. 重新读取的CVT中存在比V0更新的版本 → 中止T0(数据已被其他事务修改);
  3. 四锚点(VcellSA/VcellEA/VpkgSA/VpkgEA)不相等 → 中止T0(版本与值不一致)。 若通过所有检查,协调器安全使用Vpkg中的数据值执行事务逻辑。尽管Motor需两次RTT读取CVT和数据值,但由于无需传输无关数据值,网络负载显著降低。

关键机制解析

  1. 缓存与加锁的协同

    • 缓存命中时直接通过RDMA CAS+READ加锁,减少哈希桶遍历开销;
    • 缓存未命中时需先查桶再锁CVT,体现“先发现后锁定”的两阶段流程。
  2. 提前中止的语义

    • 可串行化要求事务按时间戳顺序提交,提前中止避免“后提交事务的更新被先提交事务忽略”的冲突;
    • 与快照隔离的差异反映隔离级别对一致性和性能的权衡。
  3. 批量操作的效率优化

    • 门铃批处理(Doorbell-Batched)减少RDMA请求的元数据开销;
    • 合并CAS(Compare-And-Swap)与READ操作,在单次RTT内完成加锁和数据读取。
  4. 锚点校验的作用

    • 作为最后一道防线,确保即使版本号正确,数据值与版本也未被中途修改(如GC操作导致的部分更新)。

与传统方案的对比

特性Motor方案传统分布式事务方案
加锁方式RDMA CAS+READ原子操作分布式锁(如Redis锁)
版本冲突检测时间戳+锚点双重校验时间戳或乐观锁
网络效率批量操作+按需读取(减少无效传输)可能多次往返传输全量数据
隔离级别支持可串行化+快照隔离灵活切换通常固定隔离级别

设计trade-off

  • 两次RTT的代价:换取更细粒度的版本控制和数据一致性;
  • 提前中止的取舍:以少量误判为代价,避免验证阶段的大量回滚开销。

(注:Doorbell批处理是RDMA中通过合并多个请求减少门铃中断次数的技术,CAS用于实现无锁原子操作。)

阶段2:验证阶段(Validation)

当读写数据的所有远程CVT成功加锁后,协调器从时间戳服务获取提交时间戳(Tcommit)。需注意:若读写事务不包含任何只读数据,可跳过以下操作以降低延迟(因所有读写数据已加锁)。但若事务包含只读数据,协调器需验证只读数据的版本在Tstart至Tcommit期间未变更,以确保可串行化。为此,协调器重新从远程主节点读取每个只读数据的CVT,并使用Tcommit选择版本V'(所有小于Tcommit的版本中最大的版本)。协调器检查以下两种情况是否发生:

  1. CVT被其他协调器锁定:可能存在更低Tcommit的事务正在提交新版本;
  2. V' ≠ V0:表明存在更低Tcommit的事务已提交新版本。 若任一情况发生,验证失败(因更高Tcommit的T0本应读取新版本,但执行阶段仍使用旧版本V0),需中止T0以确保可串行化。简而言之,仅当CVT未被锁定且V' = V0时,验证成功。

阶段3:提交阶段(Commit)

验证成功后,协调器在单次RTT内将更新提交至所有远程副本。协调器本地预处理待写数据,分为以下三种场景:

  1. 更新(Update)

    • 若记录为首次更新,协调器在预分配的增量空间内分配属性栏;
    • 在获取的CVT中查找空Vcell(Valid=0),将Valid置为1,填入版本号Tcommit,设置已修改属性的Bitmap,计算属性栏内的StartOffset,并将VcellSA和VcellEA设为相同新值;
    • 若无空Vcell或StartOffset超出属性栏长度,主动执行GC回收旧版本;
    • 收集待写入属性栏的修改属性,构建新Vpkg(填入新数据值,设置VpkgSA和VpkgEA等于VcellSA)。
  2. 插入(Insert)

    • 除与更新操作类似的Vpkg和Vcell预处理外,需创建新头部,填入TableID、Key(来自应用层)和VpkgPtr;
    • VpkgPtr在增量空间内分配,允许新插入数据与属性栏共享增量区域以提升空间效率。
  3. 删除(Delete)

    • 将V0的Valid置为0,防止后续更大时间戳的事务使用已删除版本;
    • 需将远程内存池中的完整值设置为旧版本值,通过将执行阶段获取的旧属性复制到完整值实现。

本地预处理完成后,协调器通过门铃批处理的RDMA WRITE操作,在单次RTT内将数据写入所有副本并解锁主节点。收到所有副本的ACK后,向应用层返回“提交成功”。

只读事务处理(Read-Only Transactions)

协调器获取读时间戳(Tstart),从主节点读取所需CVT,根据Tstart确定目标版本,然后从主节点获取Vpkg和必要旧属性以构建目标版本值。若四锚点相等,事务提交;否则中止。需注意:在单版本设计中,只读事务需验证[27,39,77,84],但在多版本设计中,由于获取了Tstart时刻的稳定版本快照,只读事务无需验证[57](详见§5.2)。

关键流程对比

阶段读写事务处理逻辑只读事务处理逻辑
执行加锁CVT、版本选择、锚点校验版本选择、锚点校验
验证校验只读数据版本一致性、CVT锁定状态无(多版本快照隔离)
提交多副本原子写入、属性栏/Vcell/Vpkg更新
核心目标保证可串行化、原子性更新快速读取一致性快照

设计优化点

  1. 验证阶段的选择性跳过

    • 不含只读数据时跳过验证,减少RTT开销,适用于纯写事务场景。
  2. 多操作批量提交

    • 通过门铃批处理合并WRITE请求,降低RDMA门铃中断频率,提升网络吞吐量。
  3. 空间共享策略

    • 插入操作的VpkgPtr与属性栏共享增量空间,减少碎片化,提升内存利用率。
  4. 删除操作的惰性处理

    • 仅标记版本为无效(Valid=0),而非立即物理删除,避免即时GC开销。

一致性保证的本质

  • 可串行化:通过时间戳排序+验证阶段的版本一致性检查,确保事务等价于某个串行调度。
  • 快照隔离:只读事务基于固定时间戳快照读取,无需验证,实现读写冲突的无锁化处理。

(注:门铃批处理(Doorbell-Batched)是RDMA中通过合并多个请求减少硬件中断的技术,适用于高并发场景下的性能优化。)

5.2 灵活支持隔离级别

通过本文协议,Motor支持两种广泛使用的隔离级别——可串行化(SR,Serializability)[11]和快照隔离(SI,Snapshot Isolation)[12],以灵活满足不同OLTP应用的需求。在可串行化级别下,并发事务看似按顺序逐个执行;而在快照隔离级别下,事务从某个时间点的快照读取数据,不反映其他进行中事务的修改。

可串行化(SR)的支持

  1. 读写事务的处理 若保证事务在Tstart时刻选择的目标版本与Tcommit时刻一致,则读写事务在Tcommit点具备可串行化性。这一特性使事务可视为按Tcommit顺序依次执行。Motor通过锁和验证机制确保该特性:

    • 锁机制(i) :事务在Tstart时刻获取所有CVT的锁后,读写数据的版本在Tcommit前不会被其他事务修改,因此Tstart和Tcommit时刻的版本一致。
    • 验证机制(ii) :在验证阶段,若事务检测到远程CVT被锁定或Tcommit时刻出现新版本,则验证失败并中止事务(因只读数据的旧版本已过期)。若验证成功,说明只读数据在Tstart和Tcommit时刻的版本一致。
  2. 只读事务的处理 只读事务因不修改数据而无提交时间戳。在多版本设计中,其读时间戳可视为“可移动”的,以便找到可串行化的执行顺序[57],即只读事务可插入到读写事务序列中,使所有事务看似顺序执行。 总结:通过锁处理写写冲突、验证处理读写冲突,确保所有事务调度的优先图[5]无环,从而保证可串行化[68]。

快照隔离(SI)的支持

为支持快照隔离,Motor对读写事务中的只读数据禁用版本验证,允许事务使用基于Tstart的旧快照。需注意,仍需通过锁机制解决写写冲突。快照隔离的一致性弱于可串行化,但性能更高(见§7.7),并已被MySQL[56]、PostgreSQL[60]、Oracle[59]、SQL Server[63]等主流系统采用。

ACID特性保证

Motor为事务提供ACID保证:

  1. 原子性(Atomicity) :通过维护数据的多个版本,旧版本作为“撤销日志”(undo logs)保障原子性。
  2. 一致性(Consistency) :内存池中的数据版本在事务开始前和提交后均处于一致状态。
  3. 隔离性(Isolation) :支持可串行化和快照隔离两种隔离级别。
  4. 持久性(Durability) :每个数据存储f+1个副本以应对数据丢失,并可在内存池中使用UPS供电的DRAM[27]或持久化内存[84],确保即使断电,已提交的更新也能持久存储。

核心设计对比

隔离级别读写冲突处理只读数据验证一致性强度典型应用场景
可串行化锁+验证(防止过时版本)必需金融交易、库存管理
快照隔离仅锁(处理写写冲突)禁用电商订单、社交平台

关键机制解析

  1. 可串行化的本质

    • 通过“锁+验证”双保险,确保事务的执行顺序与时间戳顺序一致,避免幻读和不可重复读。
  2. 快照隔离的性能优化

    • 移除只读数据的验证阶段,减少RTT和计算开销,适用于读多写少场景。
  3. 持久性的分层设计

    • 副本机制应对节点故障,持久化内存应对电源故障,分层实现数据可靠性。

(注:优先图(Precedence Graph)用于判断事务调度的可串行化性,若图中存在环则调度不可串行化;UPS-backed DRAM指配备不间断电源的动态随机存储器,可在断电时临时保存数据。)

5.3 容错机制

内存池中的副本故障。通过启用数据复制,Motor能够容忍内存池中的副本故障。副本故障可通过RDMA快速检测[27]。若任何副本在提交前发生故障,协调器将丢弃所有获取的数据,解锁远程锁,并中止事务。若主副本在提交过程中发生故障,Motor会将某个备份副本提升为新主副本,以保留已提交的更新(因备份副本与主副本具有相同的更新)。在更新同步到存活的副本之前,新主副本对协调器不可见。当新主副本可见且后续协调器可在新主副本上获取锁时,先前事务的更新已完成提交,从而保证可串行化。此外,若备份副本在提交过程中发生故障,协调器会选择另一个内存节点添加新的备份副本。添加备份需要数据迁移,此时Motor允许内存节点使用RDMA WRITE快速传输应用数据。涉及故障副本的后续事务将挂起,直至副本恢复。(f+1)路复制最多可容忍f个副本故障。

计算池中的协调器故障。与现有研究[27,78]一致,Motor支持使用租约(leases)[31]检测协调器故障。协调器会在本地内存中写入小型操作日志,记录执行过程中的操作(如即将加锁或提交的键)。这些操作日志存储在UPS供电的内存中,不会丢失[27]。若协调器发生故障,Motor会启用新的协调器,利用操作日志恢复进行中的提交并解锁键以完成恢复。例如,新协调器使用RDMA CAS操作解锁记录的键:若CAS成功,则释放之前的锁以避免饥饿;否则说明该键实际上未被锁定。

网络故障。网络故障会导致网络分区。在实践中,很难区分网络故障和服务器故障。与uKharon[34]类似,我们假设网络分区由数据中心管理员发现并解决。根据CAP定理[18,30],网络分区发生时,可用性和一致性无法完全同时保证。在OLTP应用场景中,提供一致性对满足ACID要求更为重要。因此,Motor通过仅允许主分区[17]处理请求来弱化可用性。

6. 实现细节

我们将介绍一些重要的实现细节,包括事务接口和执行框架。

易于使用的事务接口

Motor提供了以下接口,使应用程序能够在分离式内存上轻松运行基于MVCC的分布式事务:

  • TxnBegin():启动一个事务并记录其ID。
  • GetTS():从时间戳服务获取一个时间戳。
  • AddObject():将一个只读(或读写)对象添加到只读(或读写)集合中。
  • FetchAll():获取远程CVT和目标版本的数据值。同时锁定远程CVT。
  • Validate():验证只读数据的版本。
  • TxnCommit():通过将更新写回到远程副本并解锁主副本提交事务。

执行框架

在计算池中,Motor使用CPU核心生成大量线程来并行执行事务。然而,如果使用一个线程作为协调器,CPU核心在等待RDMA ACK时会处于空闲状态,这会降低吞吐量。为了充分利用CPU核心的计算能力,Motor在一个CPU线程中生成多个协程以流水线方式执行[39,77,84]。在一个线程中,一个协程轮询RDMA ACK,其他每个协程充当一个事务协调器。因此,Motor使大量协调器能够在计算池中并发执行事务。

7. 性能评估

7.1 实验设置

测试平台:我们配置了四台通过Mellanox SB7890 100Gbps InfiniBand(IB)交换机连接的服务器。每台服务器配备100Gbps Mellanox ConnectX-5 IB RNIC(远程直接内存访问网卡)。其中一台搭载Intel Xeon Gold 6330 CPU的服务器作为计算池运行协调器,另外三台服务器组成内存池,每台服务器配备192GB DRAM。

基准测试

  • 键值存储(KVS)微基准测试:KVS在一个数据库表中存储1000万对键值对,键为8字节,值为40字节[39,84]。在KVS中,每个事务对48字节的KV对执行读取或更新操作,访问模式遵循Zipfian分布[23]的偏斜访问。我们可配置KVS事务组合中的偏斜度和读写事务比例,以支持全面评估。

  • OLTP基准测试:使用三个广泛应用的OLTP基准测试——TATP[1]、SmallBank[4]和TPCC[13],评估端到端事务吞吐量和延迟。具体来说:

    • TATP:模拟电信应用,包含4个数据库表,80%的事务为只读。TATP包含200万用户,记录大小最大为48字节。
    • SmallBank:模拟银行应用,包含2个数据库表,85%的事务为读写。SmallBank有1000万账户,记录大小为16字节。
    • TPCC:模拟复杂订单系统,包含9个数据库表,92%的事务为读写。TPCC包含24个仓库,记录大小最大为672字节。 此外,所有基准测试中,每个数据库表均复制到三个内存节点,采用三副本策略(1个主副本和2个备份副本)。

对比系统:我们将Motor与两个最先进的系统进行对比:

  • FaRMv2 [64] :支持单体服务器上的事务多版本控制,使用“新-旧链”链接版本[64]。为使其兼容分离式内存(DM),我们使用单边RDMA实现其事务协议,本文后续称为FaRMv2-DM。
  • FORD [84] :支持分离式内存上的事务单版本控制,我们运行其开源代码。尽管FORD使用持久化内存,但其事务协议的单边RDMA设计也兼容DRAM。 注意,Motor面向分离式架构,因此不与运行在单体架构上的系统[39,57,76]进行对比。

性能指标

  • 事务吞吐量:通过统计每秒提交的事务数进行衡量。
  • 事务延迟:报告已提交事务的第50百分位和第99百分位延迟。

7.2 CVT中的版本数量

我们探究了CVT中的版本数量(VNum)对Motor性能的影响。对于每个基准测试,我们将VNum从2调整至15。KVS中读写事务的比例为80%。图8和图9表明,随着VNum增加,事务吞吐量通常先上升后下降。原因在于,当VNum较大时,只读事务的中止率降低,从而提升吞吐量。例如,在TPCC中,长运行只读事务STOCK_LEVEL的中止率从VNum=2时的32.1%降至VNum=4时的3.8%。然而,在达到事务吞吐量峰值后,增加VNum不再显著减少中止,但CVT大小持续增加,导致有效载荷尺寸增大,进而增加RDMA读取延迟(如图7所示)。读取开销的增加超过了减少中止带来的好处,从而导致性能下降。此外,较大的VNum还会消耗更多内存空间(如§7.6所述)。图8显示,在偏斜度为0.7时,KVS比偏斜度为0.99时更早达到峰值吞吐量,因为较大的偏斜度会导致更高的访问竞争,需要更多版本来减少中止。

image.png

我们观察到,在达到峰值吞吐量后继续增加VNum时,TPCC的吞吐量下降幅度(最高达49.6%)比其他工作负载更严重。这是因为TPCC中的单个事务可能访问数百条记录,远多于其他基准测试(例如,SmallBank或TATP中的单个事务仅访问1-3或1-4条记录)。因此,TPCC事务的总体读取开销(视为CVT大小×记录数)对VNum更为敏感,导致性能下降更明显。SmallBank是写密集型工作负载,但其事务较短,维护3个版本即可达到性能峰值。TATP只需为每条记录设置2个版本即可实现峰值吞吐量,因为TATP中80%的事务为只读且运行时间短、竞争低。随着VNum增加,较高的读取开销导致TATP的吞吐量持续下降。

总之,确定合适的VNum在很大程度上取决于工作负载的特征,包括访问竞争和事务中访问的记录数。当竞争较低时(如TATP),设置较小的VNum即可。如果竞争较高,则需要更多版本以支持更高的并发性,尤其是对于长运行事务。我们还需要考虑每个事务访问的记录数,以避免较大的CVT导致较高的总体读取开销。根据这些结果,我们在TPCC、TATP、SmallBank和KVS中分别设置了合适的VNum为4、2、3和4。

7.3版本结构的性能

我们在KVS基准测试上比较了我们的CVT与传统的链式版本结构,即旧到新(O2N)和新到旧(N2O)的性能。我们将访问偏斜度配置为0.7和0.99,并在KVS的事务混合中使读写事务的比例(RW-ratio)从20%变化到80%。根据图8中的结果,对于偏斜度为0.7的所有结构,我们将保留的最大版本数更改为3,对于偏斜度为0.99的结构,更改为4。

image.png

图10显示,与O2N和N2O相比,CVT分别将吞吐量提高了1.7 - 2.4倍和1.3 - 1.6倍。原因是,CVT使事务能够在单次往返中获取目标版本,而O2N和N2O需要多次往返进行链遍历。当增加RW-ratio时,三种结构的吞吐量都会下降,因为写冲突增加,并且读写事务需要更多的往返来提交。当偏斜度高(例如0.99)且RW-ratio低(例如20%)时,N2O和CVT之间的吞吐量差距变小,因为访问更加集中,许多只读事务可以从N2O的链头快速获取新值。然而,在高偏斜度下,O2N和CVT之间的性能差距变得更大,因为O2N中的新版本位于链尾,这增加了读取开销。此外,由于上述相同原因,在偏斜度为0.99时,CVT与O2N/N2O相比,平均分别将第50(和99)百分位延迟降低了59.8%/30.8%(和67.9%/47.7%)。我们还研究了,当进一步增加保留的最大版本数时,CVT比O2N和N2O能带来更多的性能优势。

image.png

7.4端到端性能

我们利用TATP、TPCC和SmallBank来评估Motor、FORD和FaRMv2-DM的端到端性能。所有系统都保证可串行化。为了公平比较,我们将FaRMv2-DM的版本链中的最大版本数配置为与我们的CVT相同。图11展示了事务吞吐量和延迟。为了绘制吞吐量 - 延迟曲线,我们通过运行10 - 40个线程和每个线程2 - 8个协程来增加请求负载,即10 - 280个并发协调器。每个线程按照每个基准的标准事务混合执行100万个事务[1, 4, 13]

image.png

与FORD相比,Motor在TATP上的事务吞吐量分别提高了14.4%,在TPCC上提高了98.1%,在SmallBank上提高了65.4%。FORD采用单版本设计,这限制了吞吐量,因为在读操作在提交期间会被写操作阻塞,并且撤销日志会消耗网络带宽。与FORD不同,Motor允许读取CVT中的现有版本,并且不需要通过维护旧版本的值来将撤销日志写入远程副本。因此,Motor比FORD提高了吞吐量。在TPCC和SmallBank中的改进更高,因为(1)它们是写密集型工作负载,在其中Motor避免了许多撤销日志,并且(2)Motor保留多个版本以减少只读事务的中止,特别是长运行的事务,例如TPCC中的STOCK_LEVEL。FORD在TATP中提供了最低的第50百分位延迟,因为两个事务,即GET_SUBSCRIBER_DATA和GET_ACCESS_DATA,占据了事务混合的70%,并且它们都只读取一个对象。在这种情况下,FORD只使用一个RTT来读取数据,而Motor需要两个RTT来分别读取CVT和数据值。然而,当事务变得复杂时,Motor在TATP上的第99百分位延迟接近FORD。此外,与FORD相比,Motor在TPCC/SmallBank上的第50百分位延迟降低了55.8%/26.2%。

与FaRMv2-DM相比,Motor在TATP/TPCC/SmallBank上分别将事务吞吐量提高了18.9%/44.3%/29.5%,并将第50(99)百分位延迟降低了8.6%(39.1%)/52.1%(35.6%)/43.6%(34.5%)。Motor实现这些改进有三个原因。(1)FaRMv2使用链表来存储不同版本,这增加了执行链遍历以获取目标版本的网络往返次数。与FaRMv2不同,Motor使用CVT在一次往返中一起获取版本。Motor在TPCC中比FaRMv2-DM表现出最高的改进,因为TPCC需要更多版本,并且事务读取许多记录,这加剧了FaRMv2-DM中的链遍历,导致高开销。(2)FaRMv2的设计消耗一个专用的RTT来锁定读写数据,但Motor能够将锁定和CVT/值读取请求批处理以节省RTT。(3)FaRMv2的设计使用两个RTT来提交备份和主副本,而Motor在一个RTT中一起更新所有副本。此外,FORD也可以通过减轻读取开销来实现比FaRMv2-DM更低的延迟,但FaRMv2-DM在多版本控制中允许更多的并发以提高吞吐量。

7.5 内存开销

我们使用两种不同规模的基准测试展示内存池中所有系统的内存开销。Scale-1(或Scale-2)配置为:TPCC包含24(或48)个仓库;TATP有200万(或400万)用户;SmallBank有1000万(或2000万)账户;KVS存储1000万(或2000万)对KV键值对(偏斜度0.99,读写事务比例80%)。Scale-1为§7.1中的默认配置。

image.png

如图12所示,FORD仅存储数据的单一版本,因此内存开销最低。由于支持多版本控制,Motor和FaRMv2-DM的内存占用高于FORD。然而,Motor通过以下三方面节省内存:

  1. 仅存储实际修改的属性:不同版本仅记录属性变更而非完整值;
  2. 合理预估属性栏大小:避免空间浪费;
  3. 动态配置版本数量:为不同负载设置合适的VNum,避免存储无效版本。 例如,TPCC中Motor支持4个数据版本,但内存占用仅为FORD的1.45倍(而非4倍)。其他基准测试也呈现类似趋势:
  • TATP:仅16%的事务为更新操作且修改属性较小,Motor内存开销仅比FORD高17.3%;
  • SmallBank和KVS:因写密集且需更多版本,内存开销分别比FORD高32.7%和37.7%。

FaRMv2-DM的内存开销比Motor高14.6%-22.8%,原因在于:

  1. 完整值存储:每个版本均存储完整数据值,而Motor仅存储属性差异;
  2. 链式指针开销:版本链需额外指针链接旧版本,而CVT结构连续存储所有版本,无需指针。

此外,图12b显示,当基准测试规模扩大时,Motor与FORD的空间消耗差距在各负载中基本保持稳定。这表明即使工作负载规模增大,Motor的内存优化策略依然有效。

总结:Motor通过适度增加内存空间换取相比单版本设计更优的性能,同时尽可能降低内存开销。

7.6 调整Motor的内存占用

我们基于基准测试Scale-1(§7.5)研究了调整Motor内存占用时其性能的变化情况。在内存池中,由于完整值始终存在以提供完整的用户数据,我们通过改变版本数量(VNum)和属性栏大小(ABS)来调整Motor的内存占用。

由于Motor已经显著降低了内存开销,进一步减少内存占用的空间有限。例如,在TATP中,Motor仅保留2个数据版本,这是多版本控制所需的最小版本数。因此,在TATP中,我们将VNum增加到8以增加内存占用。对于其他基准测试,由于它们合适的VNum大于2,我们将VNum从合适的值减小(和增大)到2(和8)以改变内存占用。当改变VNum(2 - 8)时,相应的ABS使用§4.2中的公式进行估计。此外,为了改变ABS,我们将VNum固定为每个基准测试中的合适VNum,并(1)将ABS增加到使用合适VNum估计的ABS的2 - 6倍,(2)将ABS减小到每个事务不同TotAttrSizes总和的1倍。图13 - 16显示了调整内存占用时Motor的事务吞吐量和延迟。我们还报告了FORD和FaRMv2 - DM的性能和内存占用以进行比较。

image.png

如图13所示,当从合适的值减小VNum时,Motor的内存占用最多可减少22.8%,并且在许多工作负载上接近FORD。通过减少内存占用以包含更少的版本,Motor仍然实现了比FORD和FaRMv2 - DM更高的吞吐量。原因是,与FORD相比,(1)Motor保留多个版本以避免阻塞读取并减少事务中止;(2)Motor不需要额外写入撤销日志,并且只读事务在多版本控制下不需要验证版本。此外,与FaRMv2 - DM相比,(1)我们的CVT结构避免了链遍历以减少延迟;(2)我们的MVCC协议通过高效的请求批处理节省了RTT(§7.4)。当稍微增加VNum(例如,在KVS中从4增加到6)时,由于仅在增量区域中存储必要的修改,Motor仍然比FaRMv2 - DM消耗更少的内存。因此,与FaRMv2 - DM相比,Motor可以使用更少的内存存储更多的版本。实际上,当VNum从2增加到8(增加4倍)时,Motor在TPCC / SmallBank / TATP / KVS上的内存占用仅增加了1.4倍 / 2.1倍 / 2倍 / 1.9倍。

image.png

图14显示,当固定VNum并从合适的ABS减小ABS时,吞吐量会下降,因为小尺寸的属性栏会导致在垃圾回收中有多个Vcell被无效化,从而增加中止率。然而,当从合适的ABS增加ABS时,吞吐量通常保持稳定,因为事务中止率几乎没有降低。这证明了我们对ABS估计的效率,即为属性栏保留准确且足够的大小而不浪费内存。

image.png

图15和16显示,当增加VNum以扩大内存占用时,Motor的延迟会增加,因为大尺寸的CVT会增加传输延迟。尽管如此,由于使用CVT在一次读取中获取所有版本,Motor仍然比FaRMv2 - DM具有更低的延迟。在TATP中,如§7.4中所分析的,FORD由于消耗更少的RTT来获取数据而实现了最低的延迟。但在其他基准测试中,在合适的VNum下,Motor比FORD具有更低的延迟,因为消除了读写事务写入日志和只读事务验证版本的开销。总之,这些结果证明了在调整Motor内存占用时,Motor相对于现有系统的优势。

image.png

7.7 不同隔离级别的性能

Motor支持两种隔离级别,即可串行化(SR)和快照隔离(SI)。图17表明,通过消除读写事务的验证阶段,在读取密集型(TATP)和写入密集型(TPCC)工作负载上,Motor - SI通常比Motor - SR实现更低的延迟和更高的吞吐量。与TATP相比,Motor - SI在TPCC中显示出更高的吞吐量提升,因为TPCC每个事务访问更多的只读数据并且具有更高的读写冲突,因此在放宽隔离要求时允许更多的吞吐量提升。

image.png

7.8 在内存池中使用持久内存

内存池中可以使用DRAM和持久内存(PM)[69, 86]。我们在每个内存节点中使用六个128GB的英特尔傲腾PM模块来评估Motor在TPCC上的性能。我们使用RDMA写后读操作将写入的数据从远程RNIC刷新到PM以实现远程数据持久性[84]。图18显示,由于有限的PM带宽[80, 84],在PM上的吞吐量仅下降了13.1%。结果表明,Motor在DRAM和PM上都能高效工作,从而为应用程序在不同类型的存储设备上运行提供了良好的可移植性。

image.png

7.9 容错性

我们利用TPCC展示了Motor在计算池中协调器故障和内存池中副本故障下的弹性。我们报告了随时间以1毫秒间隔的瞬时事务吞吐量(故障发生在时间0)。

图19a显示了恢复协调器的吞吐量时间线。我们运行84个协调器,其中60个同时发生故障。然后Motor生成60个新的协调器并建立网络连接,这大约需要170毫秒。之后,新的协调器接管剩余的任务。在Motor中,每个协调器在执行期间写入本地操作日志以记录操作。这些操作日志占用的空间非常小(每个事务最多556B),并且日志空间可以在事务之间重复使用。新的协调器使用故障协调器的操作日志来恢复进行中的提交并解锁CVT以避免饥饿。恢复后,Motor恢复到峰值吞吐量。

图19b显示了恢复副本的结果。考虑到CUSTOMER表经常被使用,我们分别允许CUSTOMER的主副本和一个备份副本发生故障,即无法访问。一小部分不访问故障副本的事务正常执行,因此吞吐量不会变为0。Motor通过将一个备份提升为新的主副本并添加一个备份来处理主副本故障。Motor通过添加一个备份来容忍备份副本故障。恢复主副本需要更多时间,因为Motor需要更改协调器对主副本的视图,并且在更新提交到存活的副本之前,新的主副本不可见。添加一个备份需要数据迁移,在此期间Motor允许一个内存节点使用RDMA WRITE将数据库表、CVT和属性栏传输到另一个内存节点。对涉及迁移的副本的写请求被阻塞以保证副本之间的数据一致性。由于CUSTOMER表很大,迁移消耗近200毫秒。我们还检查了如果一个小的DISTRICT表发生故障,迁移仅消耗1.1毫秒。进一步的迁移优化超出了我们的范围。实际上,考虑到现有系统[27, 64, 66]也提供毫秒级的恢复,我们的毫秒级恢复是可以接受的。

image.png

8. 相关工作

快速分布式事务:快速分布式事务处理是分布式系统中的关键支柱。许多系统使用RDMA来处理事务[22, 26, 27, 39, 41, 58, 64, 77, 78]。一些研究将分布式事务转换为本地事务以减少通信开销[19, 40, 52]。还提出了一些关于并发控制[55, 74, 79, 82]和数据复制[83]的协议来提高性能。上述系统在单体架构上运行,而我们的Motor面向分离式架构。

内存分离:内存分离提高了资源利用率。现有研究在许多领域探索内存分离,如硬件设计[35, 50, 51]、操作系统[65]、索引[53, 75, 86]、键值存储[45, 49, 66, 69]、网络[29, 67]、纠删码[47, 85]、交换[15, 20, 33, 62]和内存管理[14, 46, 48, 54, 70, 72, 73]。实际上,Motor专注于事务处理,与上述系统正交。尽管FORD [84]支持分离式内存上的事务,但它采用单版本控制,这限制了并发性并导致高日志开销。与FORD不同,Motor启用多版本控制来解决这些限制。

多版本控制方案:多版本控制方案已被用于支持分布式事务。它们专注于高性能的MVCC协议[28, 43, 57, 64]、时间戳生成[38, 76, 81]、垃圾回收[16, 44]和验证[21]。这些系统是为传统的单体服务器设计的,不适合分离式内存。与这些研究不同,我们的CVT结构和分布式事务协议有效地支持了分离式内存上的多版本控制。

9. 结论

本文提出了Motor,这是一种在分离式内存环境下用于多版本控制的高效分布式事务处理系统。Motor提出了一种新的连续版本元组结构,以有效地在内存池中组织数据的多个版本。在此基础上,Motor设计了一个完全面向单边RDMA的MVCC协议来加速事务。大量的实验结果表明,Motor在适度的内存开销下显著提高了事务吞吐量并降低了延迟。

致谢

本工作部分得到了国家自然科学基金(NSFC)的资助,项目编号为62125202和U22B2022。我们感谢匿名审稿人的建设性建议和反馈。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:司小远

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!