NOVA: A Log-structured File System for Hybrid Volatile/Non-volatile Main Memories

这篇论文主要讲述了在Volatile/Non-volatile Main Memories的混合架构上如何去构建一个Log-structured文件系统,即NOVA(NOn- Volatile memory Accelerated log-structured file system)。与传统的LFS文件系统不同的是,NOVA的log是基于Inode的,并将其保存在NVM设备上,每个Inode都有自己独立的一份log,避免了并发访问时的锁争用,充分发挥了CPU的多核性能,NOVA的log中并不会记录数据,这保证了每个inode的log都足够的小,减少了GC时带来的开销。NOVA在其log中提供了对meta,data以及mmap访问的原子性,专注于简单可靠,但同时也在DRAM中维护了复杂的数据结构来加速查找。

本篇博客主要通过三个方面来展开对NOVA这篇论文的剖析:

  1. NOVA面临的问题有哪些?
  2. NOVA是如何解决这些问题的?
  3. 总结&自己的一些思考

NOVA面临的问题有哪些?

NOVA是架构在NVM设备上的一个文件系统,在新的NVM设备上要解决的问题大致分为3类,具体如下:

  1. 发挥硬件性能

    在性能方面,NVM设备提供了低时延的访问,降低了硬件和软件间的trade-off ,但当前的存储系统都是构建在SATA或SSD等较慢的存储设备上,因此对软件自身的效率要求并不是非常敏感。在NVM设备上,现有的软件架构会极大的浪费NVM设备所提供的性能,因此亟待解决的是如何提升软件自身的效率。

    现有的基于NVM的文件系统大多都使用了Direct Access (DAX)或eXecute In Place (XIP)的方式直接访问NVM设备的,绕过DRAM的page cache ,避免了DRAM和NVM设备间的额外拷贝带来的开销。

  2. 写入重排序和一致性问题

    在现代多核CPU中,CPU及其缓存的层次结构可能会通过重新排序存储操作来提升性能,虽然CPU的一致性协议保证了数据在内存中的更新顺序,但并不能保证更新何时才能到达NVM设备,因此当出现掉电等系统crash情况时,很可能会导致NVM设备上的数据出现不一致状态。

    NVM设备可以通过显示flush缓存和使用内存屏障的方式来强制执行写入顺序,以避免出现上面所提到的情况。X86架构提供了clflush来flush CPU缓存,但clflush的执行必须时严格有序的并且包含不必要的使缓存无效的操作,这使得CPU的性能损失巨大,此外clflush仅仅将数据发送到存储控制器,并不保证数据一定会到达NVM设备。而Intel 所提供的mfence指令构造的内存屏障也只能保证在屏障前和屏障后的内存操作有序,并不会对NVM设备的数据回写操作顺序施加任何的限制。

    Intel提出了新的指令来解决这些问题,比如:clflushopt(更加高效的clflush),clwb(明确的写回CPU缓存,并不使其失效)和PCOMMIT(强制将数据刷写到NVM设备中),在NOVA的设计当中,使用了新的指令集来保证数据的一致性。

  3. 原子性问题

    POSIX风格的文件系统语义要求许多操作必须时原子的,即“要么全有,要么全无”,例如:当在POSIX中一个rename操作失败了,此时不能出去修改或创建旧名称文件,也不能创建或修改一个新名称的文件,且必须把状态回滚至未做任何操作之前的状态。rename操作是只会修改文件系统的元数据的一种操作,但有很多操作需要同时保证元数据和文件数据的原子性,比如在对一个文件进行追加操作时,需要同时原子的修改文件的长度和修改时间,许多应用程序依赖文件系统的的原子性作来保证自己的正确性。

    存储设备通常只提供非常基础的原子性保证,比如磁盘只提供扇区的原子写入,CPU只保证8-byte的对齐是原子的,对于一个文件系统的原子性构建,需要更加复杂的技术实现来保证其原子性。

NOVA是如何解决这些问题的?

先总结一下NOVA是如何解决上面提出的三个问题,并且按照解决问题的思路,我们再来看看NOVA的设计以及具体的实现过程。

  1. 发挥硬件性能
    • NOVA是一个基于log-structured来设计的文件系统,LFS架构更容易实现原子性
    • 每个Inode 具有一个独立的log,具备更好的并发性
    • 把Inode的数据结构拆分为DRAM中的radix tree和NVM设备上的Inode信息,加速了检索效率
  2. 写入重排序和一致性问题
    • 使用新指令集clflushopt,clwb和PCOMMIT,来访问NVM设备
    • 在对于不支持新指令集的机器,采用movntq非时序移动指令,配合clflush和mfence来实现写入有序
  3. 原子性问题
    • 对单个目录和文件的访问都记录log,且在确保更新生效之后,才更新Inode log的tail指针
    • 通过轻量级的Journal来保证跨Inode操作的原子性
    • 在写入操作时,通过COW的机制来保证数据修改的原子性
    • 使用Atomic-mmap来实现原子的mmap

接下来我们看看NOVA的整个设计。

NOVA概述

NVM设备带来了很多新的特性,对NOVA的设计也产生也较大的影响,NOVA是一个基于log-structured,兼容POSIX协议的文件系统,NOVA建立在LFS的优势之上,并更加适应于Volatile/Non-volatile Main Memories的混合架构,NOVA于传统的为最大化磁盘带宽而构建的LFS文件系统有很大的区别,作者从三个方面讨论了NOVA的设计初衷:

  1. 在NVM设备上,支持原子更新的log是很容易实现的,但这样的数据结构对于查询操作(文件中的目录查找和随机访问)的效率并不高,相反支持快速查询的数据结构,比如树很难在NVM中正确有效的实现。
  2. 传统LFS文件系统中,清理log的复杂性主要源自于log的存储需要提供连续的存储区域,但NVM提供了快速的随机访问能力,因此需要连续的存储空间来保存log。
  3. 在传统的LFS文件系统中,只包含一个log文件是有意义的,所有的log记录都在这个文件中,这样做限制了系统的并发性,整个文件系统的写入都受限于log文件的写入速率。由于NVM支持快速、高并发的随机访问,因此可以考虑使用多个log文件来提升系统的并发能力,而且也并不会带来过多的开销。

基于以上考虑,作者在NOVA中做出了以下设计决策:

  1. 将log保存在NVM设备中,并且在DRAM构建索引。NOVA在NVM设备上保存log和文件数据,并且在DRAM中构建radix tree来加速检索和查询操作,radix tree的叶子结点为log中的一条记录,而在这条记录中会保存指向实际数据的指针。

  2. 给每一个inode独立的一个log。在NOVA中,每个Inode都有自己的一份log,允许多个文件进行并发更新且不需要同步操作。独立log的方式使得NOVA在访问文件和recovery阶段具有更高的并发能力,在recovery阶段可以同时recovery多个log文件。NOVA还保证在log文件中保存的有效log条目尽可能的少(周期的清除无效log),这样使得扫描log的速度也变得很快。

  3. 通过记录日志和轻量级的journaling保证复杂的原子更新操作。相较于journaling和shadow paging文件系统,NOVA作为LFS文件系统对于原子更新的实现是非常容易的。NOVA是如何原子的写入log文件的呢?首先,将log数据追加到到文件中;然后,更新log的尾指针以提交此次修改。这样避免了像journaling中的数据写入,也避免了像shadow paging中的迭代更新问题。

    一些涉及文件目录的操作,比如在多个目录间移动文件,会跨越多个Inode文件,NOVA会采用journaling来原子的更新这些inode的log。NOVA首先在每个Inode log的末尾写入数据,然后将这些log的尾指针更新记录在journaling中。NOVA的journaling是非常轻量级的,因为它只记录了Inode log的尾指针更新,而且在POSIX文件系统中,一次操作涉及到的Inode最多也不会超过4个。

  4. 将log实现为一个单向链表的形式。在NVM设备上,因为随机访问能力的大大提升,顺序访问带来的优势将不再有那么多。因此NOVA在设计log时,采用了链表的模式来保存log,他将每个4KB的NVM page作为链表的一个元素,并且在每个page中保存指向下一页的指针。

    使用这样的非顺序的log存储模式有三个好处,第一,在NVM设备上分配空间更加容易,因为不需要为log分配一块非常大的连续的空间;第二,NOVA可以有更细粒度的log清理,以page为单位;第三,对于那些log所包含log以及全部失效的page,回收它们只需要移动几个指针。

  5. 不在log中记录真正的数据。在NOVA中,Inode 的log中是不包含任何文件数据的,NOVA采用了COW的机制去修改数据页,并且将此次写入有关的元数据记录在inode log中,元数据表述了此次更新操作指向的数据页。

    使用COW修改数据页数据页具有以下好处,第一,它会缩短日志的大小,加快recovery的过程;第二,它使得垃圾回收更加简单高效,因为NOVA不需要通过从日志中复制文件数据来回收日志页;第三,回收无效页面和分配新的数据页都很容易,因为只需要从DRAM的空闲列表中添加和删除页即可;第四,NOVA可以即时回收过期的数据页,在大量写入负载和NVM设备容量较满的情况下,其性能依然能够保持稳定。

NOVA实现

NOVA基于上述在概述中的讨论,在本节中将详细阐述一下NOVA是如何实现这些功能的,分别如何从整个文件系统布局及其原子性和写入顺序机制,NOVA如何原子的访问目录、文件和mmap操作,以及NOVA中的垃圾回收机制,recovery和内存保护机制。

NVM的数据结构和空间管理

NOVA的数据结构和对NVM设备区域的划分如上图所示,NOVA将NVM设备划分为了四个部分:Super block和Recovery inode、Inode tables、Journals、Log/Data page。Super block中保存了文件系统的元信息,Recovery Inode中保存了Recovery信息,这些信息会在NOVA正常关闭重启之后加速Recovery过程,Inode tables中的每个Inode table都包含一组inode,Journal为目录操作保证了原子性,剩余的区域都是Log/Data page,这些page区域用于存储实际的log数据和文件数据,每个page的大小为4KB。NOVA在每个CPU上维护了一个Inode table、journal和NVM空闲page列表,避免了CPU之间的锁争用。

看完了NOVA的数据结构图,接下来我们来详细了解一下各个数据结构的构造,以及探讨一下它在NOVA中发挥的作用。

Inode table

NOVA的Inode大小为128byte,每个Inode table的大小为2MB,每个Inode都有一个独立的编号,标识其在Inode table中的位置,便于查找。NOVA在分配Inode的时候以轮询的模式将其分配到每一个Inode table,使得每个Inode table的Inode数量尽可能的均匀。当一个Inode table写满之后,会重新申请一个新的2M的Inode table并以链表的模式链接在旧的Inode table之后。在每一个Inode上都会包含一个bit位来标识该Inode是否处于有效状态,当创建文件或目录的时候,NOVA会重复使用那些已经标记为无效的Inode,以控制Inode table的大小总量。每个CPU分配一个独立的Inode table,能够避免Inode 分配时的争用,并且在Recovery阶段能并行加载。

在NOVA中,每个Inode都会有自己独立的一个log,Inode log实际是一个单向链表,链表的每个元素为大小是4K的page,每个Inode中会保存两个指针,分别是指向Inode log的head和tail指针,tail指针总是指向最新提交的log条目。在Recovery阶段,可以通过扫描head到tail阶段的log来重建Inode。

Journal

在NOVA中,Journal主要的作用是用来保证跨多个Inode访问的操作是原子的,Journal是一个4KB大小的circular buffer,NOVA通过一个指针对<enqueue, dequeue>来管理每一个Journal。

当发生跨多个Inode的操作时,NOVA首先在此次受影响的Inode log中添加对应的log记录;然后启动一个类似于事务的操作,把每个受影响的Inode log的当前tail指针追加到每个CPU对应的Journal,更新其enqueue指针为tail指针;最后,在所有受影响的Inode log写入完成,且更新tail指针后,NOVA更新dequeue的指针等于enqueue并完成此次事务提交。

用一个创建文件的例子来说明这个过程,首先会在这个文件的父目录的Inode log中增加一条创建文件的记录,并且将这个文件的Inode的标识位置为有效,然后在journal中记录父目录Inode log的tail以及新的Inode的标识位,当出现系统掉电,在故障Recovery时,NOVA会检查每个Journal,并且回滚介于dequeue和enqueue间的所有更新操作。NOVA在CPU的每个核上只允许一次打开一个事务,不同的CPU间允许事务并发执行,对于每个目录操作,操作系统内核的VFS会锁住所有的相关Inode,因此也不用去担心并发的事务会去修改相同的inode。

NOVA的Journal是非常小,在Journal中,每个Inode记录只占用16byte,Inode log的tail指针和指向实际数据地址的指针,每个指针只占用8byte,而在POSIX中最复杂的rename操作也只涉及到4个Inode的操作,因此Journal的最大大小也不会超过64byte。

NVM 空间管理

为了能够使得NVM的地址分配更加的快速,NOVA将NVM划归成了多个pool,每个CPU会独占一个保存NVM空闲页的pool,并在DRAM中维护当前CPU所占有pool的空闲页列表,当CPU本身所持有的pool中的page已经耗尽,NOVA会从全局的pool中分配新的page给它,分配时会对全局的pool加锁,这种分配策略类似于操作系统的扩展内存分配器。

NOVA为了提升重分配的效率和使得每个CPU的pool的大小更紧凑,在DRAM中使用红黑树来保存空闲page列表,并根据page的地址排序,使得page页的回收能以log n的时间复杂度来完成。

为了提升性能,NOVA并不会把NVM分配器的状态记录在NVM中,在系统正常关闭时,NOVA会将NVM分配器的状态记录在Recovery inode中,并在重启之后直接读取恢复,当发生异常系统掉电等crash时,NOVA需要扫描所有的Inode log来回复NVM分配器的状态。

NOVA的Inode log page在一开始只包含一个page页,当这个页写满后,为了避免频繁的log page页申请,NOVA会分配是原来log page页大小两倍新log page,当Inode log的大小超过一定的阈值,NOVA每次会分配固定数量的page给它。

原子性和写入顺序一致性

NOVA提供元数据、数据和mmap的原子操作,通过使用Inode log和Journal两种技术手段来实现,其中包括了以下机制:

  • 通过使用CPU所提供的对DRAM的64-bit原子更新功能,来同样访问NVM设备,并且通过该机制保证了在提交Inode log操作,即更新Inode log tail指针时的原子性。

  • 每个Inode log只记录该Inode自己的log,这对于write、msync和chmod操作的原子性得到了保证。
  • 通过Journal保证了跨多个Inode操作的原子性,比如rename、create等等

为了写入顺序一致性问题,NOVA通过使用现有CPU提供的指令集,以及Intel为了支持NVM设备所提供的新指令集,保证了对数据的写入顺序一致性的保证,NOVA通过三个机制来保证:

  • 在更新Inode log的tail指针前,必须要先把日志记录在Inode log中,即写入NVM设备

  • 在进行跨多个Inode操作时,先要保证Journal中的数据已经写入NVM设备,然后才能提交事务

  • 在通过cow机制更新数据时,必须先提交新修改的data page,然后再回收过期的data page

new_tail = append_to_log(inode->tail, entry); 
// writes back the log entry cachelines 
clwb(inode->tail, entry->length);
sfence(); 			// orders subsequent PCOMMIT
PCOMMIT();			// commits entry to NVMM
sfence();			// orders subsequent stor
inode->tail = new_tail;

如上代码所示,NOVA通过clflushopt、clwb、PCOMMIT和sfence等指令集来实现对Inode log写入的原子性,如果平台不支持上述新指令,NOVA采用movntq非时序移动指令绕过CPU高速缓存层以便直接写NVMM,并且结合clflush和sfence确保写顺序性。

目录操作

在探讨NOVA的目录操作之前,我们先看看NOVA是如何设计目录在DRAM和NVM设备上的数据结构的。

NOVA的目录分为两个部分,DRAM中的radix treeNVM设备上的目录Inode log信息

DRAM数据结构 Directory radix tree

每个目录都会在内存中维护一棵radix tree,其中的key为每个目录或文件名的哈希值,radix tree的叶子结点指向其对应Inode在NVM中的Inode log,radix tree极大加快了NOVA的目录查询效率。

NVM数据结构Directory Inode log

NOVA目录的Inode log中包含两种不同的log记录,目录记录(dentry)Inode更新记录

目录的dentry里面记录了该目录下所有的子文件和子目录,和它们的Inode编号,以及timestamp(NOVA通过该timestamp来原子的更新Inode的ctime和mtime)。当在该目录下创建、删除和重命名文件或子目录时,会在Inode log中增加dentry记录。当在该目录下删除文件或子目录时,还需要将对应的Inode的编号置为0,以便于和其他的Inode相区分。

目录的Inode更新记录里面记录了该目录的Inode更新操作,比如对该目录执行chmod或chown操作等等,这些操作会原子的修改Inode的一些属性值。

上图中展示了NOVA如何在一个目录下去创建和删除一个文件,我们结合数据结构本身和图,一起来叙述一下这两个过程。我们从从dir目录的Inode log来看起:

  1. 一开始在dir这个目录下创建了两个文件foo和bar,创建了两条创建文件的dentry,其Inode编号分别是10和20
  2. 随后又对dir目录执行了chmod操作,即追加了一条Inode更新记录
  3. 此时Inode的log大小超过了4K,因此去新获取了一个log page,并将其以链表的模式追加到上一个写满的log page的next指针处
  4. 此时发生了删除foo文件的操作,NOVA在dir的Inode log中追加了一条删除文件的dentry,并且将foo文件的Inode的编号置为了0

现在dir目录下的文件只有一个bar,其Inode编号为20。

创建新文件zoo

此时又发起了在dir目录下创建一个zoo文件的操作,经历了三个步骤,具体如下:

  1. 首先,NOVA为zoo文件在当前Inode table中找到一个没有使用的Inode,假如说刚好找到了foo文件已经被标记为删除的那个Inode,此时zoo的Inode编号为10,并追加一条新的创建文件的dentry至dir目录的Inode log中
  2. 然后,NOVA通过当前CPU的Journal来保证原子的更新dir目录的Inode log的tail指针和设置新创建文件zoo的Inode的标志位为有效状态
  3. 最后,NOVA将新创建的文件zoo加入到dir目录在内存中的radix tree的叶子结点中,并且将其指向dir的Inode log

删除文件foo

在Linux系统中,删除一个文件一般需要两个更新操作,首先需要将该文件Inode的引用计数减1,然后将文件从目录中删除。在NOVA中,删除文件需要三个步骤,具体如下:

  1. 首先,在dir目录的Inode log中增加一条删除文件foo的dentry,并且在foo文件的Inode log中增加一条Inode更新记录
  2. 然后,NOVA通过当前CPU的Journal来原子的更新dir目录Inode log和foo文件Inode log的tail指针
  3. 最后,将foo文件从dir目录在内存中的radix tree中移除

文件原子操作

同样,在探讨NOVA的文件操作之前,我们先看看NOVA是如何设计文件在DRAM和NVM设备上的数据结构的。NOVA中的文件也分为两个部分,DRAM中的radix treeNVM设备上数据

DRAM中的File radix tree

NOVA在DRAM中维护文件的radix tree,其叶子结点中保存了文件Inode在Inode log中的偏移量。

NVM设备上数据结构File Inode log和Data page

File Inode log中记录了文件元数据的更改,在Inode中,包含两种日志记录:Inode更新记录文件写入记录(File write entries)

文件的的Inode更新记录和目录的Inode 更新记录功能是一致的,里面记录了该目录的Inode更新操作,比如对该文件执行chmod或chown操作等等,这些操作会原子的修改Inode的一些属性值。

文件写入记录用于描述文件写入操作,并指向写入已修改的数据页面,其中还包括时间戳和文件大小,以便写入操作以原子方式更新文件的元数据,NOVA把在文件写入记录中的文件偏移映射记录到DRAM的文件radix tree中。

Data page用于实际存储文件数据的单元,大小为固定的4KB,Inode log中会保存指向实际Data page的指针。

如上图所示,展示了如何在一个文件中写入数据。符号<file pgoff,num pages>表示page偏移量和写入影响的page数。从文件的Inode log中来看,前两个条目分别描述了两个写入操作,<0,1>和<1,2>,大小分别为4 KB和8 KB,实际占用了3个data page,Data0、Data1和Data2,此时正在进行第三个写入操作,大小为8 KB,其写入的data page和影响的page数为<2,2>,实际需要占用2个data page,分别是已经被使用的Data2和新的Data3,此时写入这份数据的步骤分为以下5步:

  1. NOVA首先以COW的模式Copy一份Data2的数据,并且从空闲列表中获取新的data page Data3,然后将数据写入Data2和Data3;
  2. 然后将<2,2>这条文件写入记录追加到文件的inode log中;
  3. 接下来NOVA原子地更新日志尾部以提交写入;
  4. 更新DRAM中的radix tree,以便偏移“2”指向新的Inode log写入记录;
  5. NOVA将旧的Data2的页面返回空闲列表。

在写入操作期间,每个inode通过锁来保护Inode log和radix tree,保证并发安全。当写入操作给用户做返回时,所有的更新操作都已经持久化在NVM设备中。 如果写入的数据量很大,NOVA可能无法在这个文件的Inode log中使用单个写入记录对其进行记录,如果NOVA找不到足够大的连续page,它会将这次写入操作拆分成多个写入记录,并将它们全部追加到到文件的Inode log中。

对于读取操作,NOVA会使用64位原子写入操作更新文件Inode的atime,通过使用DRAM中的文件radix tree定位所需的Inode log,再从log中找到对应的Data page,读取数据并返回至用户缓存中。

原子mmap

在讨论NOVA是如何实现原子的mmap操作时,我们先看看现有技术是如何通过mmap将NVM设备直接映射到用户态空间,绕过DRAM的page cache ,避免了DRAM和NVM设备间的额外拷贝的。

当前使用的最多的方式莫属于Direct Access (DAX)了,DAX文件系统通过将NVM的page页直接映射到用户空间的模式来对NVM完成读写操作,但DAX目前只能通过CPU提供的64-bit原子写入、fences和cache flush指令集来保证其访问NVM设备的原子性,作者认为使用这些指令来构建NVM设备上的数据结构非常困难,而且也有可能会限制NVM设备的mmap的性能(此处表示各种不理解…….)。

所以在NOVA中,提供了一种叫做atomic-mmap 的方式,具备较强的一致性。当应用程序使用atomic-mmap将文件映射到其地址空间时,NOVA会通过COW的模式从NVM拷贝一份副本page,并将文件数据复制到副本page,然后将副本映射到用户的地址空间。应用程序在访问的是副本page,当调用msync时,NOVA将执行上一步所说的文件写入请求过程,使用movntq操作将数据从副本页面直接复制到数据页面,并以原子方式提交更改。由于NOVA使用copy-on-write进行文件数据并立即回收陈旧数据页,因此NOV并A不支持DAX-mmap。

Atomic-mmap比DAX-mmap具有更高的开销,但提供更强的一致性保证。普通的DRAM mmap不是原子的,因为操作系统可能会将脏页的一部分写回文件系统,在系统出现故障时会使文件数据不一致,NOVA可以通过防止操作系统刷新脏页来支持DRAM中的原子mmap,这部分工作作者会在未来继续做实现。。。。

垃圾回收

NOVA垃圾回收仅仅局限于Inode log的回收,因为实际的data page都是还可以继续复用的,只有Inode log是以单向链表的模式存储在NVM上的,每个链表元素中的所有log记录都失效时,需要对这个log page进行回收,满足以下条件时,都可以认为这条Inode log记录是失效的:

  • 如果文件Inode log的写入记录未引用有效data page,则该文件写入记录已失效
  • 如果后续的的inode更新记录修改了同一个元数据,比如连续的chmod操作,前一条inode更新记录则为无效
  • 如果Inode的标识已经为无效,则针对这个目录的dentry更新记录已全部失效

NOVA提供了两种机制来回收Inode log,分别是Fast GCThorough GC

Fast GC

Fast GC不要求任何的拷贝,如果在一个日志页中的所有记录过时,那么Fast GC通过从日志链表中删除该页来回收日志空间,上图中(a)展示一个快速日志垃圾回收的例子。

Thorough GC

在Fast GC日志扫描期间,NOVA记录每个日志空间存活日志记录所占的空间比例。如果存活的记录所占的空间少于日志空间的50%,NOVA会在Fast GC结束后,再执行一次Thorough GC来将多个存活的日志记录拷贝到一个新的log page中,并且更新DRAM数据结构指向新的日志,然后将链表的next指针重新指向新的这个log page,最后回收旧日志。具体如上图(b)所示。

Recovery

当NOVA重新mount到文件系统时,它需要重构DRAM的数据结构。由于应用程序可能仅访问文件系统的一部分Inode,所以NOVA采用一个称为延迟重建(lazy rebuild)的策略以减少Recovery的时间。它推迟重建radix树和Inode,直到文件系统第一次访问到这个Inode时才去重建,这个策略加速了恢复过程并且减少了DRAM的消耗。

正常关机的恢复 对于正常关机操作,NOVA将NVMM页分配状态保存到recovery inode’s log中并且在接下来的remount操作中恢复该分配状态。这个过程中,NOVA不需要扫描任何的Inode日志,所以恢复过程很快。

故障恢复 在系统故障发生是,NOVA通过扫描Inode log来重建NVM分配器信息。NOVA的日志扫描过程很快的,原因有两点:

  • 每个CPU的索引结点表和每个Inode均有一个log允许日志恢复的巨大的并行性;
  • 是因为NOVA的日志仅包含元数据而不包含数据页,日志很短。

NOVA的失败恢复过程包括两步:

  1. NOVA检查每个journal并且回滚任何未提交的事务以便恢复文件系统到一致性状态。
  2. NOVA在每个CPU中运行一个恢复进程并且并行扫描Inode table,为Inode table中的每一个有效的Inode执行日志扫描。

总结&自己的一些思考

NOVA是一个基于log-structured的文件系统,与LFS文件系统不同的是,每个Inode都有自己独立的一份log,避免了并发访问时的锁争用,充分发挥了CPU的多核性能,NOVA的log中并不会记录真实的log数据,只是保留了指向log page的指针,这保证了每个inode的log都足够的小,减少了GC时带来的开销。NOVA在其log中提供了对meta,data以及mmap访问的原子性,专注于简单可靠,但同时也在DRAM中维护了目录和文件的radix tree来加速查找。对于跨多个Inode的操作,NOVA通过一个轻量级的Journa来保证原子性,并且设计了Atomic-mmap来保证了mmap的原子性,但NOVA并不支持DAX-mmap。

为什么NOVA要基于log-structured来设计?好处是什么?

NOVA是一个构建在NVM设备和DRAM混合架构上的文件系统,需要充分考虑如何去发挥NVM设备的性能,而且LFS文件系统相比journaling(WAL)和shadow paging(cow)来说更加容易实现原子性,journaling文件系统在写入数据时需要在log和文件中写入两次数据,这对于NVM设备并不友好。LFS存在的问题是,全局一个log需要占用连续的存储区域以利用磁盘的顺序读写性能来提升文件系统的性能,但这样在NVM上使用可能会造成更多的碎片空间,但在NVM设备上,随机访问性能相比顺序访问性能的差距已经非常小了,所以可以考虑把log拆分为链表的模式,减少碎片化操作。