简介
这是我阅读该文章后自己的总结记录,如有错误理解还劳烦指出,如果能帮到你,更是十分荣幸啦。
paper的原文地址: https://www.usenix.org/conference/fast18/presentation/won 可以下载原文与slides。
这篇文章是FAST18的best paper,文章的主要工作是在闪存上实现了一套barrier-enabled IO栈,这套IO栈最大的特点是能够提供order-preserving的存储,并且速度很快(速度与无序写入接近)。在完成IO栈的基础上,于系统接口层面提供了两个新的原语:fbarrier()与fdatabarrier(),在大部分情况下可以替换掉性能较低的fsync()与fdatasync(),能够很大程度上提升数据库、日志系统等需要按序写入存储器的应用的性能。
Motivation
首先现代的IO栈都是默认不提供按序写入存储器的,至于为什么呢,原因由来已久。比如HDD寻道开销是很大的,必须要有IO调度算法,像我们所熟知的电梯算法,明显会比FIFO效率高的多。但是在一些实际应用中,它们又需要保证写入存储器的有序性,比如数据库、CoW类型的文件系统、日志系统等。对于这些应用,传统的解决方案有Windows平台的fflush函数,Linux的fsync与fdatasync。这些方法被作者称为transfer-and-flush,即在数据传输完成之后强制flush缓冲区,这样做性能很差(等下细讲),而对于SSD,是没有寻道开销的,于是作者认为可以提出一套针对SSD的按序写入存储器,并且没有transfer&flush开销的IO栈,这也就是本文的工作。
Orders in IO stack
如何做到按序写入存储器呢?作者定义了四种write请求的顺序:
Order1:I
issue order:文件系统提出的一组写请求(即写请求进入IO scheduler的顺序)
Order2:D
dispatch order:分配给存储设备的一组写请求(即写请求离开IO scheduler的顺序)
Order3:X
transfer order:数据传输完成的顺序
Order4:P
persist order:数据持久化操作的顺序(即最终写入磁盘的顺序)
在定义完I、D、X、P这四种顺序后,作者对order-preserving进行了具体定义:保序并不是指所有请求的顺序都完全相同才能称之为order-preserving,而是对于两组请求顺序,如果对于指定的请求(即barrier),它们的相对位置得到保留,便可认为其是保序的(partial order is preserved),可以使用’='进行连接。
本文的目标就在于实现I=P,为此,作者指出:
I=P≡(I=D)(D=X)(X=P)
由于不相邻的请求顺序之间是不透明的,所以上式显然。
但在实际应用中由于各种原因,I=D、D=X、X=P都是无法满足的,所以作者的这篇文章就是自底向上地依次解决了X=P、D=X、I=D,最后实现了一套order-preserving的IO栈。
Transfer-and-Flush & Legacy Block Layer
T&F(即transfer-and-flush)是传统的对于按序写入的解决方案,这种方法性能很糟糕,但不得不使用它的根源在于X=P。我们都知道HDD是要寻道的,如果总是FIFO的话,磁盘很可能会花费很多时间用于来回寻道,为此HDD都有各自的、黑盒的寻道算法,能够节省许多寻道开销(当然代价是牺牲了order-preserving),所以X≠P已经成了几十年来的通用做法,由于最后一层无法保序,所以即使上层做了保序也是没有任何意义的。
为了保证两个请求如write(A),write(B)的顺序,我们可以采用如下方法:
write(fd,buf1,len1);//写请求1
fsync(fd);//barrier
write(fd,buf2,len2);//写请求2
在此fsync()便起到了一个barrier的作用,上面的代码在实际IO中流程大致如下:
- IO调度器发出写请求1
- 启动DMA,传输buf1的内容
- 将buf1的内容写入对应缓冲区
- fsync方法被调用,阻塞直到将所有缓冲区的内容被强制flush入存储器
- IO调度器发出写请求2
- 启动DMA,传输buf2的内容
- 将buf2的内容写入对应缓冲区
很明显:T&F的开销是很昂贵的(频繁的进程切换,等待DMA、flush的开销),这使得需要按序写入请求的IO性能非常糟糕。
Barrier-enabled Storage:X=P
作者为实现高性能的Barrier-enabled的存储器,首先将barrier命令与write命令合并,成为barrier-write命令。具体做法并非是添加一条SCSI命令,而是将一个SCSI指令的空闲位作为barrier位,如果是barrier-write,则将write命令的barrier标志位置1。
对于硬件如何实现barrier-write,需要分两种情况讨论:
-
对于具有掉电保护(PLP)的SSD,可以直接进行按序写入存储器,并且不需要担心是否会数据丢失
-
对于不具有PLP的SSD,可以采用多种方法来实现。在本文中,作者参考了Sprite LFS的按序恢复方法具体方法是定期设立检查点(periodic checkpoints)以及在崩溃时进行前滚(roll-forward)来保证数据一致性。
对于硬件层面的实现作者认为仅仅是一个工程方面的问题,如何实现并不重要,重要的是实现了X=P便有了在不使用flush的情况下persist order 的可能。
New request types
作者在实现Order Preserving Block Device Layer的过程主要分为三步,第一步就是设立新的请求类型,即将写请求划分为orderless、order-preserving、barrier-write三种。
SCSI任务优先级
在讲解作者的调度方法之前,首先介绍一下SCSI命令的优先级控制方法。
在SCSI命令参考手册中给出了三个优先级控制位:
HEADSUP,ORDWUP、SIMPSUP,分别对应的优先级是HEAD OF QUEUE、ORDERED、SIMPLE
HEAD OF QUEUE优先级的任务会被插入到队列头,ORDERED优先级的任务会被插入到队列尾,SIMPLE优先级的任务可以插入到任意顺序,但不能插入到HEAD OF QUEUE与ORDERED任务之前。
一般来说ORDERED优先级是很少被使用的,因为即使在这一层完成了保序,在写入磁盘时顺序依旧会被打乱,所以这么操作没有任何意义。但值得注意的是,在本文中已经实现了X=P的前提下,ORDERED优先级便有了用武之地。
Order Preserving Dispatch:D=X
Order Preserving Dispatch是这篇论文的一个亮点,这种块设备调度程序会在前面一个命令服务完后立刻服务下一个命令——并且还能保证有序,这样免去的等待数据传输的开销,为了与传统的Wait-on-Transfer区别,作者将这种机制称为Wait-on-Dispatch,具体实现方法如下。
首先,需要根据命令类型来设置任务的优先级,默认优先级是SIMPLE,对于barrier-write,设置为ORDERED。借此可以直接利用SCSI的现有接口来保证barrier-write的保序性。
作者的这种方法确实很巧妙也很简单,至于为什么别人不用,我想是因为只完成一步的保序从整体上来看没有任何意义,必须要所有操作结合起来才能完成I=P的终极目标。
Epoch-Based IO scheduling:I=D
在IO调度器层,作者重新设计了IO调度算法(即Epoch-Based IO scheduling),在讲解算法之前,首先我们来看一下算法的设计目标:
- 确保OP的写请求可以在前后的barrier之间自由调度。
- 确保OP的写请求只能在前后的barrier之间自由调度。
- 确保orderless的写请求可以随意调度。
作者设计的IO调度算法也很简明。
首先我们说明一下Epoch:一个Epoch是两个barrier之间的所有有序请求,epoch内部是自由的,可以任意调度,但是不同epoch之间的请求无法自由调度。举例说明,下面是一组请求序列:{BW2、W2、W1、BW1}(其中BW1是第一个请求),则BW2、W2、W1被分为同一个Epoch,他们之间可以相互自由调度,不同Epoch
接下来是作者将写请求按Epoch划分的实现方法:
如果一个请求不是barrier-write请求,将其插入队列,如果是barrier-write请求,移除其barrier标志将其插入队列,此时不再接收新的请求。对队列中的请求自由排序,为最后一个ordered请求设置barrier。对队列中的请求进行服务,直到所有的有序请求都被处理,再接收新的请求(无序请求仍然是自由的)。
Barrier-enabled Filesystem
完成以上步骤后,I=P便得到了满足,剩下的事情就是将其应用在文件系统上了。
作者利用新的IO栈设计了两个新的原语:fbarrier与fdatabarrier,分别对应fsync与fdatasync。相比于fsync,fbarrier也能够保证写请求的按序性,并且开销低了非常多。但fbarrier与fsync存在一处语义差异:fbarrier虽然也能够保证写请求的按序,但是它所做的仅仅是保证fbarrier之前的请求一定比fbarrier之后的请求先写入,却不保证何时才会真正写入。所以值得注意的是,虽然作者一直强调fbarrier相比于fsync有着巨大的优越性,但并不是在所有情况下都可以直接用fbarrier替换掉fsync的:如果你希望在语句执行后,数据被切切实实地写入到存储器上面了,那你还是要用fsync。
(fdatabarrier类似)
Evaluation
简单来说,作者对不同类型的SSD上利用多种数据库进行对比测试,将fbarrier与fsync和OPTFS的osync(语义与fbarrier等价)进行对比,结果当然是显而易见的:fbarrier吊打对手们(当然还是要比直接的无序写入慢一点)。总之,作者证明了他的这种方法确实很有效,能够带来很大的性能提升。