一、背景和问题
1. EXT4日志的问题
随着计算机系统加载数百个CPU内核,文件系统的可扩展性得到进一步强调。因此本文主要定位服务器中广泛使用的Ext4文件系统在做日志记录时的可扩展性问题。EXT4日志有两个严重的缺点;串行提交并提交原始页面缓存条目。
串行提交:在EXT4中,日志提交是严格的串行活动。只有在前面的日志提交完成后,它才能提交后面的日志事务。因此,在EXT4中,一次最多只能有一个正在运行的事务,最多只能有一个提交事务。图1展示了串行提交的一个示例:
将前面的fsync()和后面的fsync()的日志事务分别标记为Tx1和Tx2。JBD线程仅在完成提交Tx1后才开始提交Tx2(t3时刻)。
提交原始页面缓存条目:EXT4使用原始页面缓存条目将更新的内容提交给磁盘。它不会为日志提交创建更新的副本。如果关联的页面缓存条目被提交到磁盘,则需要更新文件系统状态的应用程序将被阻塞。这种情况也叫做事务冲突,会严重影响EXT4的日志记录可扩展性。
2. EXT4日志可扩展性问题的详细分析
本文通过EXT4执行串行日志提交和BarrierFS执行并发日志操作来探索文件系统日志中的可伸缩性平静,最终确认了影响EXT4 和 BarrierFS 性能可扩展性的四个主要组件;事务冲突、串行刷新、事务锁定间隔的长度和复合日志的合并程度。
(1) 事务冲突
事务冲突技术值定义为试图修改DMA下的日志块的文件操作数量。尽管 EXT4 的影子分页功能可以解决事务冲突,但 EXT4 日志记录仍然存在大量事务冲突。
即使BarrierFS同时提交多个事务,它也会使用单独的刷新命令刷新每个事务。由于每个日志提交都会在存储设备上产生单独的刷新,因此BarrierFS 的并发日志设计的好处是体现不出来的。此外,当正在运行的事务试图在flush下修改日志块时,它们都会发生冲突并被阻塞。这种提交事务的更高并发性导致几乎 100% 的文件操作在所有线程中都遭受事务冲突。
(2) 事务锁定
并发日志中可伸缩性失败的主要原因之一是延长的锁定间隔。
对EXT4,事务锁定间隔的长度可以忽略不计,因为锁定期只是等待未完成的文件操作完成的时间,一般来说是很短的。
BarrierFS可以在正在运行的事务摆脱冲突之前过早地将其置于锁定状态,直到所有未完成的文件操作完成并且所有冲突都得到解决。因此,一个正在运行的事务在 BarrierFS 中停留在锁定状态的时间间隔比在 EXT4 中长得多。
两个文件系统锁定时间的对比如图2:
(3) 有限合作
影响文件系统日志性能可伸缩性的关键因素是日志事务的合并程度——日志事务中文件系统操作的数量。
EXT4 日志的严格串行性质实际上有助于增加复合日志的合并程度。当日志提交正在进行时,所有与传入文件操作相关的更新都被插入到正在运行的事务中。因此,随着线程数量的增加,合并机会会增加。
而对于BarrierFS,由于它过早地将正在运行的事务置于锁定状态,从而减少了将多个文件操作合并到单个日志事务中的机会。
二、设计
作者通过以下几个技术点实现了一个支持高并发的日志文件系统—CJFS。
1. 双线程日志
作者将日志提交过程分为两个阶段,即提交阶段和刷新阶段,并为每个阶段分配单独的线程,即提交线程和刷新线程。提交线程负责向存储发出日志事务的写请求。完成后,存储设备会向主机发送中断,通知请求服务已完成。刷新线程负责使日志块和提交块持久化。一旦中断到来,刷新线程被唤醒,并向存储发出刷新命令,使日志块和提交块持久化。
如图3所示,通过分离提交线程和刷新线程,CJFS 可以在不等待前面的日志()提交完成的情况下提交后续事务。
2. 多版本影子页
为了解决事务冲突,作者提出了多版本影子分页。当提交线程启动日志提交时,它会创建日志事务中所有页面的影子副本。在提交日志事务时,提交线程使用事务中每一页的影子副本来将日志事务传输到存储设备,而不是使用原始页面。由于日志模块使用影子页面进行日志提交,后续的文件操作可以更新原始页面。
3. 机会性合并
由于影子页面的数量有限,如果所有预分配的影子页面都用于保存日志,仍然会发生事务冲突。如果发生事务冲突,正在运行的事务将进入锁定状态,并且所有修改文件系统状态的后续文件操作都将被阻止。为了解决这个问题,作者提出了机会合并。当所有未完成的文件操作完成时,提交线程检查是否存在任何冲突。如果存在冲突,提交线程会将锁定状态的事务返回到运行状态并被阻塞。当提交线程被阻塞后,正在运行的事务可以继续容纳新传入的日志块。
如图四展示了机会合并的一个例子,在两个 LOCKED 状态之间的时间段内处于RUNNING状态。在运行事务的状态变为RUNNING状态后,所有被阻塞等待日志句柄的挂起文件操作都被发布日志句柄。借助这样的机会合并,CJFS 可以将大量文件操作合并到正在运行的事务中,从而提高文件系统操作的并发性。
4. 复合刷新
为了使 CJFS 的日志以完全并发的方式工作,提交线程和刷新线程都应该能够以并发的方式处理关联的任务。针对EXT4和BarrierFS的序列化刷新问题,作者提出了一种复合刷新的概念:当刷新线程即将发送刷新命令时,它会检查是否存在任何后续提交事务,如果后续提交事务不存在,则发送刷新命令;如果存在接下来的提交事务,它会改为发送cache barrier命令(控制闪存设备保证请求处理顺序的命令),从而将持久化事务的任务委托给后面的事务提交请求。通过cache barrier命令,存储控制器可以确保各个事务的日志块按顺序持久化。
图5说明了复合刷新的工作原理。当flush线程传输完事务后,flush线程开始传输事务,而不是调用flush来刷新事务。当刷新线程完成传输事务时,它发现没有其他正在提交的事务正在运行。然后,它调用flush使事务和事务持久化。
三、实验效果
1. 实验设置
作者将CJFS与BarrierFS、SpanFS [15]、Vanilla EXT4 和带有 Fast-Commit的EXT4进行了比较。同时使用三个基准测试集进行测试:用于邮件服务器的varmail,用于文件服务器的dbench,以及MySQL上的 OLTP-Insert。测试平台为40核服务器(两个Intel Xeon Gold 6230处理器和512 GB DRAM)和三星970 Pro SSD(MLC闪存,NVMe)进行实验。
2. 实验结果
图6
可以看到在多种测试集及其对应的多种数据更新场景下,CJFS都能达到最好的多核性能扩展效果。