论文分享|XRP: In-Kernel Storage Functions with eBPF

渗透技巧 2年前 (2022) admin
666 0 0

论文分享|XRP: In-Kernel Storage Functions with eBPF

论文分享|XRP: In-Kernel Storage Functions with eBPF

Introduction

随着 NVMe 储存设备的出现,储存设备的访问的延迟逐渐变小。在如此高性能的情况下,内核储存堆栈的开销反而变成了影响应用程序访问储存设备的延迟和吞吐量的主要因素。

论文分享|XRP: In-Kernel Storage Functions with eBPF

解决这个问题的办法往往是侵入性的,比如通过 SPDK 等库来绕过内核使应用程序直接访问底层设备的话,应用程序需要自己实现用户态文件系统,并且放弃内核带来的隔离和安全性;因为无法直接接收 IO 的中断信号,每个应用程序需要自己实现轮询队列,导致 CPU 资源的浪费;不同应用程序无法共享轮询,进一步加重 CPU 资源的浪费。同时有工作表明,使用 SPDK 的应用程序在线程数超过核心数的时候会遭受严重的竞争,进一步导致延迟升高、吞吐量降低。

作者对这个问题提出的解决方案是,将数据的访问逻辑尽量下沉到靠近储存设备的位置以规避储存堆栈的访问以及用户和内核态切换的开销。作者将这种优化命名为 XRP(eXpress Resubmission Path)。 在本文中,作者使用了 BPF 来有效避免数据在内核和用户空间之间来回传递,作者认为它可以用于 “获取一系列辅助性数据并仅返回最终结果” 的场景,例如遍历B树索引获取数据的时候可以通过 BPF 程序可以将节点检索的过程维持在内核中,而不必频繁在内核态与用户态之间交互。

Motivation

论文分享|XRP: In-Kernel Storage Functions with eBPF

在这张图中,黄色箭头是应用调用链路,蓝色箭头是系统调用链路,红色是NVMe驱动调用链路。在传统 bio 场景下调用链路与黄色相同,会有内核与用户态上下文的切换开销、文件系统以及bio阻塞开销和NVMe协议以及设备开销;在使用 io_uring 场景下调用链路会短于蓝色链路,没有上下文切换的开销和 bio 阻塞,但是会有文件系统开销,NVMe协议以及设备开销。而在使用 NVMe Driver 的场景下调用链路与红色链路相同,只有NVMe协议以及设备开销。

论文分享|XRP: In-Kernel Storage Functions with eBPF

在以 B-Tree 搜索的基准测试中可以观察到,越接近储存设备,储存设备的访问性能就越高。比如,在 NVMe 层的访问吞吐量比用户空间层的提高了 2.5 倍。

另外可以发现,在一次 B-Tree 搜索的过程中需要有多次储存设备的访问。这些访问中有一些访问是简单依赖上一次访问的结果。这样大大增加了一次搜索时的内核开销。

论文分享|XRP: In-Kernel Storage Functions with eBPF

而如果能将用户定义的依赖关系下移到离设备层更近的地方,比如放在 NVMe 驱动层,那么占单次访问开销的 42% 左右的文件系统和 bio 层的开销将被节省下来,大大减小一次搜索的开销。

论文分享|XRP: In-Kernel Storage Functions with eBPF

那么 Custom Function 由谁来执行最好呢?考虑到这个函数是一个用户定义的,在内核态执行的,那么它需要足够的安全。而 eBPF 刚好就是一个符合这些要求的完美人选。BPF 是 Linux 内核提供的快速且安全的一个模块。用户编写的 eBPF 程序会被 BPF 验证器验证后进入 BPF 虚拟机执行。加载 BPF 程序不需要重新编译内核,而且能确保其不会造成内核恐慌。在上述例子当中,可以将 B-Tree 的遍历逻辑以 BPF 程序编写放入 NVMe 驱动层,就可以大幅减小 B-Tree 搜索的过程中用户态程序与内核态的数据交换次数。

Challenges and Design

由于 XRP 的用户函数需要尽可能靠近硬件层,导致 XRP 存在缺少文件系统上下文的问题,因此面临一些挑战:

1. 地址转译与安全。在驱动层, XRP 需要按照硬件块来访问数据,但在应用层,通常使用文件和偏移量来访问,两者需要通过文件系统进行转译。此外,驱动层能够访问任何硬件块数据,需要借助文件系统来完成权限和隔离的约束。

2. 并发与缓存。在 NVMe 驱动层无法访问文件系统以及内核缓存,page cache 会失效,因此对于多数应用程序来说其实并不适用。

但根据观察,很多(数据库)存储引擎所使用的数据结构是相对稳定的,要么完全不会进行原址更新(例如 LSM 的索引文件),要么不会频繁更新(例如 B-Tree 索引, 在某些常见的读写负载场景)。此外,这些存储引擎都会将索引保存在少量大文件中,且每条索引都不会跨文件保存。这些实际情况使得以上两类挑战可以在一些特定场景下被解决。

于是作者提出了 XRP 的设计原则:一次访问一个文件;面向稳定的数据结构;使用用户态的缓存机制;在异常情况下回退。

Evaluation

作者使用 XRP 集成了两个数据库引擎。一个是基于 BPF 和 B-Tree 的 BPF-KV 以及基于 BPF 和 LSM Tree 的 WIREDTIGER,并对其吞吐量和尾延迟进行了测试。这里节选其 BPF-KV上的测试。

论文分享|XRP: In-Kernel Storage Functions with eBPF

可以看出 XRP 在不牺牲隔离和 CPU 效率的情况下获得了比 SPDK 更好的吞吐量。并且同时可以观察到 SPDK 在线程数超过核心数后的性能锐减。

论文分享|XRP: In-Kernel Storage Functions with eBPF

可以看出与 read 相比,XRP 减小了 45% 左右的尾延迟;SPDK 在线程数超过核心数的 1.5 倍时,尾延迟剧烈上升。


以上,谢谢大家阅读。在原文中,作者基于 ext4 文件系统介绍了 XRP 的一些具体实现细节,欢迎移步原文查看。

原文始发于微信公众号(COMPASS Lab):论文分享|XRP: In-Kernel Storage Functions with eBPF

版权声明:admin 发表于 2022年10月24日 下午11:46。
转载请注明:论文分享|XRP: In-Kernel Storage Functions with eBPF | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...