论文阅读 | Patronus:High-Performance and Protective Remote Memory
这篇文章发表于FAST'23,是清华大学存储研究组提出的工作。文章介绍了他们基于RDMA并通过软件协同设计实现的高性能且有保护的远程内存。
Paper Link:https://www.usenix.org/conference/fast23/presentation/yan
背景和动机
远程内存(Remote Memory, RM)架构是指将内存和CPU分离为两个独立的资源池(计算节点CN和内存节点MN)的架构,CN能够根据需要动态地从MN获得内存资源,这样的架构具有高内存利用率和高效内存共享的特点。得益于RDMA网络的广泛部署,它允许CN以one-side和低延迟的方式访问远程内存,目前许多数据中心都部署了RM架构。
但是实用的RM系统仍有一个需求未解决:远程内存保护(Remote Memory Protection)。不受保护的内存会面临非法访问的风险,可能导致数据损坏或泄漏。同时实现内存保护和高性能的RM系统是比较困难的,目前的RM系统都无保护地全部或以粗粒度暴露内存。
设计目标
考虑到RM系统是作为基础设施建设的,它的性能决定了上层工作负载的性能上限。因此具备保护的RM系统的设计目标大体可以概括为以下三点:
G#1:工作负载在峰值时会有大量的内存权限请求,因此系统必须要做到高性能低延迟,避免成为实际使用时的瓶颈。
G#2:客户端可能会在持有独占权限时发生故障,这会影响整个系统运行。因此,系统必须要能快速反应客户端故障。
G#3:非法访问会导致QP进入错误状态,而拒绝接收RDMA请求,无法继续工作。这种中断可能会给共享同一QP的其它客户端造成影响。因此,系统需要一种保证在非法访问发生下保持性能的机制。
现存方案简述
现有的RM系统保护方案大体可以分为如表格所示的几类:
- Two-sided指的是在访问远程内存时完全使用RDMA的two-sided verbs,但这并不适用于RM架构,因为这需要超出内存节点所能提供的计算能力。
- MR指的是完全使用RDMA的MR进行内存保护,具体而言是对每个权限请求都相应注册一块MR并公开给客户端。然而,MR相关操作开销巨大,延迟非常高(如Fig1),且会随着内存需求变大而提升,无法满足 G#1 。而且,MR也无法感知客户端故障和处理QP错误,因此也无法满足 G#2 和 G#3 。
- QP指的是基于QP进行内存保护,具体而言是通过设定QP到不同状态从而达到不同的权限限制。但是这样的方案只能达到以通道为粒度的内存保护,而无法做到字节粒度的保护,并且修改QP状态也需要极大开销(如Fig1),无法满足 G#1 。同样,这种方案也无法感知客户端故障和处理QP错误,因此也无法满足 G#2 和 G#3 。
- MW则提供了更快更灵活的bind操作,延迟极低且固定不变,满足了 G#1 。但它仍然无法满足 G#2 和 G#3 ,因此需要软件协同设计满足这些机制。
(如果对以上名词及其机制还没有足够了解,可以参考RDMA操作类型、MR、QP、MW)
设计概览
文章提出了名为Patronus的高性能有保护的RM系统,设计思路可以概括如下:
- Patronus充分利用了RDMA的Memory Window(MW)机制减少OS内核和RNIC开销,而非直接使用开销极高的Memory Region(MR)机制,并协同软件设计进一步减少MW操作次数,以达成高性能的目标。
- Patronus引入租约机制,用于快速反应客户端故障,避免故障客户端无法释放内存资源而阻塞系统。同时为了进一步节省内存池有限的计算资源,将租约信息在保证保护的前提下委托给客户端管理。
- Patronus会让客户端和内存池准备额外的空闲QP,用于在QP进入错误状态时快速替换,从而隐藏中断,避免QP损坏带来的修复延迟。
系统设计
接口
Patronus系统的API如表2所示:
- 权限开始 :客户端获取内存权限可以分为两种情况。一种是通过 allocate 接口,分配一块新的内存;一种是通过 acquire 接口,尝试获取一块已知区域的内存访问权限。两个接口都会对MN发起RPC,MN相应绑定MW(bind)到分配好的或指定的内存区域上,随后返回权限凭证 Perm 给客户端。请求参数包括了访问模式(读/写)和所有权(共享/独占)以及期望时长。对于所有权冲突的请求,Patronus会延迟赋予权限。
- 权限延长 :客户端通过 extend 接口延长权限生命,在后文中会提到这是通过RDMA的one-sided verbs实现的。这样可以避免频繁重新获取同一区域的内存权限,提高效率。
- 权限结束 :权限会在租期到达的时候结束,也可以通过 revoke 接口显式地结束权限,这也是通过RPC实现的。无论是哪种情况,MN都会在结束权限时将对应的MW解绑(unbind)。
- 数据路径 :Patronus完全使用RDMA的one-sided verbs进行数据操作。
系统结构
Patronus提供了一个CN上客户端使用的库和MN上运行的管理进程。在MN上,内存被分为了两部分:header池(≤0.02%)和buffer池。header是负载存放权限元数据的结构,buffer则是真正供客户端访问的内存。
header的组成如Fig.3所示,它主要包括两方面信息:资源信息和租约信息。Patronus使用header的地址作为客户端访问MN上内存的凭证。
每当客户端调用接口获取权限时,管理进程会从header池里分配一个header,随后绑定两个MW到buffer和header上,绑定buffer自然是为了访问申请好的内存区域,绑定header则是为了后面会说到的CN协同租约延长。内存分配由不同object大小的slab分配器进行管理。为了检测快过期的权限,管理进程会定期轮询,找到到期的权限,并进行解绑回收操作。
CN协同租约延长
一个朴素的租约延长方式是客户端通过RPC通知MN上的管理进程延长租期。但是,这会造成巨大的开销,MN上的计算资源很少,无法支持这种方式。因此,Patronus采用的是CN协同的方式进行租约延长。具体而言就是为header的lifetime字段也绑定上一个MW,将其暴露给客户端,这样客户端就可以通过RDMA的one-sided verb去修改MN上保存的租期,不会造成极大开销。
但是这样做又可能引入饥饿问题:客户端可以将lifetime设置得极大或者连续修改租期,使其长久掌握对应内存区域的所有权,从而阻塞其它客户端的请求。因此,Patronus设定了两个规则防止这种情况发生:
1. lifetime不能超出一个预定义的最大值,当管理进程发现已经超时的权限时,会将其强制失效。
2. 把客户端修改lifetime的操作从RDMA_WRITE换成RDMA_CAS,管理进程可以通过将lifetime置0来告知客户端无法再延长租期。
降低开销
虽然MW相关操作的开销相较于MR相关操作已经有了显著降低,但是仍然不够。为了应对峰值请求,需要软件协同设计进一步降低开销。
利用成对的相反操作
系统每时每刻都会有内存分配和回收,相应不断有MW的绑定和解绑操作进行。理想状态下,MW的绑定和解绑操作各占一半。遵循这一经验事实,Patronus会合并每一对绑定和解绑操作变成一个重绑定操作(rebind)。
这一技术不会引入额外的代价。第一,管理进程不会为了合并操作而刻意等待,不会因此造成延迟;第二,客户端的请求本身会由RNIC进行batch,硬件支持下不会引入额外的batch开销,更应说handover充分利用了RNIC的这一特性。
延迟解绑
对于那些被解除分配的内存区域,如果它不被马上重新使用的话,那么可以延迟解绑相应的MW。这样可以降低被错误访问从而造成错误状态引入的概率。header就是延迟解绑很好的对象,一般来说header池里有足够多的空闲header,因此header的重新使用需求不高,对header的解绑可以延迟;而如果buffer池内仍然有足够多的空闲内存空间,那么buffer的解绑也可以延迟。
充分利用连续性
可以观察到,如果两块内存区域地址连续并且有着共享相同的租期,那么两个MW可以合并为一个。直观地说,这种情况发生的概率似乎非常小,但是可以人为构造出来。在处理 allocate 请求时,管理进程会将header和buffer一同分配,也就是在buffer前多分配32B放置header。文章对 Perm 的编码设计特意将lifetime字段放在了末尾,这样它和buffer构成了连续的内存空间,使用一个MW绑定即可。
非法访问隔离
虽然在MW的限制下,非法访问不会导致内存损坏,但是依然会不可避免得导致QP进入错误状态而无法工作,要修复QP需要通过Modify QP State的操作。但是从Fig.1中可以看到,这一操作的延迟相当高。因此,Patronus不选择修复QP,而是通过替换QP的方式隐藏错误。
Patronus准备了多个空闲的备用QP用于替换。每个客户端会被赋予一个虚拟的QPN,同时Patronus会维护一个虚拟QPN到真实QPN的映射表。当QP错误发生时,Patronus会透明地选择一个空闲QP替换,并且更改QPN映射表,由此使得错误被隐藏,系统可以继续运行。这种方式的实现得益于MW不需要和QP关联的特性,并且当QP没有被使用时,它不会占用RNIC的稀缺资源,备用QP仅需要一点点内存作为代价,这是值得的。
其它实现细节
MW池
尽管MW的绑定和解绑操作开销不高,但是它的创建仍然有较高的延迟(大约100微妙)。因此Patronus维护了一个MW池,避免该延迟出现在关键请求路径上。处理二次失效
ABA问题可能会导致权限双重失效。具体而言,当过期的RPC尝试定位一个已经被重用的权限header时,就会出现ABA问题。为了解决这个问题,Patronus会在每个RPC内附加 Perm 的start time字段,管理进程会过滤掉所有start time不匹配的RPC。
用例和实验
这个直接看论文吧。
笔者的一些想法和疑问
从利用成对的相反操作中用到了rebind操作和非法访问隔离的QP切换可以推测出来,文章使用的是Type 1的MW。Type 1 MW不需要和QP绑定,而是通过和PD关联,间接与QP关联。并且Type 1 MW支持直接换绑,绑定新的内存区域前不需要显示地解绑旧区域,换绑后旧的R_Key直接失效。此外,文中所说的MW池的实现也得益于Type 1 MW支持零长度绑定。
内存区域的权限有最长租约的限制,这是否意味着这样的系统不适用于长期持续运行的进程使用,例如内存数据库?
合并成对bind和unbind的操作,以及延迟解绑,这两个优化是不是相互矛盾的存在?
为了实现内存连续将header和buffer合并,那么是不是就不存在header池了?
总而言之,我个人认为这篇文章还是有很多没交代清楚的地方。