Barrier-Enabled IO Stack for Flash Storage

原文

Abstract

  这项工作致力于消除保证现代IO堆栈中存储顺序所需的开销。现有的块设备在确保写请求之间的存储顺序时采用了非常昂贵的方法:将写请求与Transfer-and-Flush交错。利用Flash存储器的缓存屏障命令,我们修改了IO调度程序,调度模块和文件系统,以便编排这些层以保留由应用程序施加的相关数据块的持久化顺序条件。启用Barrier的IO堆栈的关键组件是基于时代的IO调度,顺序保留调度和双模式日志记录。启用屏障的IO堆栈可以控制存储顺序而无需传输和刷新开销。我们在服务器以及移动平台中实现启用屏障的IO堆栈。在服务器和智能手机中,SQLite性能分别提高了270%和75%。在服务器存储中,BarrierFS通过放松事务的持久性,在MySQL和SQLite中分别实现了43倍和73倍的性能提升,相对于EXT4。

Motivation

  现代Linux IO堆栈是仲裁层的集合; IO调度程序,命令队列管理器和写回缓存管理器在将它们传递到下一层之前,自行处理传入请求。尽管多层仲裁存在复合不确定性,但软件编写人员在许多情况下必须执行一定的顺序,将数据块反映到存储表面,存储顺序,例如保证持久性和原子性数据库事务[47,26,35],文件系统日志[67,41,65,4],软更新[42,63]或复制写入或日志结构文件系统[61,35, 60,31]。通过一种非常昂贵的方法实现存储顺序:只有在与前一个请求相关联的数据块完全传输到存储设备并且持久化之后才调度以下请求。我们称这种机制为Transfer-and-Flush。几十年来,使用Transfer-and-Flush交叉写入请求一直是确保一组请求中的存储顺序的基本原则[24,16]。
  我们观察到闪存性能和容量的显着增加。 性能的提高很大程度上归因于Flash存储的并发性和并行性,例如 多通道/方式控制器[73,6],大容量存储高速缓存[48]和深层命令队列[19,27,72]。 据报道,最先进的NVMe SSD显示高达750 KIOPS的随机读取性能[72]。 它的性能接近4000倍。 容量增加是由于采用了更精细的制造工艺(低于10nm)[25,36],以及每个单元的多比特(MLC,TLC和QLC)[5,11]。 同时,编程闪存单元的时间几乎没有改善,甚至在某些情况下会恶化[22]。
  基于Transfer-and-Flush的顺序保留机制与Flash存储的并行性和并发性相冲突。它会禁用Flash存储的并行和并发功能,并将原始Flash单元编程延迟暴露给主机。随着闪存存储采用更高程度的并行性和更密集的闪存设备,Transfer-and-Flush机制的开销将变得更加重要。图1显示了一个重要的趋势。我们测量了EXT4文件系统中无序随机写入(普通缓冲写入)和有序随机写入的持续吞吐量。在有序随机写入中,每个写入请求之后都跟着fdatasync()。 X轴表示无节制写入的吞吐量,其对应于存储设备以全速服务写入请求的速率。这通常与供应商发布的存储设备性能相匹配。每个点旁边的数字表示有序写入的持续吞吐量。 Y轴表示两者之间的比率。在智能手机的单通道移动存储(SSD A)中,有序写入的性能是无序写入(1351 IOPS和7000 IOPS)的20%。在32通道闪存阵列(SSD G)中,此比率降低至1%(2296 IOPS vs. 230K IOPS)。在采用超级电容(SSD E)的SSD中,有序写入性能是无序写入性能的25%。 Flash存储使用超级电容来隐藏主机的清空延迟。即使在使用超级电容的闪存中,Transfer-and-Flush的开销也很大。
图1
  许多研究人员试图解决存储顺序保证的开销。在生产平台中部署的技术包括Flash存储器中的非易失性写回缓存[23],EXT4文件系统中的无障碍挂载选项[15]以及事务校验和[56,32,64]。诸如事务性文件系统[50,18,54,35,68]和事务性块设备[30,74,43,70,52]的努力使应用程序免于执行与文件系统日志记录相关联的存储顺序的开销。一系列工作解决控制存储顺序的更基本的方面,例如将顺序保证与持久性保证分开[9],提供编程模型以定义写入集合[20]之间的顺序依赖性,并且持续存储数据块只有当结果需要在外部可见时[49]。尽管它们优雅,但在需要强制执行存储顺序时,这些作品依赖于Transfer-and-Flush。 OptFS [9]依靠Transfer-and-Flush来强制执行日志提交和相关检查点之间的顺序。 Featherstitch [20]依靠Transfer-and-Flush来实现补丁组之间的顺序依赖关系。
  在这项工作中,我们重新讨论了在现代IO栈中消除传输和刷新开销的问题。我们开发了一个启用Barrier的IO堆栈,其中文件系统可以在提供前面的请求之前发出以下请求,但IO堆栈可以强制它们之间的存储顺序。启用屏障的IO堆栈由缓存屏障感知存储设备,顺序保留块设备层和启用屏障的文件系统组成。对于缓存感知存储设备,我们利用“缓存屏障”命令[28]。启用屏障的IO堆栈建立在主机可以控制缓存内容刷新的特定部分顺序的基础上。 “缓存屏障”命令恰恰可以达到这个目的。对于顺序保留块设备层,修改了命令分派机制和IO调度程序进行,以便块设备层确保来自文件系统的IO请求得到服务,从而保留特定的部分顺序。对于启用屏障的文件系统,我们定义了新的接口fbarrier()和fdatabarrier(),以将顺序保证与持久性保证分开。它们分别与fsync()和fdatasync()类似,不同之处在于它们不等待关联块变为持久而返回。我们修改EXT4用于保留顺序的块设备层。我们为顺序保留区块设备开发双模式日志。基于双模式日志记录,我们新实现fbarrier()和fdatabarrier()并重写fsync()。
  启用屏障的IO堆栈可消除刷新开销以及执行存储顺序时的传输开销。 尽管大部分工作着重于消除刷新开销,但很少有工作解决了DMA传输的开销来强制执行存储顺序。 启用屏障的IO堆栈的好处包括以下几点:

  • 应用程序可以在没有任何开销的情况下控制存储顺序,包括刷新开销,DMA传输开销和上下文切换。
  • 日志提交的延迟显着下降。 日记模块可以强制执行日志记录和日志提交模块之间的存储顺序,而不会将它们与flush或DMA传输进行交叉。
  • 文件系统日志的吞吐量显着提高。 双模式日志同时提交多个事务,但可以保证单个日志提交的持久性。

通过消除传输和刷新开销,启用屏障的IO堆栈成功利用了现代闪存中的并发性和并行性。

Backgroud

Orders in IO stack

  写入请求经过复杂的路由,直到数据块到达存储表面。 文件系统将请求放入IO调度程序队列。 块设备驱动程序从队列中移除一个或多个请求并构造命令。 它在设备可用时探测设备并分派命令。 如果命令队列未满,该设备可用。 存储控制器在命令队列中插入传入命令。 存储控制器从命令队列中移除该命令并为其提供服务(即传输主机和存储器之间的关联数据块)。 传输完成后,设备会向主机发送信号。 写回缓存的内容定期或通过来自主机的明确请求提交到存储表面。
  我们在IO堆栈中定义四种类型的顺序;发行顺序I,分配顺序D,传输顺序X和持续顺序P。发行顺序I = {i1; i2;…; in}是由文件系统发出的一组写入请求。下标表示请求进入IO调度程序的顺序。调度顺序D = {d1; d2; …; dn}表示分派给存储设备的一组写入请求。下标表示请求离开IO调度程序的顺序。转移顺序X = {x1; x2; …; xn}是转移完成的集合。持久顺序,P = {p1; P2,…,pn}是一组操作,可以使写回缓存中的数据块持久。我们说,如果在两种不同类型的顺序之间,请求与指定请求(屏障)之间的相对位置被保留,则称为保留部分顺序。我们使用符号’=’来表示保留了部分顺序。由于以下原因,不同类型顺序之间的部分顺序可能不一致

  • I ≠ D .IO调度程序根据调度原则重新顺序和合并IO请求,例如, CFQ,DEADLINE等。当没有调度机制时,例如 NO-OP调度程序[3]或NVMe [13]接口,调度顺序可能与问题顺序相同。
  • D ≠ X。 存储控制器可以自由地在命令队列中安排命令。 另外,由于错误,超时和重试,命令可能会乱序服务。
  • X ≠ P。存储器的写回缓存不是FIFO。 在Flash存储器中,持久顺序不受数据块持久化的顺序的控制,而是受相关映射表条目的更新顺序的控制。 这两者可能不一致。

由于所有这些不确定性,现代IO堆栈被认为是无序的[8]
图片2

Transfer-and-Flush

  执行存储顺序对应于保留文件系统发出请求的顺序I和相关数据块的持久顺序P之间的部分顺序。这相当于共同执行在图2中的相邻层次中的顺序对之间的部分顺序。它可以正式表示为方程1。

(I = P) ≡ (I = D) ^(D = X ) ^(X = P) (1)

现代IO栈已经在主机不能控制持续顺序的假设下发展,即X ≠ P。这是由于旋转媒体的物理特性。 对于像HDD这样的旋转媒体,持久顺序由磁盘调度算法控制。 磁盘调度完全留给存储控制器,因为其复杂的扇区几何形状是外部隐藏的[21]。 当主机盲目执行某个持续的命令时,IO服务可能会出现异常延迟。 由于X ≠ P的这个约束, 1是不可满足的。 现代IO堆栈设计中主机无法控制持续顺序的限制是一个基本限制
  尽管约束X ≠ P,块设备层采用间接和昂贵的方法来控制存储顺序。首先,在将写入命令分派给存储设备后,调用者推迟分派后续命令,直到先前的命令被服务,即直到关联的DMA传输完成。我们将此机制称为等待传输(wait-on-transfer)。等待传输机制确保命令按顺序服务并满足D = X。等待传输代价昂贵;它会阻塞调用者并将请求与DMA传输交错。其次,当前面的命令被服务时,调用者发出flush命令并等待其完成。调用者只有在flush命令返回后才会执行后续命令。这是为了确保相关数据块按顺序持久化并满足X = P。我们将此机制称为Wait-on-Flush。现代块设备层在需要强制执行写入请求之间的存储顺序时使用成对的Wait-on-Transfer和Wait-on-Flush。我们将这种机制称为Transfer-and-Flush。.
  Transfer-and-Flush的成本是令人望而却步的。 它消除了Flash存储控制器的内部并行性,停止了命令队列,并将调用者暴露给DMA传输和原始单元编程延迟。

Analysis: fsync() in EXT4

图3
  我们检查EXT4文件系统如何控制fsync()中的存储顺序。在有序日记模式下(默认),数据块在日记事务之前持久化。图3说明了fsync()的行为。文件系统为一组脏页面发出写请求,D可能由来自不同文件的数据块组成。发出写入请求后,应用程序线程会等待DMA传输完成。当DMA传输完成时,应用程序线程恢复并触发JBD线程提交日志事务。触发JBD线程后,应用程序线程再次休眠。当JBD线程使日志事务持久化时,fsync()返回。应该强调的是应用程序线程只有在D传输后才会触发JBD线程。否则,存储控制器可能以无序方式服务于写入请求D和用于日志提交的写入请求,并且存储控制器可能过早地持久化日志事务(在D被传送之前)
  日志事务通常使用两个写入请求来提交:一个用于写入日志描述符块和日志块的合并块,另一个用于写入提交块。在本文的其余部分,我们将分别使用JD和JC来表示日志描述符和日志块的合并块,以及提交块。在提交日志事务时,JBD需要在两个关系中执行存储顺序:事务内和事务间。在事务内,JBD需要确保JD在JC之前持久。在事务间,JBD必须确保日记事务按序持久化。当违反这两个条件中的任何一个时,如果发生意外故障,文件系统可能会错误地恢复[67,9]。对于事务内的存储顺序,JBD将JD的写入请求和JC的写入请求与Transfer-and-Flush交错。为了控制事务之间的存储顺序,JBD线程在开始提交后面的事务之前等待JC变为持久。 JBD使用Transfer-and-Flush机制来执行intra-transaction(内部事务)和inter-transaction(事务间)存储顺序。
  在早期的Linux中,块设备层在提交日志事务时显式发出flush命令[15]。在这种方法中,flush命令不仅阻塞调用者,而且阻塞同一个调度队列中的其他请求。从Linux 2.6.37开始,文件系统(JBD)隐式地发出了一个flush命令[16]。在写入JC时,JBD用REQ_FLUSH和REQ_FUA标记写入请求。大多数存储控制器已经演变为支持这两个标志;有了这两个标志,存储控制器在为命令提供服务之前刷新写回缓存,并在服务命令时,它直接持久化JC到存储表面,绕过写回缓存。在这种方法中,只有JBD线程块和共享相同调度队列的其他线程才能继续。我们的努力可以被认为是IO堆栈演化的延续。我们通过使存储设备更具能力来缓解Transfer-and-Flush开销:支持屏障命令并相应地重新设计主机端IO堆栈。

Order-Preserving Block Device Layer

Design

  顺序保留块设备层由新定义的屏障写入命令,顺序保留调度模块和基于时期的IO调度程序组成。 我们修改IO调度程序,调度模块和写入命令,以便它们可以分别保留不同类型的顺序之间的部分顺序,即I = D,D = X和X = P。 顺序保留调度模块消除了等待传输开销,新定义的屏障写入命令消除了等待刷新开销。 它们共同一起保留了发行顺序I和持久化顺序P之间的部分顺序,而无需Transfer-and-Flush。
图4
  顺序保留块设备层将写入请求分为两类,无序写入和保留顺序写入。顺序保留请求是受存储顺序约束的请求。无序请求是与顺序依赖关系无关,并可以自由调度的请求。我们将两者区分开以避免在调度请求时施加不必要的顺序约束。细节即将到来。我们将一组可以相互重新排序的顺序保留请求当作时代(epoch)[14]。我们定义了一种特殊类型的顺序保留写入作为屏障写入。屏障写入用于界定时代(epoch)。我们为bio对象和request对象引入了REQ_ORDERED和REQ_BARRIER这两个新属性,以表示保序写入和屏障写入。 REQ_ORDERED属性用于指定保序写入。屏障写请求具有REQ_ORDERED和REQ_BARRIER属性。顺序保留块设备层根据其类别对请求进行不同的处理。图4说明了启用Barrier的IO栈的组织结构。

Barrier Write,the Command

  用于移动闪存的标准命令集定义了“高速缓存屏障”或简称“屏障”命令[28]。 通过屏障命令,主机可以控制持久顺序,而无需显式调用缓存刷新。 当存储控制器接收到屏障命令时,控制器保证屏障命令之前传输的数据块在执行屏障命令之后的变为持久。市场上的一些eMMC产品支持缓存屏障命令[1,2]。 屏障命令可以满足公式(1)中的条件X = P。 1,由于旋转介质的机械特性,它几十年来一直不能令人满意。 使用屏障的天真方式是取代现有的刷新操作[66]。 这种简单的替换仍然使调用者处于Wait-on-Transfer开销之下以强制执行存储顺序
  将障碍作为单独的命令实施会占用命令队列中的一个条目,并使主机花费分派命令的延迟。为了避免这种开销,我们将屏障定义为命令标志。我们在SCSI命令中指定一个未使用的位作为屏障标志。我们设置写入命令的屏障标志使它成为屏障写入。当存储控制器接收到屏障写入命令时,它为屏障写入命令提供服务,就好像屏障命令在写入命令之后立即到达一样。
  通过合理的复杂性,Flash存储可以支持屏障写入命令[30,57,39]。当闪存具有掉电保护(PLP)功能时,例如超级电容器,写回缓存内容保证持久。存储控制器可以利用其并行性充分刷新写回缓存,还可以保证持久顺序。在使用PLP的闪存中,我们期望屏障写入的性能开销不重要。
  对于没有PLP的设备,可以通过三种方式支持屏障写入命令;按顺序写回,事务回写或按顺序恢复。在有序写回中,存储控制器按时代(epoch)粒度刷新数据块。在一个时代(epoch)中数据块的数量可能不够大,无法充分利用Flash存储的并行性。屏障写入实现的按顺序写回风格会导致高速缓存刷新中的性能下降。在事务回写中,存储控制器将写回缓存内容作为单个单元进行刷新[57,39]。由于写回缓存中的所有时期都被刷新在一起,因此屏障命令施加的持久顺序得到满足。如果控制器利用Flash页面的备用区域来表示事务[57]中的一组页面,则可以在没有任何性能开销的情况下实现事务回写。按顺序恢复方法依靠崩溃恢复例程来控制持久顺序。当多个控制器内核同时将数据块写入多个通道时,可能需要使用复杂的故障恢复协议,如ARIES [46]将存储恢复到一致状态。如果整个Flash存储被视为单个日志设备,那么我们可以使用LFS中使用的简单故障恢复算法[61]。由于持久顺序是由崩溃恢复逻辑强制执行的,因此存储控制器可以在完全控制下刷新写回缓存,就好像没有顺序依赖关系一样。控制器在恢复例程中以复杂性为代价节省性能损失。
  在这项工作中,我们修改UFS存储设备的固件以支持屏障写入命令。 我们使用简单的LFS风格按序恢复方案。 修改后的固件加载到Galaxy S6智能手机1的商用UFS产品中。 修改后的固件将整个存储设备视为单个日志结构设备。 它在内存中维护一个活动段。 FTL将传入数据块按传输顺序附加到活动段。 当活动段变满时,控制器按照日志结构的方式在多个Flash芯片上划分活动段。 在崩溃恢复中,UFS控制器定位最近刷新的段的开始。 它从头开始扫描段中的页面,直到遇到未成功编程的页面。 存储控制器丢弃其余的页面,包括不完整的页面。
  开发具有屏障功能的SSD控制器是一项工程练习。 它受许多设计选择的约束,应在单独的环境中解决。 在这项工作中,我们证明了如果主机端IO堆栈可以正确利用它,那么障碍命令实现的性能优势很值得它的复杂性。

Order-Preserving Dispatch

图5
  保留顺序调度是这项工作的一项基本创新。 为了保序调度中,块设备层在调度前一个调度之后立即调度后续命令(图5),但主机可以确保两个命令按顺序进行服务。 我们将此机制称为等待调度(Wait-on-Dispatch)。 保序调度是满足公式(1)中的条件D = X。没有等待传输开销。
  调度模块根据请求构造命令。 调度模块在遇到屏障写入请求时构造屏障写入命令,即,具有REQ_ORDERED和REQ_BARRIER标志的写入请求。 对于其他请求,它将构建与传统块设备中所用的命令相同的命令。
  实现保留顺序的调度相当简单; 块设备驱动程序按照顺序设置屏障写入命令的优先级。 然后,SCSI兼容存储设备服务满足顺序约束的命令。 以下是原因。 SCSI标准定义了三个命令优先级:队列头,有序和简单的[59]。 每个存储控制器都将输入的命令分别放置在命令队列的头部,命令队列的尾部或任意确定的任意位置。 默认优先级很简单。 具有简单优先级的命令不能插入队列命令的有序或头部前面。 利用现有SCSI接口的命令优先级,顺序保留调度模块确保只有在命令队列中的现有请求被服务之后并且在屏障写入之后的任何命令服务之前对屏障写入进行服务。
  在调度写请求后,设备可能暂时不可用,或者调用者可能被非自愿地切换出去。顺序保留调度模块使用现有块设备驱动程序的相同错误处理例程;内核守护进程继承该任务,并在特定的时间间隔后重试失败的请求,例如, SCSI设备为3毫秒[59]。有序的优先级命令很少用在现有的块设备实现中。这是因为当主机无法控制持续顺序时,从确保存储顺序的角度来看,强制执行具有有序优先级命令的传输顺序几乎没有任何意义。在屏障写入的出现中,有序的优先级在使整个IO堆栈保留顺序的过程中起着至关重要的作用。
  无法进一步强调保序调度的重要性。通过顺序保留调度,主机可以在不释放CPU的情况下控制传输顺序,也不会拖延命令队列。由于CPU调度程序干扰调用者的执行的可能性较小,IO延迟可变得更加可预测。图5中的DWoT和DWoD分别说明了Wait-on-Transfer和Wait-on-Dispatch中连续请求之间的延迟。在Wait-on-Dispatch中,主机在发出Wi后立即发出下一个请求Wi + 1(WoD)。在等待传送中,主机只有在服务完Wi后才发出下一个请求Wi + 1(WoT)。 DWoD比DWoT小一个数量级。

Epoch-Based IO scheduling

  基于时期的IO调度旨在保留发布顺序和调度顺序之间的部分顺序。它满足条件I = D。它有三个原则设计; (i)它保留了这些时期(epoch)之间的部分顺序,(ii)一个时期内的请求可以相互自由地进行调度,以及(iii)可以调度跨时期的无序请求。
  当IO请求进入调度器队列时,IO调度程序确定它是否是屏障写入。如果请求是屏障写入,则IO调度程序将从请求中移除屏障标志并将其插入到队列中。否则,调度程序按原样将其插入队列。当调度程序向队列插入屏障写入时,它将停止接受更多请求。由于调度程序在插入屏障写入后阻塞队列,队列中的所有顺序保留请求都属于同一个时期。队列中的请求可以自由重新排序并相互合并。 IO调度器使用现有的调度规则,例如, CFQ。如果其中一个组件是顺序保留请求,合并的请求将保留顺序。 IO调度程序指定最后一个离开队列的保留顺序的请求作为新的屏障写入。这种机制被称为Epoch-Based Barrier Reassignment。当队列中没有任何顺序保留请求时,IO调度程序再次开始接受IO请求。当IO调度程序取消阻塞队列时,队列中可能有一个或多个无序请求。这些无序请求与后面时期的请求一起调度。区分无序的请求和保留顺序的请求,我们避免对不相关的请求施加不必要的顺序约束。
图6
  图6举例说明。包围写入请求的圆圈和矩形分别表示顺序保留标志和屏障标志。 fdatasync()会创建三个写入请求:w1; w2和w4。即将详细介绍的启用屏障的文件系统会将写入请求标记为有序保留的文件系统。最后一个请求w4被指定为屏障写入,并建立一个新时代{w1; w2; w4}。 pdflush创建三个写请求w3; w5和w6。他们都是无序的写入。来自两个线程的请求作为w1; w2; w3; w5; w4barrier; w6被馈送到IO调度器。当屏障写入w4进入队列时,调度程序停止接受新的请求。因此,w6不能进入队列。 IO调度程序对队列中的请求进行重新顺序,并将它们分配为w2; w3; w4; w5; w1barrier命令。 IO调度程序将屏障标志从w4重定位到w1。在IO调度之后,时代被保留。
  顺序保留块设备层现在满足公式1所有三个条件,I = D; D = X和X = P。分别具有基于时代的IO调度,顺序保留调度和屏障写入。顺序保留块设备层成功消除了控制存储顺序的Transfer-and-Flush开销,并且可以控制存储顺序, 仅只有调度开销。

Barrier-Enabled Filesystem

Programming Model

  启用屏障的IO堆栈提供了四个同步原语:fsync(),fdatasync(),fbarrier()和fdatabarrier()。 我们提出两个新的文件系统接口fbarrier()和fdatabarrier(),分别支持顺序保证。 fbarrier()和fdatabarrier()分别与fsync()和fdatasync()同步同一组块,但它们返回时不确保关联的块变为持久。 fbarrier()与OptFS [9]中的osync()具有相同的语义,它按顺序写入数据块和日志事务,但返回时没有确保它们变为持久的
  fdatabarrier()同步修改的块,但不同步日志事务。 与fdatasync()不同,fdatabarrier()返回时没有保留关联的块。 fdatabarrier()是一个通用的存储屏障。 通过将write()调用与fdatabarrier()交错,应用程序确保fdatabarrier()之前的写入请求关联的数据块在fdatabarrier()后面的写入请求关联的数据块之前变得持久。 它起到与记忆屏障相同的作用[53]。 请参阅下面的小程序。 使用fdatabarrier(),应用程序确保“world”只有在“hello”之后才会变得持久。
  write(fileA, “Hello”) ;
  fdatabarrier(fileA) ;
  write(fileA, “World”) ;
顺序保留块设备层是文件系统不可知的。 在我们的工作中,我们修改了启用屏障IO堆栈的EXT4。

Dual Mode Journaling

  提交日志事务基本上由两个完全相同的任务组成:(i)为JD和JC分配写入命令,以及(ii)使JD和JC持久。 利用底层块设备的顺序保留特性,我们将日志提交操作的控制层活动(分派写入请求)和数据层活动(持久化相关数据块和日志事务)在物理上分开。 此外,我们为每个任务分配单独的线程,以便两个活动可以以最小的依赖性并行进行。 这两个线程分别称为提交线程和刷新线程。 我们将此机制称为双模式日志功能。 双模式日记机制可以多种方式支持两种日记模式,持久性保证模式和顺序保证模式。
图7
  提交线程负责调度JD和JC的写入请求。提交线程用屏障写入来写入JD和JC,以便JD和JC按顺序持久化。提交线程在没有任何延迟的情况下分派写入请求(图7(b))。在EXT4中,JBD线程将JC和JD的写入请求与Transfer-and-Flush交错(图7(a))。在调度JC的写入请求之后,提交线程将日志事务插入到提交事务列表中,并将控制权移交给刷新线程。
  flush线程负责(i)发出flush命令,(ii)处理错误并重试,以及(iii)从提交事务列表中移除事务。刷新线程的行为会根据日志提交的持久性要求而变化。如果日志提交由fbarrier()触发,则在从提交事务列表中除去事务后,刷新线程将返回。它将返回而不发出flush命令。如果日志提交由fsync()触发,则刷新线程涉及更多步骤。它发出一个flush命令并等待完成。刷新完成后,它将从提交事务列表中删除关联的事务并返回。 BarrierFS支持EXT4中的所有日记模式;写回,顺序和数据
  BarrierFS日志的双线程组织在文件系统设计中具有深远的意义。首先,对顺序保证和持久保证的单独支持自然成为文件系统的组成部分。顺序保证只涉及控制层面的活动。持久性保证要求控制平面活动以及数据平面活动。 BarrierFS将日志落实活动分为两个独立的组件,即控制平面和数据平面,并为每个组件分配不同的线程。这种模块化设计使文件系统原语能够根据日志提交操作的持久性要求自适应地调整数据平面线程的活动:fsync()与fbarrier()。其次,文件系统日志成为并发活动。由于双线程设计,在运行中可能会有多个提交事务。在我们知道的大多数日志文件系统中,文件系统日志是一系列活动;只有在前面的事务变为持久之后,日志线程才会提交以下事务。在双线程设计中,提交线程可以提交新的日志事务,而不必等待前面的提交事务变为持久。刷新线程异步通知应用程序线程完成日志提交

Synchronization Primitives

  在fbarrier()和fsync()中,BarrierFS以一种流水线的方式写入D,JD和JC,而没有任何延迟(图7(b))。 BarrierFS使用一个或多个保留顺序写入D,而写入JD和JC使用屏障写入。以这种方式,BarrierFS形成两个时期{D; JD}和{JC}在fsync()或fbarrier()中,并确保这两个时期之间的存储顺序。 fbarrier()在文件系统调度JC的写入请求时返回。 fsync()在确保JC持久化之后返回。顺序保留块设备满足前缀约束[69]。当JC变得持久时,顺序保留块设备保证所有与前一时代相关的区块已经变得持久。应用程序可能会重复调用fbarrier()同时提交多个事务。通过用屏障写入来写入JC,BarrierFS确保这些提交事务按顺序持久。 BarrierFS中fsync()的延迟显着降低。它将EXT4中的两个刷新操作数量减少到一个,并消除了等待传输开销(图7)。
  在fdatabarrier()和fdatasync()中,BarrierFS用屏障写入写入D.如果在写入D时有多个写入请求,则只有最后一个写入请求被设置为屏障写入,其他设置为顺序保留写入。 fdatasync()在数据块D变为持久后返回。 fdatabarrier()在调度D的写入请求后立即返回。fdatabarrier()是barrier-enabled IO堆栈的关键。使用fdatabarrier(),应用程序可以在没有任何开销的情况下控制存储顺序:无需等待刷新,无需等待DMA完成,甚至没有上下文切换。 fdatabarrier()是一个非常轻量级的存储屏障。
  fdatabarrier()(或fdatasync())在执行时可能找不到任何脏页面进行同步。 在这种情况下,BarrierFS显式触发日志提交。 它迫使BarrierFS为JD和JC发布屏障写入。 通过这种机制,即使在没有任何脏页面的情况下,fdatabarrier()或fdatasync()也可以根据应用程序的需要划分时期。

Handling Page Conflicts

  当应用程序尝试将缓冲页面插入到正在运行的事务中时,其可能已经被提交事务持有。我们将这种情况称为页面冲突。盲目地将冲突页面插入正在运行的事务中会使其在提交事务变为持久之前从中删除。 EXT4文件系统在将缓冲页面插入正在运行的事务时检查页面冲突[67]。如果文件系统发现冲突,则线程将插入委托给JBD线程和块。当提交事务变为持久化时,JBD线程将识别提交事务中的冲突页面并将其插入正在运行的事务中。在EXT4中,最多可以有一个提交事务。当JBD线程使其持久并完成将冲突页面插入到正在运行的事务中时,正在运行的事务将保证不会发生页面冲突。
  在BarrierFS中,可以有多个提交事务。冲突页面可能与不同的提交事务相关联。我们将这种情况称为多事务页面冲突。和EXT4一样,BarrierFS在使一个提交事务持久化时,将冲突页面插入正在运行的事务中。但是,要提交正在运行的事务,BarrierFS必须扫描提交事务中的所有缓冲页面以寻找页面冲突,并确保它没有任何页面冲突。当存在大量的提交事务时,在BarrierFS中检查页面冲突的扫描开销可能会过高
  为了减少这种开销,我们为正在运行的事务提出冲突页面列表。 冲突页面列表表示与正在运行的事务关联的一组冲突页面。 当文件系统发现其需要插入到正在运行的事务的缓冲页面受到页面冲突时,它会将缓冲页面插入到冲突页面列表中。 当文件系统使提交事务持久化时,除了将它插入正在运行的事务之外,它还会从冲突页面列表中删除冲突页面。 正在运行的事务只能在冲突页面列表为空时提交。

Concurrency in Journaling

  我们检查了不同存储顺序保证机制下的日志提交操作的并发度:BarrierFS,EXT4(无屏障选项)(EXT4(无刷新)),EXT4(带超级SSD SSD)(EXT4(快速刷新))和EXT4(EXT4 (全刷新))。使用无障碍安装选项时,JBD线程在提交日志事务时省略了flush命令。使用这个选项,EXT4既不保证日志提交操作的持久性,也不保证顺序,因为存储控制器可能会使数据块不按顺序持久。我们测试此配置以说明在日志提交操作中删除flush命令时的文件系统日记记录行为。
图8
  在图8中,每个水平线段表示日志提交活动。它由实线段和虚线段组成。水平线段的结尾表示事务到达磁盘表面的时间。实线段的末尾表示日志提交返回的时间。如果它们不一致,则意味着日志提交在事务到达磁盘表面之前完成。在EXT4(全刷新),EXT4(快速刷新)和EXT4(无刷新)中,文件系统仅在前一个日志提交完成后才提交新的事务。日志提交是一个序列活动。在EXT4(完全刷新)中,日志提交仅在所有关联块持久化后才会完成。在EXT4(快速刷新)中,由于SSD返回flush命令而不保留数据块,因此日志提交的完成速度比EXT4快(完全刷新)。在EXT4中(不刷新),日志提交比EXT(快速刷新)更快,因为它不会发出flush命令。在日志吞吐量方面,由于连续日志提交之间的间隔与调度延迟tD一样小,因此BarrierFS占优势。
  EXT4(无刷新)和EXT4(快速刷新)中的日志并发有其代价。 EXT4(快速刷新)需要SSD中的附加硬件组件超级电容。 EXT4(快速刷新)不保证日记提交中的持久性或顺序。 BarrierFS同时提交多个事务,还可以保证每个日志提交的持久性,无需额外硬件的帮助。 启用IO堆栈的屏障不需要对IO堆栈的现有内存或磁盘结构进行任何重大更改。 我们引入的唯一新数据结构是正在运行的事务的“冲突页面列表”。启用IO堆栈的屏障包含Linux内核的IO堆栈中大约3K LOC的更改。

Comparison with OptFS

  作为我们这种最接近的方法,OptFS值得详细阐述。 OptFS和屏障启用的IO栈主要在三个方面不同;目标存储介质,技术领域和编程模型。首先,OptFS不是专为闪存而设计的,但是启用屏障的IO堆栈是。 OptFS旨在减少文件系统日志中的磁盘查找开销;通过一起提交多个事务(延迟提交)并通过使磁盘顺序访问(选择性数据模式日志)。其次,OptFS是文件系统技术,而启用屏障的IO堆栈处理整个IO堆栈;存储设备,块设备层和文件系统。 OptFS建立在传统块设备层上。它受到与现有文件系统相同的开销。 OptFS使用Wait-on-Transfer来控制D和JD之间的传输顺序。 OptFS依靠Transfer-and-Flush来控制日志提交与osync()中的关联检查点之间的存储顺序。启用IO堆栈的屏障消除了等待传输和Transfer-and-Flush在控制存储顺序方面的开销。第三,OptFS专注于修改文件系统日志模型。 BarrierFS不限于修改文件系统日志记录模型,还可以导出通用存储屏障,应用程序可以将一组写入分组为一个时期

Applications

  迄今为止,fdatasync()一直是强制写入请求之间的存储顺序的唯一手段。 VM磁盘映像的虚拟磁盘管理器(例如qcow2)使用fdatasync()来强制写入VM磁盘映像[7]中的存储顺序。 SQLite使用fdatasync()来控制撤销日志和日志头之间以及更新的数据库节点和提交块[37]之间的存储顺序。在单个插入事务中,SQLite调用fdatasync()四次,其中三次是控制存储顺序。在这些情况下,可以使用fdatabarrier()来代替fdatasync()。在一些现代应用中,例如邮件服务器[62]或OLTP,fsync()占IO的主要部分。在TPC-C工作负载中,90%的IO由fsync()[51]创建。通过BarrierFS改进的fsync(),应用程序的性能可以显着提高。一些应用程序倾向于用结果的持久性和新鲜度交换操作的性能和可扩展性 [12,17]。在这些应用程序中,可以分别替换所有fsync()和fdatasync(),并分别使用顺序保证对应函数fbarrier()和fdatabarrier()

Experiment

  我们在三个不同的平台上实现了一个启用屏障的IO堆栈:企业服务器(12核,Linux 3.10.61),PC服务器(4核,Linux 3.10.61)和智能手机(Galaxy S6,Android 5.0.2,Linux 3.10)。 我们测试了三种存储设备:843TN(SATA 3.0,QD2 = 32,8通道,超级电容),850PRO(SATA 3.0,QD = 32,8通道)和移动存储(UFS 2.0,QD = 16,单通道)。 我们将BarrierFS与EXT4和OptFS进行比较[9]。 我们分别将它们分别称为supercap-SSD,plain-SSD和UFS。 我们在UFS设备中实施屏障写入命令。 在普通的固态硬盘和超级电容固态硬盘SSD中,我们假设屏障写入的性能开销分别为5%和无开销

Order-Preserving Block Layer

图9-10
  我们用不同的方式强制执行存储顺序来检查4 KB随机写入的性能:P(无序写入[即普通缓冲写入]),B(屏障写入),X(等待传输)和XnF(传输和Flush) 。图9显示了结果。
  Transfer-and-Flush的开销很大。使用Transfer-and-Flush时,有序写入的IO性能分别为plain-SSD和UFS中无序写入的0.5%和10%。在超级SSD中,性能开销不太显着,但仍然相当可观;有序写入的性能是UFS中无序写入的35%。 DMA传输的开销很大。当我们使用DMA传输交叉写入请求时,IO性能低于三个存储设备中每一个的无序写入的40%。
  屏障写入的开销可以忽略不计。当使用屏障写入时,有序写入在普通SSD和超级SSD中表现出90%的无序写入性能。对于UFS,它表现出80%的无序写入性能。屏障写入将驱动队列在所有三个Flash存储中达到其最大值。存储性能与命令队列利用密切相关[33]。在等待传输中,队列深度永远不会超过一个(图10(a)和图10(c))。在屏障写入中,队列深度在所有存储设备中都接近其最大值增长(图10(b)和图10(d))

Filesystem Journaling

  我们在EXT4和BarrierFS中测试文件系统日志中的延迟,上下文切换次数和队列深度。我们使用Mobibench [26]。对于延迟,我们执行4 KByte分配write(),然后执行fsync()。有了这个,fsync()总能找到日志更新的元数据,而fsync()延迟恰当地表示提交日志事务的时间。对于上下文切换和队列深度,我们使用4 KB的非分配随机写入,后跟不同的同步原语。
表1
  延迟:在普通SSD和超级SSD中,当我们在使用BarrierFS相对于使用EXT4时(表1),平均fsync()延迟降低40%。在UFS中,BarrierFS的fsync()延迟比EXT4减少了60%。与其他SSD相比,UFS在fsync()延迟方面经历了更显着的减少。
BarrierFS使fsync()延迟变得更少。在supercap-SSD和UFS中,第99.99百分位的fsync()延迟是平均fsync()延迟的30倍(表1)。在BarrierFS中,99.99%百分位数的尾部延迟分别在UFS,plain-SSD和supercap-SSD中分别相对于EXT4降低50%,20%和70%。
图11
  上下文切换:我们检查不同日志模式下应用程序级上下文切换的数量(图11)。在EXT4中,fsync()会将调用者唤醒两次:在传输D之后并且使日志事务处理持久化后(EXT4-DR)。这适用于所有三个存储。在BarrierFS中,fsync()中的上下文切换次数因存储设备而异。在UFS和supercap SSD中,BarrierFS的fsync()会将调用者唤醒两次,如EXT4的fsync()的情况。但是,原因完全不同。在UFS和supercap-SSD中,由于较小的刷新延迟,连续写入请求之间的间隔远小于定时器中断间隔。 write()请求很少找到更新的元数据,并且fsync()通常借助fdatasync()。 fdatasync()在BarrierFS中唤醒调用者(应用程序线程)两次:在传输D之后和flush完成之后。在普通的SSD中,BarrierFS的fsync()会将调用者唤醒一次:事务经过持久化后。普通的SSD使用TLC Flash。连续writes()之间的间隔比定时器中断间隔长。触发日志提交后,应用程序线程会阻塞,并在日志提交操作完成后唤醒。
BFS-OD体现了BarrierFS的优势。 fbarrier()很少发现更新后的元数据,因为它很快返回,因此大多数fbarrier()调用都是作为fdatabarrier()服务的。 fdatabarrier()不阻止调用者,因此不会伴随任何非自愿的上下文切换。
图12
  命令队列深度:在BarrierFS中,主机一起调度D,JD和JC的写入请求。 理想情况下,队列中最多可以有三个命令。 在服务fsync()时,我们只观察队列中的两个命令(图12(a))。 这是由于应用程序线程和提交线程之间的上下文切换所致。 写入D和写入JD间隔为160微秒,但需要70微秒来为D写入请求提供服务。在fbarrier()中,BarrierFS成功驱动命令队列达到其全部容量(图12(b))。
图13
  吞吐量和可扩展性:文件系统日志是构建多核可扩展系统的主要障碍[44]。 我们在12核心机器中测试EXT4和BarrierFS中文件系统日志记录的吞吐量以及不同数量的CPU核心。 我们在fxmark [45]中使用修改后的DWSL工作负载; 每个线程执行4 KB分配写入,然后执行fsync()。 每个线程都在自己的文件上运行。 BarrierFS展现出比EXT4更具有可扩展性的行为(图13)。 在普通SSD中,BarrierFS在所有数量的内核中都表现出相对于EXT4的2倍性能(图13(a))。 在Supercap-SSD中,EXT4和BarrierFS中的性能都达到了六核。 BarrierFS在EXT4上显示1.3倍的日志吞吐量(图13(b))

Server Workload

图14
  我们运行两个工作负载:varmail [71]和OLTP-insert [34]。 OLTP插入工作负载使用MySQL DBMS [47]。 varmail是一个元数据密集型工作负载。它是众所周知的繁重的fsync()流量。总共有四种工作负载和SSD组合(plainSSD和supercap-SSD)组合。对于每个组合,我们分别测试持久性保证和顺序保证的基准性能。对于持久性保证,我们保留应用程序并使用两个文件系统,即EXT4和BarrierFS(EXT4-DR和BFS-DR)。本实验的目标是分别检查EXT4和BarrierFS中fsync()实现的效率。对于顺序保证,我们测试了三个文件系统,OptFS,EXT4和BarrierFS。在OptFS和BarrierFS中,我们分别使用osync()和fdatabarrier()代替fsync()。在EXT4中,我们使用nobarrier安装选项。这个实验检测了等待调度的好处。图14说明了结果。
  让我们测试一下varmail工作负载的性能。在普通的SSD中,BFS-DR在varmail工作负载中相对于EXT4-DR带来60%的性能提升。在Supercap-SSD中,BFS-DR对EXT4-DR的性能提高10%。 supercapSSD案例的实验结果清楚地表明了在控制存储顺序时消除等待传输开销的重要性。 BarrierFS的优势体现在我们放松持久性保证时。在顺序保证中,BarrierFS的EXT4-OD性能提升了80%。与基准线EXT4-DR相比,当我们只使用普通SSD中的BarrierFS(BFS-OD)强制执行顺序保证时,BarrierFS可实现36倍的性能(1.0 vs. 35.6 IOPS)
  在MySQL中,BFS-OD的EXT4-OD赢得了12%的性能提升。 与基准线EXT4-DR相比,当我们仅使用普通SSD中的BarrierFS(BFS-OD)强制执行顺序保证时,BarrierFS的性能达到43倍(1.3比56.0 IOPS)

Mobile Workload:SQLite

  我们分别在持久性保证和顺序保证下检查基于libarary的嵌入式DBMS,SQLite的性能。我们研究两种日志模式,PERSIST和WAL。我们使用“完全同步”,WAL文件大小设置为1,000页,这两个都是默认设置[58]。在单个插入事务中,SQLite调用fdatasync()四次。其中三个是控制存储顺序,最后一个是使事务结果持久。
图15
  对于持久性保证模式,我们用fdatabarrier()替换前三个fdatasync(),并保留最后一个。在移动存储领域,BarrierFS提高了75%性能相对于在持久性保证(图15)下,默认PERSIST日志模式下的EXT4。在顺序保证中,我们用fdatabarrier()替换了所有四个fdatasync()。在UFS中,SQLite在BFS-OD中对EXT4-DR的性能提高了2.8倍。随着存储控制器采用更高级别的并行性,消除传输和刷新的好处变得更加显着。在普通的SSD中,SQLite在BFS-OD上对EXT4-DR的性能增益为73×(73 vs 5300 ins/sec)。
  OptFS的注意事项:OptFS在我们的实验中表现不佳(图14和图15),与[9]不同。我们找到两个原因。首先,延迟检查点和选择性数据模式日志的好处在Flash存储中变得微不足道。其次,在闪存(即具有较短IO延迟的存储器)中,延迟检查点和选择性数据模式日志记录彼此负相互作用,并导致存储器压力显着增加。增加的内存压力严重影响osync()的性能。 osync()会在开始时扫描检查点的所有脏页。选择性数据模式日记将更新的数据块插入日记帐事务。延迟检查点禁止日志事务中的数据块检查点,直到关联的ADN到达。因此,osync()每次调用时都只检查一小部分脏页。日志事务中的脏页在检查点之前会被多次扫描。 osync()在OLTP工作负载中显示特别差的性能(图14),其中大部分更新都受数据模式日志记录的影响。

Crash Consistency

  我们测试BarrierFS是否能够正确恢复系统意外故障。 我们使用CrashMonkey进行测试[40]。 我们修改CrashMonkey以了解屏障写入,以便CrashMonkey在遇到屏障写入时可以正确划定时期。 我们运行两个工作负载; rename_root_to_sub 和 create_delete。 对于持久性保证(fsync()),BarrierFS通过了所有1,000个测试用例,如EXT4在两种工作负载中所做的那样。 对于顺序保证(EXT4-OD中的fsync()和BarrierFS中的fbarrier()),BarrierFS通过所有1,000个测试用例,而EXT4-OD在某些情况下失败。 这并不奇怪,因为使用nobarrier选项的EXT4在提交文件系统日志事务时不保证传输顺序和持久顺序。
表2

Related Work

  Featherstitch [20]提出了一种编程模型来指定可以一起调度的一组请求(patchgroup)以及它们之间的顺序依赖关系(pg depend())。尽管xsyncfs [49]减轻了fsync()的开销,但它需要在缓冲更新之间维护复杂的因果关系。 NoFS(无顺序文件系统)[10]引入了“backpointer”来消除文件系统中基于顺序的Transfer-and-Flush。它不支持交易。
  一些作品提出使用多个运行事务或多个提交事务来规避文件系统日志中的传输和刷新开销[38,29,55]。 IceFS [38]为每个容器分配单独的运行事务。 SpanFS [29]将日志区域分割为多个分区并为每个分区分配提交事务。 CCFS [55]为单独的线程分配独立的运行事务。在这些系统中,每个日志会话仍然依靠Transfer-and-Flush机制。
  许多文件系统提供了一个多块原子写入特性[18,35,54,68]来减轻应用程序的日志记录和日志开销。这些文件系统在内部使用Transfer-and-Flush机制来强制执行存储顺序以写入数据块和关联的元数据块。利用顺序保留块设备层,这些文件系统可以使用Wait-on-Dispatch机制来强制执行数据块和元数据块之间的存储顺序,并且避免Transfer-and-Flush开销。

Conclusion

  Flash存储提供缓存屏障命令,以允许主机控制持久顺序。 HDD无法提供此功能。现在是为Flash存储设计新的IO堆栈的时候了,该堆栈不受从旧的遗留下来的不必要约束的限制,即主机无法控制持久秩序。我们基于主机可以控制持久顺序的基础构建了一个barrier-enabled IO堆栈。在启用屏障的IO堆栈中,主机可以在控制存储顺序时免除传输和刷新开销,并可以成功地使底层闪存存储饱和。我们希望通过两个关键的观察来总结这项工作。首先,“缓存屏障”命令是必要的,而不是奢侈品。它应该支持所有的闪存存储产品,从移动存储到具有超级电容的高性能闪存。其次,应该设计块设备层来消除控制存储顺序的DMA传输开销。随着Flash存储变得更快,迟到的“等待传输”的相对成本将变得更加可观。为了使闪存饱和,主机应该能够控制传输顺序,而不将请求与DMA传输交错。
  我们希望这项工作为设计闪存新的IO堆栈提供了有用的基础

Acknowledgement

  我们要感谢我们的牧羊人Vijay Chidambaram和匿名评论者的宝贵意见。 我们还要感谢Jayashree Mohan在CrashMonkey上的帮助。 这项工作是由基础研究实验室计划(NRF,编号2017R1A4A1015498),BK21加(NRF),ICT研发计划(IITP,R7117-16-0232)和三星电子资助

阅读更多
个人分类: 存储
上一篇博客二之博客美化
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭