最近和文件系统内核开发人员做技术交流,对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
~~~~~~~~~~~~~~~
万物之中,希望至美
~~~~~~~~~~~~~~~