论文悦读(4)——NVM文件系统之SplitFS(SOSP‘19)文件系统

导读:

  • 本质:这是一篇挺有意思的NVM文件系统论文。其本质是利用mmap()实现对已有的文件系统套壳,然后通过避免陷入内核来减少NVM文件系统的软件开销。这是一种新颖且具启发性的研究思路,让人看完不禁赞叹:原来NVM文件系统还能这么设计,原来顶会论文还能这么发……
  • 延伸:个人感觉其做的工作与PMDK库类似。考虑到PMDK依靠下层文件系统提供的基本mmap()接口,然后为上层提供更易用的NVM读写接口,例如:pmem_persist()(该函数写入并调用fenceflush持久化数据);而SplitFS为上层提供的是POSIX接口。
  • 感想:
    • 系统设计中很难有非常创新的idea,因此动机在存储系统设计中占有举足轻重的地位
    • 系统的设计思路不需要很复杂,足够为其动机服务即可。

1. 背景 (Background)

1.1 PM与PM文件系统

关于PM的特点,在之前的文章中已经讲过许多,这里对SpliFS的介绍进行简单概括。PM(NVM)拥有与DRAM相同数量级的性能参数:

Latency (PM/DRAM)Bandwidth (PM/DRAM)
写 (Store) ≈ 1 × \approx 1\times 1× 1 / 6 1/6 1/6
读 (Load) 2 − 3.7 × 2-3.7\times 23.7× 1 / 3 1/3 1/3

单个机器可以配备至多6TB的PM设备(每台机器有两个CPU,每个CPU可以插6块PM,目前每块PM最大512GB,因此6TB),因此将PM设备作为存储介质是很重要的一种应用场景

传统的文件系统在写路径上有着巨大的开销,例如:块分配、日志、更新复杂的结构等;最新的PM文件系统针对PM特点进行设计,大大减少了软件开销。其中Aerie与Strata两个文件系统都通过尽可能避免内核态操作来减少软件开销:

  • Aerie: Aerie构建了一个用户态文件系统库,并且利用一个微内核模块对PM空间进行粗粒度分配;
  • Strata: Strata将文件系统部署在用户态,包括一个用户态文件系统库以及一个用户态元数据服务器(用于更新元数据);

然而,这些文件系统在数据写入方面还是存在着较大的开销。

1.2 DAX与Memory Mapping

  • DAX文件系统可以绕过page cache,利用memory mapping直接访问PM
  • mmap()将进程虚拟地址空间中的一个或多个页映射到 PM 上的数据块,这样应用对进程虚拟地址的访问就会被硬件翻译转换到对PM的访问
  • DAX和mmap()虽然提供了对PM的低延迟访问,但却没有提供原子操作、文件管理等,因此,PM文件系统仍然为应用和用户提供了非常有用的特性。

2. 动机(Motivation)

本文以顺序追加写入4K大小块为出发点,观察了几个不同文件系统的写入开销,如下表所示:

在这里插入图片描述

值得说明的是,在本文的实验环境下,写入4KB数据到PM上只消耗671ns,因此 O v e r h e a d = A p p e n d   T i m e − 671 n s Overhead=Append\ Time - 671ns Overhead=Append Time671ns。从表中的结果来看,可以发现对于文件追加写这一场景来说,现有的PM文件系统造成了 3.5 − 12.4 × 3.5-12.4\times 3.512.4×的额外开销。

上述观察说明了问题的严重性。但我们还应该思考的是:**问题在真实场景下真有这么严重吗?**频繁Append I/O是存在的吗?或者说存在的情况多吗?此是后话,旨在唤醒读者的个人思考。

3. SplitFS设计与实现 (Design & Implementation)

3.1 设计目标 (Design goals)

  • 低软件开销: SplitFS主要目标是减少数据操作的开销,尤其是写入追加写入
  • 透明性: 上层应用不感知
  • 最小化数据拷贝与写I/O:尽可能减少对PM的写,一方面提升性能,另一方面避免PM的磨损;
  • 减少开发复杂性:基于EXT4-DAX进行开发,减少对SplitFS的代码实现与维护量
  • 灵活性:应用有多种一致性选项可以选择,例如POSIX、SYNC等;而现有的PM文件系统为所有应用提供了相同的一致性选项(因为大家都用同一文件系统)。

3.2 模式选项 (Mode)

作者为SplitFS提供了多种选项,猜测在其研究过程中,发现STRICT模式下性能欠佳,通过放松语义性能更高。

SplitFS为应用提供了三种不同的模式:POSIX、SYNC以及STRICT,并行的应用可以通过使用不同的模式来达到不同的目标(例如:有些应用不需要每次I/O都是同步的,而有些应用则需要)。下表总结了各模式之间的差异:

在这里插入图片描述

1. POSIX模式

在POSIX模式下,SpiltFS提供元数据一致性,这与EXT4 DAX相似。当系统崩溃后,文件系统根据元数据回滚到一致的状态。在该模式下,覆盖写入是同步的,并且是原地更新的(即,可能出现数据写一半掉电的情况)。注意Append写入不是同步的,需要显示调用fsync()函数来进行持久化。然而,SplitFS能够保证原子地追加写。SplitFS的POSIX与传统POSIX语义有所不同的地方在于:当文件被访问或是修改时,文件的元数据不会立刻被更新。事实上,这与O_DSYNC语义类似。参考这里

2. SYNC模式

在SYNC模式下,SpiltFS在POSIX的基础上保证了操作的同步性:任何文件操作都不需要通过显式调用fsync()即可保证其已被持久化。但是在该模式下,操作同样不是原子的,例如:数据写一半掉电。

3. STRICT模式

在该模式下,SplitFS在SYNC的基础上保证了操作的原子性。然而,该模式不能保证跨系统调用的原子性,例如,不能保证同时更新两个文件的原子性。

3.3 系统概述 (Overview)

下表总结了SpiltFS使用的技术和其达到的目的:

在这里插入图片描述

1. Split architecture:

SplitFS架构如下图所示:

在这里插入图片描述

包含两个主要部分:

  • U-Split: 链接到不同应用的用户态文件系统库
  • K-Split: 一个内核文件系统

其中,SplitFS在用户态执行数据操作(例如,read()write()等),而将元数据操作(例如,fsync()open()等)导向下层内核文件系统。这种设计方法类似Exokernel,其中所有控制操作都由内核处理,数据操作都在用户态处理。

2. Collection of mmaps

覆盖写通过mmap()一个文件的2MB来实现,且读通过调用memcpy(),写通过调用non-temporal store。值得说明的是,一个逻辑文件的数据可能存储在多个物理文件内(被mmap()的文件)。例如,SplitFS会将对某文件的Append数据先发送到staging file内(后面讲到staging file,其实就是类似一个缓冲区),这样一来,该文件的数据就至少存在于原文件与staging file内了。

SplitFS通过Collection of mmaps来处理这种情况,即,为每个文件关联一系列对不同物理文件的mmap()集合,此后,覆盖写便能够正确地被引导到不同的mmap()区域内了。

3. Staging

SplitFS利用临时的staging file来处理append操作和原子地数据操作。对某文件的Append操作会先被引导到一个staging file,然后被批量relink到该文件内(后面讲到什么是relink)。类似的,在STRICT模式下文件的覆盖写(要保证原子性)也会先被发送到staging file中,然后再被relink到相应的文件内。

4. Relink

Relink操作由fsync()触发,如前文所述,在staging file中的append操作和覆盖写操作数据会被relink到相应的文件中。

为了实现这个目标,一种很简单的方案是为被relink的目标文件分配新的数据块,然后将数据拷贝至这些数据块中。然而,这种方式带来额外的数据拷贝,造成了极大的写放大。Relink优雅地解决了这个问题。如下图所示

在这里插入图片描述

对上图进行简单解释:

  1. 每个staging file会预分配块(这些块是被mmap()出来的);
  2. 对目标文件进行append操作后,数据会被写入到staging file中,后续对该数据块的读也会被映射到该区域中;
  3. 调用fsync()后,relinkstaging file中的数据块逻辑链接到目标文件中,不修改物理块与mmap()

5. Optimized logging

在STRCIT模式下,SplitFS利用Operation Logredo logging的方式保证操作的原子性。每个U-Split都有一个独占的、预分配的(利用mmap())的Operation Log,对Operation Log的写入利用non-temporal store (nt-store)进行。同时,SplitFS还利用fence指令来保证Operation Log中记录项的顺序性。

为了减少Logging带来的开销,对于每个常见的操作(例如:write()open()等),SplitFS向Operation Log中写入一个cache line (64B)的数据,并且使用单个fence指令。需要说明的是,Operation Log不包含数据,只包含指向这些数据的指针。

此外,作者对Operation Log做出如下优化:

  • 减少fence。计算checksum保证日志项的有效与否,避免了二次fence

    传统在PM上写日志项的流程是:先写日志项内容,然后fence、flush;然后标记日志项有效,然后fence、flush;使用checksum后,可以写checksum+日志项内容,然后fence、flush,此时,不完整的写入可以在后续进行checksum校验的时候被检查出来。

    这里需要考虑的是checksum计算的速度。

  • 高并发。日志的尾部在内存中被原子更新,支持高并发写日志。

  • 清零与重放。初始化SplitFS时,将Log清零。在崩溃恢复过程中,通过检查非0的日志项的checksum来判断日志项是否有效。当Log满后,SplitFS为当前应用的所有已打开文件调用relink,即,清空staging file。接下来便可以将Log清零,然后复用。

6. 原子操作

SplitFS的原子操作结合了staging filerelinkoptimized log。比如,append操作数据先写到staging file,然后在optimized log中记录该操作以及数据的位置。在fsync()的过程中,staging file中的数据将通过relink操作被原子地追加到目标文件中。

3.4 读、覆盖写、追加写操作

1. 读

读操作会在Collection of mmaps中查询对应offset的最新数据(因为数据可能在staging file中,或是被覆盖写,或是被append)

如果对应的数据块没有被mmap(),那么该offset周围2MB的数据区域将会被映射上来,并且被添加到Collection of mmaps中,然后再被读取。

2. 覆盖写

与读操作类似,如果offset已经被映射了,那么覆盖写就直接改映射后的区域;否则mmap()上来再改。

在STRICT模式下,SplitFS会先将数据写入staging file中,然后将覆盖写操作用Operation Log记录下来,最后在fsync()或是close()的时候relink

3. 追加写

所有追加写操作都先被写入staging file中,然后在fsync()或是close()的时候relink。同样,在STRICT模式下,SplitFS会把append操作记录到Opeartion Log中。

3.5 系统实现 (Implementation)

  • 系统调用截取:利用LD_PRELOAD截获诸如open()write()等系统调用即可
  • Relink:通过ioctl实现relink操作。relink操作会释放目标文件(被link的文件)的被替换的数据块。在实现过程中,relink不会进行任何拷贝、持久化操作,只会对文件的源数据进行修改。同时,需要保证relink后的mmap()区域仍然存在,这样能够有效避免page fault
  • 文件的open()close():在open()一个文件的时候,SplitFS将该文件的属性缓存在内存中,以便于后续调用。此后,当文件close()后,SplitFS也不会释放该缓存信息。当文件被删除后,该信息才被清除。同样,如果文件被mmap()了,当且仅当文件被删除时才会unmmap()该文件。这些被缓存的信息被U-Split用于快速检查文件的状态。
  • Fork:当fork()被调用后,SplitFS会同时被拷贝到新进程的地址空间中,这样一来子进程也可以访问SplitFS。
  • Execveexecve()会覆盖当前进程的地址空间,但是已经被打开的文件描述符(open file descriptors)应该能被继续使用。为了处理这种情况,SplitFS在调用execve之前将在内存中有关打开的文件描述符信息先拷贝到/dev/shm/pid中,pid是当前进程的进程号。执行完execve()后,SplitFS检查/dev/shm/pid,并且把相应的信息拷贝到当前进程中。
  • Dup:当文件描述符被复制后,文件的偏移量和文件状态标志将被共享。为了处理这种情况,SplitFS为每个打开的文件维护一个offset,并且使用指针指向该文件。如此一来,如果两个线程都dup()了同一个文件描述符,那么offset的修改对这两个线程都可见(通过指针)。
  • Staging files:SplitFS会在初始化时预先分配staging file,它会创建10个160MB的文件。当一个staging file被使用殆尽后,一个后台线程会被唤醒,并预先创建、分配新的staging file,这避免了在关键路径上创建staging file 的开销。
  • memory-mappings缓存:SplitFS将所有mmap()缓存起来,放到Collection of memory mappings中,一个mmap区域当前仅当unlink时才被unmmap。这减少了在关键路径上的创建mmap()开销。
  • 多线程访问:SplitFS通过一个无锁队列来管理staging file。它还利用细粒度读写锁来保护内存中关于已打开文件、inodes和mmap区域的信息。

3.6 可调节参数 (Tunable parameters)

  • mmap()大小:目前的实现中,SplitFS支持mmap()的大小为2MB到512MB。默认选项是2MB。
  • staging file在初始时的数量:默认情况下是10个。因为实验表明初始化10个staging file在性能、初始化开销以及空间利用间提供了较好的平衡。
  • Operation Log的大小:默认128MB,支持2M个操作。意味着尾延迟可能较小。

3.7 安全保证 (Security)

相对于内核文件系统,SplitFS并没有带来额外的问题,因为:

  • 所有元数据操作都在内核执行
  • 只允许有权限的用户访问
  • 各U-Split间隔离

4. 评估 (Evaluation)

本节通过评估SplitFS、PMFS、EXT4 DAX、NOVA,回答以下6个问题

  • SplitFS的各系统调用性能和EXT4 DAX比何如(因为SplitFS是基于EXT4 DAX实现的)?
  • SplitFS各项技术对最终性能的贡献?
  • SplitFS相比其他文件系统在不同的访问模式下何如?
  • SplitFS相较于其他PM文件系统来说有减少软件开销吗?
  • SplitFS在真实场景下如何?
  • SplitFS的计算和存储开销是怎样的?

4.1 实验环境

  • Optane DCPMM:768GB
  • DRAM:375GB
  • LLC:32MB
  • OS:Ubuntu 16.04
  • Kernel:Linux 4.13

4.2 测试负载

在这里插入图片描述

下面简单介绍一下各负载情况:

  • TPC-C on SQLite:TPC-C是一个在线事务处理的基准测试负载。它有5种包含不同比例的读写的事务种类;
  • YCSB on LevelDB:YCSB是个键值存储基准测试负载;
  • Redis:Redis会将更新记录到一个日志文件中,然后每秒对该文件调用fsync()
  • Git、Tar、Rsync
    • Git:对Linux Kernel运行git addgit commit十次
    • Tar:压缩Linux Kernel 4.18
    • Rsync:将7GB的数据集从PM的一个位置拷贝到另一个位置

4.3 正确性与恢复测试

  • 正确性测试:对比运行SplitFS后和运行EXT4 DAX后文件系统的状态,发现一致;
  • 恢复时间
    • 在POSIX和SYNC模式下,SplitFS依赖EXT4 DAX便可恢复;
    • 在STRYICT模式下,SplitFS的恢复时间依赖于日志中的日志项多少。测试结果表明,最坏情况下的恢复时间为6s;

4.4 SplitFS的系统调用开销

为了说明以开销较大的元数据操作换取快速数据操作是一个很好的trade-off,设计了一个类似Filebench中Varmail的负载进行测试:
在这里插入图片描述

得出如下三点结论:

  • SplitFS的数据操作远快于EXT4 DAX
  • SplitFS的元数据操作略慢于EXT4 DAX,因为SplitFS还要构建一些自己的结构
  • 随着语义的增强,开销越来越大

总的来说,SplitFS达到了最初的设计目标。

4.5 SplitFS性能分解测试

这里设计了顺序4K覆盖写与追加写两个实验,fsync()在每次操作后都会被调用,实验结果如下:

在这里插入图片描述

  • 顺序覆盖写:性能是EXT4 DAX的两倍,因为SplitFS直接通过nt-store进行数据写入;
  • 顺序追加写入:仅使用staging file来buffer数据性能提高了2倍,这是因为在fsync()时存在大量数据拷贝;加入relink后,避免了这些数据拷贝,因此性能变为EXT4 DAX的5倍;

4.6 不同I/O模式下性能

测试顺序读、随机读、顺序写、随机写、追加写,单次I/O 4KB,共128MB。测试结果如下图所示

在这里插入图片描述

在所有模式下,SplitFS均有不同程度的提升,其中:

  • 在POSIX和SYNC模式下append提升最显著,因为避免了陷入内核,所有写操作均在用户态完成。
  • 在STRICT模式下,SplitFS的性能有了下降,这是因为日志机制。但其仍然快于NOVA。

4.7 减少软件开销

定义软件开销时间=一次系统调用的时间-访问PM设备的时间。下图展示了SplitFS在写繁重负载下的软件开销:

请添加图片描述

这里简要说明上图的负载:

  • Load A与Run A:LevelDB上运行YCSB的Load A和Run A
  • TPCC:SQLite上运行TPCC

不用多说,SplitFS怎么都是最少的。

4.8 数据繁重型负载上的性能

和NOVA、EXT4 DAX、PMFS相比,在YCSB、TPCC等工作负载上均有提升。

请添加图片描述

与Strata相比,SplitFS也均有不小的提升:

请添加图片描述

4.9 元数据繁重型负载上的性能

GIT、TAR、RSYNC是元数据繁重型负载,如图6所示,SplitFS在这些负载的性能上没有太大的提升,反而有所下降,最大性能下降为13%。这是因为SplitFS在元数据操作方面要构建、维护额外的结构。

4.10 资源开销

SplitFS要消耗内存资源来维护文件相关的元数据,例如追踪已打开的文件描述符、staging file等,这里对SplitFS的内存消耗和CPU利用率进行了评估:

  • 内存开销:最多只使用100MB,STRICT模式下多用40MB;
  • CPU利用率:后台线程占用一个物理线程,有时会占用100%的CPU;

总结

SplitFS最大的贡献在于提出了一种新型文件系统架构:利用mmap()构建从用户态到内核态的关联,并能够在用户态对数据进行操作,从而大大提升了文件系统数据操作的性能。

此外,SplitFS的多一致性模式设计(允许不同应用同时运行不同的一致性模式)也为我们带来了不小的启发。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值