最近和文件系统内核开发人员做技术交流,对O_DIRECT选项有了新的认识。
在 InnoDB存储引擎的配置中参数innodb_flush_method通常设置为O_DIRECT,这也是官方文档所推荐的设置值。DBA或开发人员 知道该参数是文件打开的一个标识,启用后文件的写入将绕过操作系统缓存,直接写文件。其在InnoDB存储引擎中的表现为对于写入到数据表空间将绕过操作 系统缓存。这样设置通常不会有更好的性能,但是数据库已经有自己的缓存系统,这样的设置可以确定数据库系统对于内存的使用。下面是man手册中对于 O_DIRECT选项的说明:

Try to minimize cache effects of the I / O to and from this file . In general this will degrade performance , but it is useful in special situations , such as when applications do their own caching .

然而在源码内,细心的读者可能会产生一些困惑,因为不管参数 innodb_flush_method的设置为何值,在刷新脏页时都会调用fsync操作,具体见函数 buf_flush_buffered_writes。那么,当用户已经打开文件操作的O_DIRECT标识,为什么还需要进行一次fsync操作确保文件的写入呢?
最早发现这个问题的是Facebook的MySQL团队负责人Mark Calleghan。其在2009年时在MySQL数据库的官方论坛中提交 Bug #45892 ,当年看到此Bug的回复时还是有些疑惑,因为其答复的诸如xfs这类文件系统,有些元数据还需要通过fsync进行刷新。公司同事花花也有问过我同样的问题,不知道是否在做云硬盘时遇到了类似问题。
最近和文件系统内核开发人员做交流,其对ext4的文件系统做了简单的介绍,自己对文件系统有了重新认识,对之前Bug的回复也有了更为清楚的理解。在 有些 文件系统中,例如ext4、xfs,文件(包括目录,在Linux中所有对象都是文件)都有一个inode与之对应,其保存有两部分的内容,元数据和文件的存储数据。根据 wiki 的介绍,元数据包含的内容有:
  • 文件的字节数
  • 文件的权限
  • 文件的时间戳
  • 链接的数量,即有多少文件指向该inode
  • 指向数据块的链接
  • 可 以发现元数据及其重要的,不仅仅是文件最后修改时间、权限等信息,它还包含有指向存储块的信息。若在数据增长时,元数据没有及时更新,那么同样可能会导致 数据丢失的情况。虽然此时,数据可能在磁盘上,但文件不知道那些块也是其组成部门。可以看到,这也是MySQL官网对上述Bug中的回复:

    For example , if a file grows while O_DIRECT is enabled it will still write to the new part of the file , however since the metadata doesn 't reflect the new size of the file the tail portion can be lost in the event of a crash.

    inode中的元数据是保存在inode cache中,inode的保存文件的数据是保存在操作系统缓存中(若未开启O_DIRECT标识)。读者可以观察下图Linux文件系统的实现方式:
    fsync操作会同步上图中的Inode cache,Buffer cache(也就是操作系统缓存),Directory cache 因此这就是为什么InnoDB存储引擎即使在文件打开时加上O_DIRECT标识,刷新脏页依然需要fsync操作。这是因为O_DIRECT标识只是忽 略了图中Buffe cache。刷新文件的另一个函数fdatasync,其仅刷新buffer cache的内容到磁盘,因此比fsync有更好的性能,但是存在数据丢失的风险。
    若用户想通过O_DIRECT写入文件,但避免可能存在的潜在风险时,可以再加上O_SYNC标识,此时写入实际变为了一个同步写( synchronous I/O) 操作,因此不再需要额外的fsync操作。见man手册中的说明:

    The O_DIRECT flag on its own makes an effort to transfer data synchronously , but does not give the guarantees of the O_SYNC flag that data and necessary metadata are transferred . To guarantee synchronous I / O , O_SYNC must be used in addition to O_DIRECT .

    未完待续......
    MySQL5.6中增加了一个选项: O_DIRECT_NO_FSYNC
    ~~~~~~~~~~~~~~~ 万物之中,希望至美 ~~~~~~~~~~~~~~~