WALDIO: Eliminating Filesystem Journaling in JOJ

来源:互联网 发布:c创建windows窗口程序 编辑:程序博客网 时间:2024/05/16 17:16

移动设备中使用的DRAM与NAND Flash占全球DRAM与NAND Flash的30%和40%,由于Android IO栈中应用层SQLite与EXT4文件系统的不匹配,导致IO整个性能比较低,导致不匹配的原因是SQLite每次通过fsync与fdatasync同步修改时会引起EXT4文件系统日志模块更新元数据信息,这种现象也被称为JOJ(Journaling of Journal Anomaly)。
这篇文章主要是通过EXT4与SQLite的配合,减少EXT4文件系统的日志内容,同时要保证系统出现失效时的。工作包括三部分
(1)为日志进行预分配;为SQLite日志文件与文件元数据的日志预先分配数据块,后面direct IO不会产生任何元数据的恩关系,从而文件系统的日志可以被删除。
(2)头部嵌入;开发Header Embedding并重新设计日志文件的数据结构,保证SQLite日志文件结构为4KB对其,从而兼容direct IO。
(3)组同步;通过组同步,我们可以聚集多个log并在一个单元内部同步它们,提供性能。
背景介绍

  1. SQLite
    SQLite是一个嵌入式的关系型数据库DBMS,主要是采用B-tree数据结构,B-tree节点大小从512字节到64KB,默认为1KB。每一个B-tree节点由一个页头、索引数组和cell数组构成,页头在节点的最开始,后面是索引数组,每一个索引指向不同大小的记录,这些记录被称为cell,它是从节点的尾部开始分配,当一个cell被删除的时候,空间被标志位dead,并链入页头维护的一个链表。
    不像其他的关系型数据库,SQLite没有自己的存储空间管理模块,要依赖于文件系统进行持久化的管理并保证系统的可靠性。SQLite使用文件在维护log日志,为了事务完整性,每次都需要显式的同步,例如调用fdatasync。
    SQLite提供了六种日志模式:DELETE, TRUNCATE, PERSIST, WAL, MEMORY和OFF。MEMORY和OFF将日志保存在内存中,剩下四种大概分成两类: rollback与rollforward,DELETE,TRUNCATE,PERSIST为rollback,WAL为rollforward。在rollback模式下,SQLite的操作(INSERT, DELETE, UPDATE)包括三个阶段:logging;数据库update;log reset;rollback下三种日志模式,前两个阶段是相同的,主要是最后一个阶段,DELETE模式会将日志文件删除;TRUNCATE模式会将日志文件赋值为0,;PERSIST模式将特殊的标识在日志文件中;三种操作方式看似简单,但是影响差别是很大的。DELETE模式下,SQLite需要创建一个新的日志文件,这会导致文件系统有大量元数据的更新;TRUNCATE模式保留了inode和释放了文件的block,因为不创建日志文件,减少了文件系统元数据的更新;PERSIST模式不仅保留了inode还保留文件的block,文件系统元数据更新仅仅是时间mtime。
    在WAL模式下,SQLite将节点头部和数据库更新的页追加到log文件中,当数据库表关闭,WAL文件达到预定大小就会将持久化检查点,SQLite提供两种方式的同步log:Full Sync和Normal Sync,Full Sync是在每次提交log时会同步,Normal Sync是在每次到达检查点时同步。
  2. EXT4 Journaling
    EXT4文件系统提供了三种日志方式:Journal,Ordered和Writeback。Ordered模式使用最广泛,仅仅需要对更新的元数据进行log。当log元数据时,文件系统需要flush与元数据相关所有的data blocks。

JOJ的分析
OFF模式下,SQLite仅仅更新数据库文件,不会带来SQLite日志相关的IO,相应的EXT4文件系统会写入两个数据block以及对应的元数据,其中元数据日志有3个block:日志描述符、更新元数据、日志commit标识。
WAL模式下,SQLite需要写日志到文件中,并通过fdatasync进行同步。由于调用fdatasync,EXT4就会对SQLite日志文件的元数据进行更新,并会加上日志描述符与commit标识。因为数据库数据会被加入到检查点中不会产生IO操作

Direct IO
Direct IO是允许应用直接与存储设备进行读写数据的交互,不需要经过page cache。SQLite主要通过日志文件来维护logs,保存对应的log到日志文件中,并通过fsync与fdatasync显式下刷,这样会带来文件系统本身日志,如果SQLite采用direct IO会节省文件系统开销。
写block到存储设备有三种方式:(1)write + fsync (2)write + fdatasync (3)DIO write;这些方式的不同主要就是在文件系统处理更新的元数据上。fsync的处理上,EXT4文件系统会为对应文件journal要更新的元数据。fdatasync中,EXT4文件系统仅仅当文件block要分配的时候才会journal更新的元数据;而DIO本身不会产生任何文件系统的journal。所以可以大致将写操作分为两类:分配write操作与不分配write操作。分配write要求文件系统重新分配block,这样会导致元数据更新:block位图、inode表等。不分配write操作仅仅只是更新访问时间,不会有其他元数据。

Eliminating Filesystem Journaling

  1. Preallocation Explicit Journaling
    目标是在减少文件系统日志的情况下还要保证日志文件元数据的完整性,因此设计开发日志块预分配,首先为WAL文件预先分配一定数量初始化好的数据块,并通过调用fdatasync对元数据日志记录,这个方法我们就需要依赖文件系统来保护SQLite日志文件的元数据。EXT4文件系统为每一个数据块都设置一个已初始化flag。这样任何读取未初始化的block都返回0,避免提供脏数据。
    对文件的数据块进行预分配之后,DIO就会成为是non-allocating写操作,数据与元数据都可以保证其完整性,当WAL文件创建时或者扩展时会调用fdatasync同步元数据。预分配时特别需要考虑的是初始化已分配的数据块,否则当系统出现失效时日志会变成不可读。原因是:fallocate系统调用返回的是未初始化的数据块,当第一次被写入的时候才会进行初始化,当数据块通过direct io进行写入的时候,文件系统会设置初始化的flag。然而,因为DIO不会引发文件系统的日志,因此当系统出现故障这个更新的flag标识会丢失。
    我们提供三种方法来初始化分配的数据块,同时保护分配块脏数据。最简单的方法进行零值填充。第二种和第三种都利用eMMC中discard(trim)命令来保护脏数据,discard命令主要以逻辑地址作为输入,要求eMMC解除此地址的映射关系。其中第二种在mount文件系统的时候使用discard,并修改fallocate分配数据块为已初始化。

  2. Header Embedding

  3. Group Synchronization
0 0
原创粉丝点击