今天我们来看一篇由多伦多大学、微软亚洲研究院的研究人员发表的文章《DDS: DPU-optimized Disaggregated Storage》。
原文可从以下地址下载:https://arxiv.org/abs/2407.13618。
一句话总结:
本文介绍了DDS(一种新的基于DPU 优化的分离式存储架构)的详细设计以及原型。
DDS利用 DPU优化分离式存储服务器的延迟和 CPU 消耗,通过大量使用 DMA、零拷贝和用户空间 I/O 来最小化开销,引入卸载引擎直接在 DPU 上执行客户端请求以减少主机 CPU 使用。
实验和生产系统集成显示,DDS 实现了更高的分布式存储吞吐量,延迟降低一个数量级,每台存储服务器最多可节省数十个 CPU 核心,且只需对 DBMS 进行最小修改。
数据库领域的从事人员可以从该文中受益。
一、研究背景和挑战
1.现有分离式存储的痛点分析
云数据系统的一个典型特征是存储与计算的分离。
在这种架构中,应用工作负载在计算服务器上执行,数据则存储在专门的服务器中,这些服务器负责管理存储硬件并处理存储请求。
存储I/O请求以消息形式通过数据中心网络从计算服务器传输到存储服务器。计算与存储的分离带来了诸多优势,例如增强的弹性、持久性、资源利用率,进而降低成本。
存储分离被很多云原生数据库管理系统所采用,例如Aurora(Amazon产品)、SQL Hyperscale(微软Azure产品)、PolarDB(阿里云产品)和AlloyDB(谷歌GCP产品)。这些系统将数据库文件存储和复制在存储优化的服务器上,并在计算优化的服务器上执行SQL查询和事务。
在大多数这类系统中,仅主计算服务器可执行更新事务。为扩展计算能力,可添加辅助服务器来执行只读工作负载;为扩展存储能力,可将大型数据库分区至多个存储节点。计算与数据的分离使数据库管理系统能够承载任意规模的工作负载,并使用户只需为所需资源付费。
这些系统的主要成本之一是处理读写操作所需的数据通信。主计算服务器会将日志(而非脏页)发送到辅助计算服务器和存储服务器,这些服务器通过重放日志记录来更新本地页面。当计算服务器发生缓存未命中时,会从存储服务器读取整个页面。由于页面通常比日志记录大,且在大多数工作负载中读操作多于写操作,因此页面读取在数据通信中所占的比例远高于日志写入。
处理这些页面读取的CPU 成本很高,并且还在不断增加。这在很大程度上是由于存储带宽的稳步提升,目前读取带宽已达数 GB/s 。
由于数据库管理系统(DBMS) 每处理一页所需执行的 CPU 指令数量是固定的,因此存储带宽的增加会导致 CPU 消耗的增加。
例如,本文作者在Windows Server 2022 上的基准测试显示,2 GB/s 的磁盘 I/O 吞吐量(约 23 万个 8 KB 页面的 IOPS)需要消耗 5-6 个专用的最新 CPU 核心。在云端,每个核心在服务器的整个生命周期内的成本高达数千美元。
网络协议的CPU 开销也很高。在上述基准测试中,使用Windows 套接字通过 TCP/IP 以 2 GB/s 的速度传输页面时,发送方的 CPU 消耗达到了 14 个核心。
除了CPU 成本外,存储和网络栈还会增加请求延迟。从本地连接的SSD 访问数据库页面通常需要 100-200 微秒 ,但采用分离式存储时的读取延迟可能会糟糕 10 倍。为避免如此高的延迟,一些计算服务器将本地 SSD 用作扩展缓冲池,例如 Azure SQL Hyperscale 中的弹性缓冲池扩展和AlloyDB中的块缓存。此类缓存会增加服务成本和计算服务器故障的恢复时间。分布式键值(KV)服务也面临高 CPU 成本和延迟问题。
2.现有解决方案以及不足分析
用于存储的SPDK访问,和用于网络的DPDK和RDMA技术可以将 I/O 转移到用户空间或硬件中。这样可消除OS开销,并有可能降低分离式存储的 CPU 成本和延迟。
但这些解决方案有两点缺陷:
(1)为了实现高效率,它们需要专用CPU 核心来发出请求和轮询完成情况,因此仍然面临高 CPU 成本;
(2)它们无法降低数据系统内部的网络和存储例程的开销,这些开销比操作系统的开销更高,这点将在下文阐述。
为了评估kernel bypass技术的潜在优势,本文作者让计算服务器从管理128GB数据库的页面服务器随机读取8KB页面,对SQL Hyperscale进行测试。
下图展示了页面服务器的CPU消耗情况,同时将网络成本在DBMS内部网络模块和OS网络栈之间进行了划分。
可以看出,随着目标吞吐量的增加,消耗的CPU核心数量在显著上升,当读取速度达到156K页/秒时,需要17个核心。
虽然所有I/O组件在高读取吞吐量下都会产生显著成本,但DBMS的网络模块占比最大。所以,如RDMA和DPDK这样的kernel bypass技术仅能部分消除成本(OS部分)。
或者需要重新设计端到端通信栈,这会带来过高的工程成本,并给云DBMS的部署带来问题。
3.本文提出的解决方案DDS
本文提出了一种新的使用DPU的分离式存储设计,以降低数据系统的成本和延迟。
DPU是一种配备了计算和内存资源的网络接口卡(NIC),应用程序可以访问这些资源。
DPU位于系统总线PCIe上,这是访问SSD的最快路径,并且直接与网络接口连接。
因此,DPU可以在存储请求到达NIC时立即处理这些请求。
与基于FPGA或ASIC的智能网卡不同,DPU提供通用计算资源,因此能够支持灵活的存储卸载。
基于DPU的这些特点,文章提出了 DPU 优化的分离式存储(DDS)。
DDS 对云数据系统的分离式存储服务器进行了优化,使其能够以最小的主机资源消耗提供低延迟的远程存储访问服务。
下图展示了现有的分离式存储和DDS。前者通过主机上的多个层处理每个请求:操作系统通过 TCP/IP 和文件系统访问网络和存储硬件,以及服务请求的数据系统。相比之下,DDS 直接从 DPU 访问存储设备。
(1)构建DDS的一个挑战是选择如何在主机服务器与其DPU之间分配功能。
主机服务器拥有更多内存和更强大的处理器,这些资源是处理写入操作所必需的。DPU可直接访问网络和存储设备,并且能以比主机更低的延迟和成本执行读取操作。因此,DDS将读取请求卸载到DPU,并在主机上处理更新请求。
(a)为什么在主机上处理更新请求
在云原生DBMS中,写入操作是通过将日志发送到存储服务器和辅助计算服务器并在这些服务器上重放日志来实现的。为了对更新事务提供良好的响应时间并确保后续读取能获取到最新数据,这一过程必须主动执行。
这需要大型缓冲缓存,因为许多更新操作会应用于"温"数据页或"热"数据页。这一任务在主机上执行效果最佳。
而DPU的内存相对较小,因此在DPU上重放更新操作会比在主机上产生更多的SSD写入操作。
类似的情况也适用于键值存储。键值存储中的"读取-修改-写入"操作通常在温数据上执行,因此在能够维护大型缓冲缓存的主机上运行会更有优势。
(b)为什么将读取操作卸载到DPU
计算服务器通常拥有大型缓冲缓存,因此能够为温数据查询提供良好的性能。
所以如果计算服务器请求存储服务器读取某个页面,该页面很可能是冷数据,不值得在主机或DPU 上进行缓存。
此外,页面读取的逻辑相对简单。因此,DPU 完全有能力处理读取操作,并且由于避免了 DPU 与主机之间的通信开销,且针对 I/O 效率进行了优化,能够以更低的延迟和成本完成处理。
(2)主机和DPU之间分配存储功能必须解决的问题&DDS的解决方案
问题1:如何实现读取操作向DPU的卸载;
解决方案:DDS 提供了一种通用且易用的卸载抽象,使用户能够向 DPU 提供自定义代码,将感兴趣的数据包解析为用户请求并执行读取请求。
问题2:如何在主机上处理更新请求,同时最小化I/O开销并让DPU及时知晓文件映射的变化;
解决方案:DDS 通过一个跨越 DPU 和主机的文件系统,来统一主机与 DPU 的文件操作。
主机上存储应用使用的用户空间库作为前端,提供熟悉的文件API 以最小化应用修改;
运行在DPU 上的文件服务作为后端,用于卸载文件执行操作,从而节省主机 CPU 成本。
DPU 文件服务还确保 DPU 位于所有分离式存储的 I/O 路径上,因此具备足够的信息用于读取卸载。
问题3:如何处理待读取数据在主机上被修改并缓存的情况。
解决方案:DDS采用了“部分卸载”的方案。
访问主机缓存数据的读取请求更适合由主机处理。但在DPU 中拦截和处理部分客户端与主机的通信会破坏客户端与主机之间的端到端传输语义。
为此,DDS 引入了一种网络路径来应对这一挑战,并在必要时将读取请求转发至主机。
(3)DDS效果
作者使用了基准测试应用程序在两个生产数据系统(一个云DBMS和一个键值存储服务)中对 DDS 进行了评估。
在这些系统中,DDS 为每个存储服务器节省了数十个 CPU 核心,还将远程读取延迟最多改善了一个数量级。
并且,DDS 只需对存储应用进行最小程度的修改,这是其得以采用的关键特性。将 DDS 集成到这两个生产系统中仅需修改数百行代码。
本文贡献如下:
a.阐述了 DPU 在提升分离式存储性能和降低成本方面的潜力。
b.提出了一种新架构DDS,通过将存储解耦数据系统中的主机存储操作卸载到 DPU,以降低其成本和延迟
c.介绍了DDS 的详细设计,包括统一存储路径、透明网络路径和通用卸载引擎
d.实现了一个原型,评估了其效率,并将其与两个生产系统集成。
二、DPU简介
DPU 是基于片上系统(SoC)的可编程网络接口卡(NIC),旨在卸载主机功能。
与纯 ASIC 和纯 FPGA 的智能网卡(SmartNIC)相比,DPU 更易于编程、能耗更低,且能达到相同的效率水平。
许多芯片公司都提供 DPU 产品,例如英伟达 BlueField、英特尔 IPU 、AMD Pensando 和赛灵思 Zynq MPSoCs 、博通 Stingray、Kalray MPPA 以及Marvell的Octeon。
云服务提供商也在开发 DPU 芯片,有的通过自研(如亚马逊 AWS Nitro和阿里巴巴 CIPU),有的通过收购(如微软)或合作(如谷歌),其中许多已大规模部署。
1.DPU特性
下图对DPU 的特性进行了描述,其包含五个组件:
(1)高速网络接口。
作为网络设备,DPU 旨在数据中心中以高吞吐量转发数据包。DPU 的网卡可提供数百 Gbps 的带宽。
(2)高能效CPU 核心。
DPU 配备了支持通用编程的 CPU,例如控制功能和通用用户空间数据包处理程序。DPU 的核心通常采用高能效架构。例如,BlueField 中的 CPU 是低功耗的 Arm 核心。
(3)板载内存。
DPU 配备内存以容纳卸载程序的工作集。
与通常集成HBM的 GPU 和 FPGA 不同,DPU 大多使用成本较低的 DDR 内存。
由于接口和板载内存通常通过片上网络互连,访问DPU 内存的速度比访问主机内存更快。当前 DPU 的默认内存容量在 16GB 到 32GB 之间,并支持 DDR4/5 DIMM 扩展。
(4)内置硬件加速器。
DPU 通过板载硬件加速器,强化了计算密集型数据路径任务(如压缩/解压缩和正则表达式匹配)。在硬件加速器中执行相应工作负载的速度比在 CPU 上运行快数个数量级。
(5)支持PCIe 接口。
最后一个组件是PCIe 交换机,它使 DPU 能够访问主机资源(如通过 DMA 访问主机内存),还允许 DPU 通过 PCIe P2P通信直接访问其他 PCIe 设备(如 SSD)。
2.DPU 的机遇。
为降低成本,DPU 采用用户空间 I/O 和硬件卸载等技术对数据平面操作进行高度优化。结合通用计算和内存资源,可在 DPU 上构建超高效引擎来卸载任意 I/O 操作,从而节省主机 CPU 周期。
为降低延迟,当请求到达接口时,DPU 不会像如今的网卡那样将其转发至主机,而是直接处理请求并访问 SSD 上的数据。
下面这项基准测试可证明这一做法的优势:客户端向服务器发送TCP 消息,服务器将消息回传。测试服务器配备的是英伟达 BlueField-2 DPU。
下图记录了不同消息大小下,主机服务器响应消息与DPU 直接响应时的消息的往返延迟。可以观察到,DPU 通过避免将消息转发至主机,可将延迟减半。
3.DPU 的局限性
DPU 存在若干限制,使其无法在云数据系统设计中替代主机服务器。
(1)为实现能效优化,DPU 的核心性能弱于主机且数量更少。
例如,本文的测试平台中,英伟达BlueField-2(BF-2)DPU 采用 8 个 Arm Cortex-A72 核心,其性能远低于主机的 AMD EPYC 处理器。
由于云数据系统中的存储更新需进行相当复杂的处理,将此类工作负载卸载到DPU 只会降低性能。
为量化这一影响,本文使用YCSB( Yahoo! Cloud Serving Benchmark,是一个雅虎云基准测试工具,包括一套框架和通用的工作负载,以评估不同的 “key-value” 和 “云” 服务的性能)的读取-修改-写入(RMW)基准测试,在主机和 BF-2 DPU 上评估了 FASTER 键值存储。
如下图所示,FASTER 在 DPU 上的运行速度比在主机上慢 4.5 倍,且仅能扩展至 8 个线程。
这表明,由于CPU 性能较低,在 DPU 上执行更新工作负载会显著减慢数据系统的速度。
(2)当处理大规模工作负载时,DPU 的内存通常不足以让数据系统在存储服务器上缓存热数据。
在本文测试场景中,DPU 配备 16GB 板载 DDR4 内存,而像 Azure SQL Hyperscale 和 FASTER 这样的生产系统可能会分配一个数量级更大的内存,以高效执行更新工作负载(如日志重放和 RMW)。
三、DDS概述
1.设计目标
DDS 旨在通过部分卸载来弥合数据系统存储服务器的资源需求与 DPU 特性之间的差距。
具体如下:
(1)最小化存储成本:存储服务器需要消耗大量CPU 算力来支持资源解耦。DDS 旨在通过将大量计算任务卸载到 DPU,从而降低 CPU 利用率。
(2)最小化存储访问延迟:当前分离式存储读取延迟远高于原始SSD 设备的读取延迟。DDS 旨在缩小这一差距。
(3)易于采用:数据系统采用新技术时往往需要进行重大改动。DDS 旨在让数据系统只需进行最小程度的修改就能获得其带来的好处。...
(4)通用性:DDS 力求提供对各种云数据系统都有用的机制。
下图是支撑这些设计目标的架构。该架构由多个组件组成,这些组件共同对存储服务器进行优化。接下来还将概述实现该系统所面临的研究挑战。
2.架构设计
在DDS中,存储路径将主机和DPU上的应用程序文件操作进行了统一。
DDS将文件执行功能迁移至DPU文件服务,仅为主机应用程序保留轻量级文件库,用于发送请求和轮询响应。
如前(第一节)所述,现有主机存储栈效率低下且耗费CPU资源。在DPU上执行文件操作可节省这些主机CPU周期。
由于分离式存储请求最终会转换为文件操作以执行,因此统一的文件访问是DPU 卸载的前提条件。
这使得DDS 能够在 DPU 上维护文件元数据以及文件到物理磁盘块的映射(即文件映射)。当网络请求到达时,DPU 会构造一个文件操作并执行,而无需咨询主机。
随后,网络路径会在主机和DPU之间引导流量,以实现部分卸载。
在传统架构中,每个客户端都会与存储服务器建立端到端的传输连接,网络数据包由主机操作系统中的TCP/IP协议栈处理。
相比之下,DDS在流级别和数据包级别均对网络流量进行了分离:在一个流内,部分数据包属于可卸载至DPU的读取请求,而其他数据包则应转发至主机;在数据包级别,DDS需要将批处理请求划分为主机子集和DPU子集,并相应地引导这些请求。
为了检查流中的每个数据包和数据包中的每个请求,作者们开发了一个流量导向器,这是一种运行在DPU 核心上的在线设备(bump-in-the-wire),用于处理数据包。它应用了用户提供的两类信息:①应用签名 通过数据包头部对其进行过滤;②卸载谓词:用于检查数据包的有效载荷。
最后,最后,卸载引擎支持可自定义的存储卸载功能,以利用存储和网络路径启用的功能。
具体而言,DPU 上的卸载引擎将来自流量导向器的远程存储请求作为输入,输出文件读取操作,并直接将其提交给 DPU 文件服务。该过程由用户提供的卸载函数引导。当数据被读取时,它会通过流量导向器响应客户端。
3.挑战与核心思路。
DDS 作为一个独立系统,需与存储、网络和数据系统等多个层级交互。在与现有系统协同工作的同时实现所有目标,面临诸多挑战。
(1)挑战一:文件访问效率与易用性。
文件前端和后端的分离要求DDS在主机和DPU之间交换数据。
如何在实现这一点的同时将性能损耗降至最低?
如何利用DPU的低性能core执行文件操作?
DDS通过以下方式解决这些问题:使用由DPU发起的DMA驱动的非阻塞、无锁环形缓冲区,以及在访问SSD时集成用户空间I/O和零拷贝技术。
DDS在设计接口时沿用了现有文件API的风格,以尽量减少应用程序的修改量。
(2)挑战二:卸载效率与通用性。
设计卸载引擎的一大挑战在于定义通用的卸载API。现有 DPU 开发工具包(如 DOCA 和 IPDK)要求用户对底层数据包和流进行编程,而DDS的 API 直接暴露数据系统交互的对象。
实现通用性的障碍在于请求执行通常需要应用特定状态,为此DDS在 DPU 上引入内存哈希表,用户可自定义该表以缓存信息,从而将用户请求转换为文件操作。
与主机文件服务相比,在DPU 上实现卸载请求的高效执行所面临的一个挑战是网络和存储的异步接口。DDS通过精心分配缓冲区并协调请求执行,以避免任何数据拷贝。
(3)挑战三:传输兼容性。
首先通过硬件加速对在线设备(bump-in-the-wire)进行优化。
更关键的是,部分卸载会打破传输协议(如TCP)的端到端语义 —— 被卸载的数据包会被主机网络栈视为丢失,进而不必要地触发拥塞控制动作。
为解决这一问题,DDS将流量导向器设计为性能增强代理(PEP),在维持传输语义的同时支持数据包重定向。该 PEP 实现了传输透明性,避免了对传输协议及依赖该协议的主机应用进行重新设计。
后续章节将会对上述内容进行进一步阐述。
四、统一存储
1.Message移动
将文件执行迁移至DPU,需要把主机应用程序生成的文件请求(即读取和写入)传输到 DPU,并在执行后将响应传输回主机。
这种主机与DPU之间的数据传输处于存储访问的关键路径上,因此DDS对其进行了优化以实现整体高性能,否则则可能成为瓶颈。DDS设计的核心是支持主机与 DPU 快速通信的消息缓冲区。
主机上的存储应用程序通常是多线程的,每个线程均可访问文件。在DPU上,DDS 会分配一个文件服务线程来获取请求并返回响应。这种设置形成了 “多生产者 - 单消费者” 的请求缓冲区以及 “单生产者 - 多消费者” 的响应缓冲区。下文的讨论将主要围绕请求缓冲区展开。
除了性能之外,DDS 的第一个设计目标还对请求缓冲区提出了两项额外要求:①主机上的多个线程可能会同时发出文件请求,但这种争用不应消耗 CPU 周期;②通信可能会消耗大量 CPU 资源,但主机与 DPU 之间的通信不应消耗主机资源。为此,DDS引入了由 DMA 支持的无锁环形缓冲区。图7展示了该设计。下文将首先解释如何通过原子操作协调生产者和消费者,然后说明主机和 DPU 如何通过 DMA 有效地利用该环形缓冲区。
三个指针控制着环形缓冲区的使用:环形缓冲区常用的头部和尾部指针,以及一个支持并发插入的新进度指针。
生产者通过比较并交换(compare-and-swap)原子性地递增尾部和进度指针,消费者则通过原子加载(atomic load)读取这些指针。
头部指针由单个消费者更新,并由生产者读取。
引入环形缓冲区的进度指针是为了支持多个并发生产者并便于批量处理。
DDS的方案不同于现有的环形缓冲区设计(如 Linux io_uring),它能够通过 DPU 发起的 DMA 优化主机与 DPU 之间的通信,例如实现机会性批量处理和减少DMA 操作。
这样做的优势如下:
如果多个生产者同时插入请求,DDS的缓冲区设计会产生自然的批处理效果。
对插入请求,生产者首先检查批处理大小(图8a 中的第 1-3 行)。
如果大小达到阈值M,它会返回 RETRY,表明插入速度超过了消费速度。
否则,生产者递增尾部指针以在环上为请求预留空间,插入请求,然后递增进度指针以指示完成(图8a 中的第 4-6 行)。
对消费请求,消费者首先检查进度指针和尾部指针是否相等(图8b 中的第 1-4 行),以查看若要读取请求是否安全。
如果相等,消费者会读取批处理请求并递增头部指针(图8b 中的第 5-6 行)。
如果不等,说明某些生产者已预留空间但尚未完成插入,因此返回RETRY。
通过使用无锁原子指令,DDS避免了消耗 CPU 的自旋锁或牺牲性能的睡眠锁。
图7(右)展示了主机上环形缓冲区的内存组织方式,DDS的设计旨在最小化主机 CPU 消耗。
首先,主机上的所有操作均为纯本地内存访问,消除了与PCIe 通信相关的任何 CPU 开销(这一点至关重要)。
其次,内存已预先注册到DPU 驱动程序,因此DPU 可通过 DMA 直接访问环形缓冲区,而无需主机 CPU 参与。
在图8 中,DPU 对环形缓冲区的操作仅包括 DMA 读取和 DMA 写入。
环形缓冲区内存由两部分组成:存储环形缓冲区三个指针的指针区域,以及插入请求的数据缓冲区。
这些指针均按缓存行对齐,以确保访问不同指针的两个线程不会产生伪竞争(即虚假共享)。这些指针的物理顺序也经过优化,以反映操作顺序 —— 进度指针位于尾部指针之前。这使得单个 DMA 操作即可读取进度指针(P)和尾部指针(T),从而执行图 8b 中突出显示的条件检查,因此效率更高(若将尾部指针(T)置于进度指针(P)之前,则需要两次 DMA 读取:先读 P,再读 T)。
2.主机设计
(1)文件接口
DDS 的文件库提供了用户熟悉的 API,以支持对文件、目录和通知组的常见操作。
应用程序可调用CreateDirectory创建新目录,并通过CreateFile在目录中创建文件(该接口会返回文件句柄)。
此外,还可通过CreatePoll创建类似 epoll 的通知组,并使用PollAdd将文件添加到该组中。
若要对文件执行读写操作,可调用ReadFile和WriteFile,并通过PollWait函数轮询通知组中的完成事件。
除PollWait(后文将详细讨论)外,所有操作均为非阻塞式设计,以最大程度减少库内耗时及 CPU 周期消耗。DDS的优化与讨论将聚焦于文件读写操作,因其对存储引擎的效率至关重要。
(2)发出请求
该库通过通知组管理文件操作与完成事件。
创建通知组时,库会为其分配请求和响应环形缓冲区,并将缓冲区预注册到 DPU 驱动程序以支持 DMA 传输。
当应用程序执行文件操作时,库会查找文件对应的通知组,在组内记录该操作、分配请求 ID,并将请求插入请求环。
图 9(上)展示了请求环中文件写入操作的编码方式:除描述请求的头部外,写入数据会内联在请求中,从而可通过单次 DMA 读取将整个请求传输至 DPU。
(3)轮询响应
在通知组中轮询文件 I/O 完成事件需要从响应环读取数据。
DDS 支持两种模式处理PollWait调用时响应环中无响应的情况:非阻塞模式和睡眠模式(通过 DPU 驱动中断实现),以适配各种异步 I/O 实现。
两种模式均能实现 CPU 高效轮询 —— 睡眠模式不消耗 CPU 周期,非阻塞模式则将控制权交给应用程序。
图 9(下)展示了读取响应在响应环中的编码方式,包括头部和读取数据。与读取请求类似,写入响应仅包含头部。
文件库一旦轮询到响应,就会使用请求 ID 在其记录的操作列表中定位该操作,并执行完成后处理。
3.DPU设计
对于每个通知组,DPU 上的文件服务会维护两个缓冲区,用于与主机上的请求环和响应环(分别作为 DMA 读取/写入的目标/源)进行消息同步。
系统会分配一个线程专门执行DMA 操作,以通过图 8 中的算法获取请求并交付响应。
(1)低延迟文件访问。
DPU 上的用户空间存储 I/O 使用户程序能够绕过操作系统开销。
例如,NVIDIA BlueField 在其存储驱动程序中集成了 SPDK ,而英特尔 IPU 则提供了IPDK。
因此,DDS通过这些 DPU 提供的库来管理 PCIe 连接的 SSD,以实现文件功能并执行 I/O 操作。
简而言之,DDS使用固定长度的段(按磁盘块大小对齐)对 SSD 空间进行划分和分配,并通过平面目录对文件进行分组。
其中一个段被预留用于持久化存储目录和文件的元数据,以及文件映射(即分配给每个文件的段向量)。
尽管实现简单,我们得文件实现足以支持生产系统。我们将DDS 文件演进为功能完整的基于 DPU 的文件系统的工作留给未来。
为执行文件I/O,文件服务会使用文件映射将文件地址转换为磁盘块地址,然后将相应的 I/O 操作提交给用户空间存储驱动程序。
当I/O 操作完成后,文件服务会生成包含执行结果的响应,并将其插入响应缓冲区以进行交付。
(2)消除数据拷贝。
文件I/O 本质上是异步的。在执行请求和交付响应时可能会发生数据拷贝。
例如,若用于接收主机DMA 读取结果的请求缓冲区在相邻 DMA 读取操作中被共享,就必须在获取新请求前拷贝之前的请求数据,以避免数据覆盖。
响应缓冲区和DMA 写入操作也存在同样问题。
为规避潜在的效率损耗,DDS的文件服务首先将 DPU 上的请求缓冲区大小设置为与主机请求环大小相等或更大,确保缓冲区中不存在重叠的未完成请求。
这一条件使存储驱动程序能够直接将请求缓冲区中的数据作为提交I/O 操作的输入,从而消除请求拷贝。
此外,在提交I/O 操作前,DDS会在响应缓冲区中预留空间,并将该地址作为 I/O 操作输出的目标位置(如图 10 所示)。由于图 9 已明确定义响应头部结构,且对于读取请求,DDS可直接使用请求大小作为读取数据的长度,这种预分配机制有效消除了响应拷贝的需求。
为了在响应空间预分配中保留请求顺序,DDS在响应缓冲区中添加了两个尾部指针。
原始尾部指针(记为TailC (ompleted))指向已交付至主机响应环的响应末尾;
第二个尾部指针TailB (uffered) 指向已由文件服务完成但尚未交付至主机的响应末尾;
最后一个尾部指针TailA (llocated) 表示预分配响应空间的末尾。
对于每个新请求,文件服务会计算其预期响应大小并相应推进TailA,同时将预分配响应的状态(错误码字段)设置为 “待处理”。
文件服务会定期检查预分配响应的状态:当I/O 操作完成时,若成功则异步将状态改为 “成功”,若失败则设置为错误码。
随后,文件服务会推进TailB 直至遇到待处理的响应。
若TailB 与 TailC 的差值达到预配置的批处理大小,则发起 DMA 写入操作将响应交付至主机。DMA 写入完成后,TailC 将被推进。
五、流量导向
在DDS中,分离后的存储请求在DPU流量导向器处分流。下文将解释该组件的设计、部分卸载带来的关键挑战,以及如何解决性能问题。
1.在线拦截器
DPU 通常支持用户空间存储 I/O(如 DPDK)和 NIC 加速的匹配-动作规则。
流量导向器基于这些能力构建,作为一种在线拦截器(bump-in-the-wire)DPU 应用。
具体而言,对数据包的检查分为两个阶段:
首先,通过数据包头部的用户定义应用签名识别该数据包是否属于感兴趣的流。签名指定流的五元组(即客户端和服务器的IP 地址、端口及协议),此阶段仅涉及数据包的 L3 和 L4 头部。
若数据包匹配成功,流量导向器进入下一阶段;否则,将数据包导向主机。
第二阶段评估卸载谓词。流量导向器从数据包中提取有效载荷作为用户消息,并将其输入到卸载谓词中。对于每条用户消息,卸载谓词可能输出两条消息,一条发往主机,另一条发往DDS 的卸载引擎。若存在发往主机的消息,流量导向器会生成新数据包并将其导向主机。发往 DDS 卸载引擎的消息将与卸载引擎共享以进行处理。
2.传输透明性
在存储解耦的数据系统中,客户端通常会与服务器建立可靠的网络连接,以传输请求和响应。底层传输协议负责处理数据包丢失和拥塞问题,并确保正确数据的有序交付。
特别地,数据系统中最流行的协议TCP,会使用序列号来实现可靠性并执行拥塞控制。
当服务器接收数据包时,TCP 会检查该数据包的序列号。
如果由于数据包丢失、乱序交付(在我们的场景中还包括DPU 卸载)等原因导致序列号与预期不符,TCP 就会触发快速恢复机制,并向客户端发送重复确认(ACK)。
结果是,客户端会重传从预期序列号到服务器接收到的序列号之间的所有数据包。
图11 展示了一个示例:存储服务器主机首先处理序列号为 100 的数据包,并向客户端发送确认。
卸载谓词判定后续数据包将被卸载至DPU,直到序列号为 1064 的数据包。
当主机TCP 接收该数据包时,由于序列号未按预期出现,会重复发送序列号 132 的确认。
此时客户端将重传所有已卸载到DPU 的请求。
修改传输协议以支持部分卸载不仅会使设计复杂化,还需要修改依赖该协议的数据系统,并将其锁定在特定实现中。
为攻克这一挑战,我们将流量导向器转变为性能增强代理(PEP)。
PEP 是一种网络内代理,仅通过本地链路修改即可提升传输协议的端到端性能,且无需对传输层进行任何改动。
我们将DDS 的流量导向器设计为支持 TCP 拆分的 PEP,它能自动将单一的客户端-服务器 TCP 连接拆分为两条 TCP 连接:一条建立在客户端与 DPU 之间,另一条建立在 DPU 与存储服务器主机之间。
TCP 拆分通过以下方式支持部分卸载:DPU 上的流量导向器通过第一条连接接收来自客户端的所有请求,若某个请求无法被卸载,则流量导向器会通过第二条连接将其发送至主机。
3.性能优化
在 DPU 上进行流量导向可能会产生性能开销。
首先,在线拦截(bump-in-the-wire)设计需要处理所有传入数据包。对于流量始终流经 CPU 核心的路径内 DPU,这种开销较低。但对于像 BlueField-2(BF-2)这样的路径外 DPU,其 CPU 并未硬连接在数据路径上,非感兴趣流的数据包必须转发至主机。
我们测量发现,在 BF-2 上通过 Arm 核心将数据包转发至主机的延迟约为 6 微秒。
其次,TCP 拆分要求 DPU 上运行 TCP/IP 栈。SoC 操作系统(如 Linux)中内置的网络栈会因内核开销引入显著延迟,而 DPU 核心性能较弱会进一步加剧这一问题。
为解决上述开销问题,DDS先将应用签名的评估下沉至网络接口层——该层硬件支持基于数据包头部的线速路由功能。
只有匹配签名的数据包会被转发至流量导向器进行后续卸载谓词评估,其余数据包则直接转发至主机。
这一设计避免了对非感兴趣流数据包添加任何延迟。
对于卸载谓词评估,DDS在流量导向器中适配并优化了用户空间 TCP 栈。
值得注意的是,尽管进行了这些优化,若某个请求匹配应用签名但未通过卸载谓词(如写请求),其往返流量导向器的过程仍会引入延迟(在 BF-2 上约为 10 微秒)。
应用程序可通过以下方式规避该开销:将请求隔离到不同 TCP 连接中,并仅允许符合应用签名且可被卸载的请求通过。
六、 卸载至DPU
1.卸载API
DDS 的卸载 API 允许数据系统以高层次方式表达存储操作。
该 API 基于 DDS 中 DPU 卸载的两步流程,针对简洁性和通用性进行了优化:
对于每个请求,用户(1)判断其是否可卸载,(2)若可卸载,则指定卸载方式(即生成文件操作)。
表 1 中的 OffPred 执行步骤(1),它以网络消息和缓存表为输入,输出两个请求列表:一个用于卸载至 DPU(DPUReqs),另一个发往主机(HostReqs)。
其中仅一个列表可为空,表示对应目标无需处理请求。
这一设计支持批处理场景(单个网络消息包含多个 I/O 请求),这是提升分离式存储吞吐量的常用优化手段。
以无关的写入和读取操作为例,一个简单的 OffPred 逻辑是将写入放入 HostReqs,读取放入 DPUReqs。
步骤(2)需要用户进行更多的规范定义。
例如,在键值(KV)服务中,一个 GET 请求仅包含对象的键。
将键映射到文件中记录位置的索引由主机维护。
在DBMS中,GET 请求包含页面 ID,可直接从中推导出页面的文件位置。
因此,DBMS引入卸载函数 OffFunc,用于将读取请求转换为文件读取操作(见表 1)。在上述 KV 示例中,OffFunc 的实现可以通过给定的键查询缓存表,以生成文件读取的参数:文件 ID、偏移量和读取字节数。
OffFunc 是由数据系统操作员或服务提供商定义的命令式函数,用于将用户请求转换为文件操作;它并非用于通用编程(例如,为实现快速执行,不允许进行内存分配等系统调用)。
在 DDS 中,缓存表是一个内存哈希表,允许用户缓存任意对象键和数据以支持其卸载计划。该表旨在汇总存储在磁盘文件中的数据。
写入操作会修改文件中的数据,因此是触发缓存的合适时机。因此,我们提出了一种“写时缓存” 机制,当从主机接收到文件写入请求时,利用该机制填充缓存表。
当主机从文件中读取对象时,用户可能希望从表中删除这些对象,因为它们可能已在主机上被修改。
因此,访问这些对象的远程读取请求应由主机处理。用户定义的“读时失效” 函数即用于实现这一目的。
该缓存表与文件服务中的文件映射共同构成了灵活的两级映射机制,可将各种数据系统中的用户请求先转换为文件地址,再转换为物理磁盘块地址,从而为通用卸载提供支持。
如表 1 所示,对于每次文件写入操作,缓存(Cache)会返回一个对象键及其缓存项的列表,用于插入到缓存表中;对于每次读取操作,失效(Invalidate)会返回需要移除缓存条目的键。
写时缓存和读时失效的一个示例是:在文件写入时缓存每个对象的文件 ID、偏移量和大小,而在文件读取时使每个对象的缓存失效。
2.执行引擎
应用签名和卸载谓词的执行已在第五节讨论过。下文将描述卸载引擎如何执行已卸载的读取请求并管理缓存表。
(1)执行已卸载的读取操作
当接收到远程读取请求时,卸载引擎会通过 OffFunc 生成文件读取操作(ReadOp {文件 ID、偏移量、大小})。
为执行该操作,必须分配内存缓冲区作为读取数据的目标位置。
一种简单的解决方案是动态分配 ReadOp.Size 字节的内存并传递给文件服务,读取完成后再将缓冲区传递给流量导向器以生成最终的客户端响应。
但这种方法存在两次内存拷贝开销:数据首先从文件服务拷贝到读取缓冲区,然后在流量导向器中再次拷贝以生成网络数据包。
为避免该开销,DDS的卸载引擎预分配了同时容纳读取缓冲区和数据包缓冲区的内存,实现零拷贝。具体如图 12所示:
卸载引擎会预留一个支持 DMA 访问的大页池。执行文件读取时,引擎根据读取大小(①)准备读取缓冲区,然后将缓冲区指针与其他读取参数一同传递给 DPU 文件服务以执行操作(②)。
由于这些内存地址支持 DMA 访问,预分配的读取缓冲区与存储设备之间的额外内存拷贝被完全消除。
读取完成后,引擎会创建间接数据包缓冲区用于最终的网络通信,该缓冲区仅包含 L4-L2 协议头的占位符,而读取缓冲区则直接作为数据包的有效载荷被引用。
若读取数据大小超过网络接口的 MTU 限制,则会将数据分段拆分为多个数据包传输(③)。流量导向器最终用适当的值填充数据包头部,并将其发送至远程客户端(④)。
这样做消除了卸载引擎与流量导向器之间的数据拷贝。零拷贝的优势将在第八节中评估。
由于 DPU 文件服务以异步方式执行读取操作,卸载引擎需要跟踪未完成的操作并强制实施顺序控制。
为此,它维护一个上下文环(ring of contexts),每个上下文记录远程请求的客户端 ID、读取操作的元数据、完成状态以及预分配的读取缓冲区。
对于每个卸载的读取请求,若上下文环已满,卸载引擎会通过流量导向器将其发送至主机;否则,它利用当前尾部的上下文进行记录,并将读取状态设置为“待处理”。
随后,它递增尾部指针并将读取请求提交给文件服务,文件服务在读取完成后将状态从“待处理” 改为 “已完成”。
为处理完成的操作,卸载引擎从当前头部开始检查每个待处理的读取:若读取已完成,则创建数据包并发送至流量导向器,然后递增头部指针;否则停止检查以确保顺序性。
(2)缓存表管理
缓存表由卸载引擎初始化,并与流量导向器和文件服务共享(见表 2)。
当文件服务执行主机文件的写入/读取操作时,会调用用户提供的 Cache/Invalidate 函数生成用于缓存/失效的条目。
流量导向器和卸载引擎分别通过查询该表来执行 OffPred 和 OffFunc。
缓存表的设计目标源于以下特定场景:文件服务的更新速率受存储设备性能限制(最高可达数百万次操作/秒),但查询吞吐量不得影响 DPU 的数据包处理性能(最高可达数千万个数据包 / 秒),因此需对缓存表进行相应优化。
具体而言,DDS采用布谷鸟哈希(cuckoo hashing)以确保最坏情况下的常量查询时间,并在哈希桶中对条目进行链式处理,以降低哈希冲突对插入操作的影响。
(3)与现有工作的对比
相较于其他智能网卡(SmartNIC)卸载框架,DDS 仅卸载能受益于 DPU 能力的存储请求。这种部分卸载原则催生了独特的 API 设计。
与计算存储卸载方案相比,DDS 直接与网络接口交互以处理远程存储请求,无需主机参与。这种主机旁路机制使得卸载增益更高,但需要精心协同设计网络路径(即流量导向器)与卸载引擎。
总体而言,DDS 卸载引擎的设计充分利用了 DPU SoC 的全可编程性,以满足数据系统的多样化卸载需求。
七、DDS实现
我们的DPU 平台为 NVIDIA BlueField-2(BF-2),其配置为:
100 Gbps 网络接口;
8 个 Armv8 A72 核心(64 位,L1、L2 和 L3 缓存分别为 80 KB、1 MB 和 6 MB);
16 GB DDR4 内存及数据路径硬件加速器;
并通过 PCIe Gen 4.0 与主机服务器连接。
我们在 DPU 上安装了 Ubuntu 20.04 作为操作系统,采用 DOCA 1.5.1 作为 DPU 软件开发工具包(SDK)。
所有 DDS 组件总计包含 47,200 行 C/C++ 代码。
1.主机端
主机上的 DDS 前端库采用 Microsoft Visual C++ 实现,并提供 Windows 文件语义。我们将 Windows shell与 DDS 主机设计分离,以便其他文件接口(如 POSIX)可层叠在 DDS 前端之上。
2.DPU 端
我们在文件服务中采用 SPDK(存储性能开发套件)实现用户空间的存储 I/O 操作。
当 DMA 线程接收到文件请求后,会调用 spdk_thread_send_msg 将操作发送给 SPDK 工作线程,该线程通过 spdk_bdev_read/write 将操作提交至 NVMe SSD 驱动程序,并在 I/O 完成后填充响应数据。
对于流量导向器的用户空间网络部分,我们使用传输层开发套件(TLDK),这是一种基于 DPDK 的 TCP/IP 实现。
TLDK 原本是为英特尔 x86-64 处理器构建的,因此我们手动将英特尔 SIMD 指令转换为 Arm Neon 指令。我们将每个 TCP 连接的 L2-L4 处理集中在单个核心上,以避免核心间协调。通过使用接收方扩展(Receive Side Scaling)技术,实现了将流量导向器扩展到多个 Arm 核心。
3.资源利用率
DDS 将资源消耗降至最低。
在 DPU 上,除非另有说明,它仅使用 BF-2 的三个 Arm 核心:一个用于 DMA 通信,一个用于 SPDK 文件服务,最后一个用于协同部署流量导向器和卸载引擎。
主机 CPU 消耗将在下一节讨论。
此外,DDS 所需内存占用极小(DPU 上低于 1 GB,不包含取决于应用的缓存表)。
八、评估
本节对DDS的评估,将回答以下问题:
DDS 能在存储服务器主机上节省多少 CPU?
DDS 能将远程存储访问的延迟降低多少?
每个组件中的优化对提升 DDS 效率的效果如何?
前两个问题对应 DDS 的前两个设计目标(本文将在第九节通过实际案例研究探讨 DDS 的其他目标)。
1.方法论
(1)测试组网
我们评估中的默认集群由两台机器组成。每台机器配备两颗 AMD EPYC 7325 24 核 CPU、256 GB DDR4 内存和 1 TB NVMe SSD,并运行 Windows Server 2022 Datacenter 操作系统。
存储服务器搭载 BF-2 DPU,客户端通过 NVIDIA ConnectX-6 100 Gbps 网卡与服务器连接。
(2)测试方法
我们实现了一个分离式存储的应用程序:
客户端会发出随机的 1 KB 文件 I/O 请求。为了测试存储服务器的性能极限,我们通过以下参数控制请求速率:每条消息中批处理的请求数量、未完成的消息数量以及并发连接数。
服务器接收请求,执行文件 I/O 操作,并将数据返回给客户端。
我们衡量存储服务器的性能和成本:
整体吞吐量(I/O 请求完成率)、
端到端延迟(客户端发出请求到客户端接收响应的时间)、
以及存储服务器主机上的 CPU 消耗(执行工作负载的 CPU 核心数量)。
我们将所产生的延迟和 CPU 成本作为所实现吞吐量的函数进行报告。
我们的基准测试使用 Windows TCP 套接字进行网络 I/O,并使用 Windows NTFS 进行文件 I/O。
我们在第九节中也使用了这些技术。
更多评估结果可在扩展报告中找到。
2.节省的CPU资源
我们首先研究了 DDS 在减少执行 I/O 所需 CPU 资源方面的有效性。
对于读取操作,应用程序通过使用 DDS 的前端库执行文件 I/O 来替代操作系统的文件系统,从而从 DDS 中获益。
图 13a 显示,这带来了显著的 CPU 减少:
基准方案需要消耗10.7 个核心来实现 390 K IOPS 的吞吐量,而通过 DDS 库访问文件时,仅用 6.5 个核心就能实现 580 K IOPS 的吞吐量。
为了充分利用 DDS 在读取方面的优势,应用程序可以使用 DDS 的卸载 API 在 DPU 上完全执行请求。
图中显示,由于 DDS 在 DPU 上高效的 I/O 栈能够将读取吞吐量提升至 730 K IOPS,DPU 卸载有效地消除了主机的 CPU 消耗。
只需实现一个 30 行的 OffloadPred 和一个 20 行的 OffloadFunc,就能以低主机 CPU 成本实现高性能的远程存储访问。
写入结果与读取结果类似,但写入速度较慢(图 13b)。由于数据系统中的更新操作要么是计算密集型的,要么需要超过 DPU 支持的内存(见第二节),因此 DDS 的卸载 API 不支持写入操作。
尽管如此,当写入吞吐量超过 200 K IOPS 时,与基准方案相比,使用 DDS 前端库执行写入操作仍可节省超过 5 个 CPU 核心。
3.延时降低
DDS 还通过消除OS文件系统的开销来改善远程存储访问延迟。将读取操作卸载到 DPU 进一步避免了到主机的往返及其相关的延迟开销。
图 14a 展示了读取基准测试的结果。如预期所示,由于主机 I/O 栈的开销,基准方案在实现相同吞吐量时产生的延迟最高。用 DDS 库取代 Windows 文件系统可使延迟降低 6 倍。
当通过 DDS 卸载绕过所有主机开销时,读取请求的延迟可改善一个数量级。具体而言,基准方案实现 390 K IOPS 时的延迟为 11 毫秒,而 DDS 在实现 730 K IOPS 时仅产生 850 微秒的延迟。
写入延迟如图 14b 所示。基准方案的写入尾延迟高得惊人,在达到 210 K IOPS 时为 48 毫秒。用 DDS 文件取代 Windows 文件效果显著 —— 在吞吐量更高(290 K IOPS)的情况下,尾延迟更低(3 毫秒)。
4.详细对比
我们通过比较十种不同的存储解决方案来剖析 DDS 的优势。
为了解分离式存储的开销,我们使用 Windows NTFS(①)和 DDS 文件(主机上的用户空间前端,文件执行卸载到 DPU②)来本地访问 SSD。
为了与应用程序实现的分离式存储进行比较,我们采用了 SMB(③),这是 Windows 中的一种远程文件服务。将远程磁盘挂载到本地计算机的服务器,以及将 SMB 中的 TCP/IP 替换为 RDMA 的 SMB Direct(④)。
为了进一步隔离网络和存储,除了 TCP+Windows 文件(⑤)和 TCP+DDS 文件(⑥)之外,我们还通过 Redy RPC(一种基于 RDMA 的优化 RPC)来替代 TCP,以在网络上传输存储请求,即 Redy+Windows 文件(⑦)和 Redy+DDS 文件(⑧)。
最后,除了 DDS 针对 TCP 的原生设计(⑨),我们还将 RDMA 移植到 DDS 中,以在客户端和 DPU 之间传输消息来执行文件操作(⑩)。
我们使用第八节中描述的读取工作负载来评估这些方法。我们测量了它们的峰值存储吞吐量、客户端和服务器的总 CPU 消耗(对于本地存储,即①和②,在单台机器上测量),以及达到峰值吞吐量时的端到端延迟。
图 15 展示了我们的研究结果。
使用传统 I/O 栈(即操OS文件系统和 TCP/IP)时,分离式存储会降低峰值吞吐量,并带来显著的 CPU 和延迟开销(⑤与①对比)。
尽管 SMB Direct 由于采用更快的 RDMA 传输(④ 与③对比)而优于 SMB,但这两种协议的吞吐量都远低于应用程序控制的分离式存储方案(③④ 与⑤-⑩ 对比)。
后者可通过批处理等应用优化手段提升性能。
当操作系统开销被消除后,分离式存储的峰值吞吐量可达到与本地存储相同的水平(⑧-⑩ 与②对比)。
然而,不同分离式方案的 CPU 消耗和延迟存在差异:Redy 结合 DDS 文件虽能实现低延迟,但其部分性能提升依赖于在客户端和服务器上消耗少量 CPU 核心;
DDS(TCP)由于 DPU 卸载降低了主机 CPU 消耗,但 TCP 栈会增加延迟开销。
相比之下,DDS(RDMA)的数据接近本地存储,仅产生最小的解耦开销。
这里展示 RDMA 仅用于性能和成本比较。
正如我们在第 1 节中所讨论的,使用内核绕过技术(如 DPDK 和 RDMA)来提高数据系统中的 I/O 效率需要进行精心且昂贵的栈重新设计。该实验验证了即使与这些技术相比,DDS 仍具有优势。
5.组件效率
(1)存储路径效率
DDS存储路径性能的关键在于基于DMA的环形缓冲区,用于在主机和DPU之间传递数据。
为评估其性能,我们启动主机线程向DPU发送8字节消息。
我们将基于进度的无锁方案与两个基线进行比较:
(1)FaRM风格的环形缓冲区,其中主机线程在每条消息后设置标志位表示完成,DPU轮询完成状态,并在接收消息后通过清除位来释放主机环形缓冲区的空间以供后续消息使用;(2)基于锁的环形缓冲区,其中主机线程在写入消息前对环形缓冲区加锁。
图16a展示了不同生产者数量下的消息交换速率。
FaRM风格的环形缓冲区吞吐量最低(峰值为64 K OPS),因为它不支持批处理,且DPU通过PCIe轮询的开销很高。
基于锁的批处理在主机无并发时可实现高吞吐量(22 M op/s),但当有64个生产者时,由于主机竞争,吞吐量降至1.4 M op/s。
我们的方案显著优于基线方案——在64个主机线程下仍能保持6.5 M op/s,分别是FaRM风格和基于锁实现的10倍和4.5倍。
图16b展示了传输单个消息的中位延迟。
与 DDS 的无锁设计(在所有情况下均实现最低延迟)相比,基线方案表现出显著的开销:
FaRM 风格的方法需要通过多次 DPU 发起的 DMA 读取来轮询消息,并额外通过 DMA 写入来释放主机端的每条消息空间;
而基于锁的方法中,当并发生产者数量增加时,线程间的锁竞争会成为延迟瓶颈。
我们还评估了 DDS 存储路径在处理不同大小的主机发出的文件请求时的效率(图 17)。
这些结果表明,我们在第四节中提出的零拷贝方案是有效的:
与为适应异步 I/O 而进行额外内存拷贝的基线方案相比,DDS 的零拷贝设计将文件吞吐量提高了近 93%。
(2)网络路径效率
为了展示 DDS 流量导向器使用用户空间网络的优势,我们测量了一个应用程序的服务器端延迟 —— 客户端向服务器发送 TCP 消息,服务器将相同的消息回传(见图 18)。
我们比较了两种版本:由主机回传消息的原始版本,以及由 DPU 处理回传的卸载版本。对于后者,我们采用两种 TCP 拆分方式:BF-2 上的 Linux TCP(操作系统)和 TLDK(用户空间)。
以下是研究发现。
首先,Linux TCP 会引入不可忽视的开销,抵消了 DPU 卸载的优势。
事实上,卸载回显的延迟甚至高于原始版本。相比之下,我们对 TLDK 的优化使用避免了大部分开销,与 Linux TCP 相比延迟降低了 3 倍,使 DPU 卸载具有优势(延迟比原始版本低 2.5 倍)。
为了将用户空间网络的优势与DPU卸载的优势区分开来,我们进行了一项额外实验:
在主机上运行Linux(Ubuntu 20.04,内核5.4.0)以支持TLDK,并比较在上述应用中于主机和DPU上运行TLDK的性能。
图19展示了实验结果:当处理大型消息时(操作更具内存密集型,如数据库系统中的页面访问场景),DPU上的TLDK处理速度更快。这是因为:(1)避免了NIC到主机的往返传输;(2)DPU内存通常比主机内存效率更高,这与之前的研究结论一致。该实验验证了采用用户空间技术对DPU上的DDS进行优化的重要性。
流量导向器可通过单个 DPU 核心引导 6.4 Gbps 的流量,并且由于接收端缩放(RSS)机制,当添加更多核心时,处理能力可线性扩展。
(3)卸载引擎效率
与存储路径类似,DDS 的卸载引擎集成了零拷贝技术以提升 I/O 性能。为验证其有效性,我们在分离式应用中分别运行启用和未启用此优化的场景。
图 20 显示,在数据路径上避免内存拷贝可同时提升吞吐量(峰值吞吐量从 520 K IOPS 增至 730 K IOPS)和降低延迟(峰值吞吐量下的中位延迟从 310 微秒降至 260 微秒)。
我们进一步研究了缓存表的性能。
我们随机生成缓存项,并测量将它们插入 BF-2 DPU 的缓存表以及随后从中查询的吞吐量。对于查询操作,我们将工作线程的数量从 1 个调整到 8 个。
平均而言,无论缓存项大小如何,该缓存表在单个写入线程下可实现 120 万次/秒的插入操作,在 8 个读取线程下可实现 1570 万次/秒的查询操作。因此,表 2 中列出的需求能够得到满足。
九、生产系统集成
1.云DBMS
作为一种分离式存储的云数据库管理系统(DBMS),SQL Hyperscale 在计算服务器与云存储服务之间部署了页面服务器,并增设日志服务器以进一步分离日志和数据存储。
页面服务器将数据库的一个分区(如128 GB)存储在其本地固态硬盘(SSD)上,这些 SSD 由弹性缓冲区池扩展(RBPEX)管理并重放从日志服务器获取的日志以刷新页面。
当缓存未命中时,计算服务器会发送读取请求从页面服务器获取数据,页面服务器在Windows 套接字和文件系统之上运行 SQL 栈来处理这些请求。
这些栈的底层是执行网络通信和文件I/O 操作的 Windows 套接字和文件系统。
如图2 所示,处理读取请求会在页面服务器上消耗大量 CPU。
托管大型数据库意味着需要启动大量页面服务器(例如,一个100 TB 的数据库需要 800 个页面服务器),这很容易成为主要的成本因素。
我们的SQL Hyperscale 部署包含一台计算服务器、一台页面服务器和一台日志服务器,每台服务器均运行在第八节中指定的机器上。
承载页面服务器的机器配备BF-2 DPU,并按以下方式集成 DDS:
首先,我们使用DDS 前端库替换 Windows NTFS 来管理 RBPEX 文件。
这仅需对SQL Hyperscale 的源代码进行轻量级修改(数百行代码变更,与 SQL Hyperscale 代码库的规模相比可忽略不计),因为我们的库提供了与 Windows 文件相同的文件 API。
针对应用的卸载还需要自定义表1 中的四个函数。
具体而言,我们需要缓存存储在RBPEX 文件中每个页面的 LSN(日志序列号)和文件偏移量,以页面 ID 为键(Cache 函数),并在页面服务器重放日志更新页面时将其失效(Invalidate 函数)。
当流量导向器检测到读取请求时,会使用页面ID 查询缓存表,若缓存的 LSN 大于或等于请求的 LSN,则卸载该请求(OffloadPred 函数)。
最后,通过OffloadFunc 函数构造对 RBPEX 文件的读取操作。
将读取操作卸载到DPU 有效消除了图 2 中所示的 CPU 消耗。
如图21 所示,它还改善了页面服务延迟:页面服务器在达到 90 K IOPS 时,p99 延迟为 4.4 毫秒,而使用 DDS 达到 160 K IOPS 时,仅产生 1.3 毫秒的延迟。
2.KV服务
FASTER 将键值(KV)记录存储在跨越主内存和辅助存储的日志中。
内存部分(即日志尾部)支持原地更新,其余部分为只读,通过辅助存储抽象 IDevice 进行访问。
新记录会追加到日志尾部,若内存不足,旧记录会被刷新到 IDevice。
执行读取操作时,FASTER 会在哈希索引中查找键,并从内存或存储中检索记录。
当内存受限时,大部分请求由 IDevice 处理。FASTER 的默认存储是使用 Windows 文件实现的 IDevice。
我们使用两台与上述相同类型的机器搭建了一个解耦的键值服务,其中服务器运行带有 YCSB 基准测试 的 FASTER(8 字节键和 8 字节值),并将大部分记录存储在存储器中,客户端通过 TCP 向服务器发送 YCSB 均匀读取工作负载。
图 22 显示,当吞吐量增加时,CPU 消耗很高:340 千次操作/秒需要 20个服务器核心。
为了使用 DDS 优化键值(KV)服务器,我们首先利用其前端库实现了一个 IDevice。
然后,我们借助表 1 中的卸载 API 将 {键、文件 ID、文件偏移量、记录大小} 条目缓存到缓存表中,并利用这些条目将工作负载卸载到 DPU。
所有这些改动总共仅 360 行代码,却带来了显著的改进。
图 22 显示,集成 DDS 的 FASTER 实现了 970千次操作/秒的吞吐量,且无需消耗任何主机 CPU 资源。得益于其高效的 DPU I/O 栈,DDS 的延迟降低了一个数量级。
十、相关工作
大多数关于 DPU(数据处理单元)的研究由网络和分布式系统领域的社区完成。系统领域的研究已探索了将文件系统操作卸载到 DPU 的技术。
下面我们对其中一些论文进行总结。
IO-TCP 将媒体文件服务卸载至 DPU,但仍将每个请求转发至主机。由于操作系统和数据系统栈的开销,这种方式对数据系统而言效率较低。
Xenic是一个使用 DPU 缓存数据的主存事务系统。DDS 虽可用于数据缓存,但其适用场景远不止于此。
Gimbal利用 DPU 在多租户环境中提升远程存储的通道利用率,这一思路可用于扩展 DDS 以更好地支持多租户场景。
hKVS在 DPU 内存中缓存键值(KV)记录,并在主机与 DPU 之间同步写入操作。与之不同的是,DDS 支持基于固态硬盘(SSD)的数据。
LineFS 将分布式文件系统中 CPU 密集型操作卸载至 DPU,而 DDS 则在分离式存储架构中卸载远程读取请求。
Lovelock提出了一种无主机的 DPU 集群设计,其评估了 DPU 与 CPU 的成本和功耗,但未报告原型实现。
借助诸如 NVMe-oF、网络附加存储和计算存储等最新存储技术,卸载操作也得以实现,这些技术为应用程序提供了对存储设备的直接访问。
例如,在固件支持下,X-SSD维护了一个逻辑块地址列表,用于将日志数据从持久性内存直接降级到固态硬盘。
相比之下,受益于 DPU 的通用可编程性,DDS 提供了两级地址转换:将应用请求映射到文件地址的缓存表,以及将文件地址映射到磁盘块的文件映射。这种灵活性允许为各种数据系统进行定制,例如卸载对键值记录或数据库管理系统页面的操作。
下推是适用于分离式架构的有效查询优化策略。
尽管由于 DPU 的弱核心和有限内存,查询下推在 DPU 上颇具挑战性,但 DPU 上的硬件加速器可加速特定数据库操作 。例如,正则表达式 ASIC 可用于字符串操作。我们将此研究留作未来工作。
十一、结论
我们证明了 DPU(数据处理单元)能够显著提升解耦存储的性能并降低成本。我们提出了实现这些优势的架构、该架构的具体实现以及性能评估。
未来的工作可以探索将其他数据库管理系统(DBMS)功能卸载到 DPU,例如 DBMS 特定的网络功能和查询操作符,还可以利用 DPU 的硬件加速器(如加密、压缩和正则表达式引擎)以可移植的方式执行云数据系统任务中计算密集型的组件。