文件打洞 (Hole Punching) 及其应用

一个文件的长度和它实际所占用的磁盘空间很可能是不同的,这主要涉及到稀疏文件(sparse file)和文件打洞(hole punching)的概念。这两个特性需要操作系统和文件系统的支持,目前Linux的ext4、XFS等文件系统都支持这两个特性。

稀疏文件 (Sparse File)

了解系数文件最直观的例子是,创建一个文件,然后用lseek定位到较大的偏移量,在这个偏移量实际写一些内容,这时实际占用的磁盘空间很小,但文件的长度却比较大。比如:

ls-s选项可以在第一列打印出文件所占的磁盘空间:

可以看到,两个文件的长度分别为3字节和98K字节,但是占用的磁盘空间却是相同的,即文件系统的最小存储单元4 KB。这时因为file_sparse在100000偏移量前根本没有使用磁盘块。

文件打洞 (Hole Punching)

上边例子中稀疏文件,是通过在空文件中的某个偏移量写入了3个字节得到的。而某些情况下,一个文件开始并非稀疏的,它已经占用了若干的磁盘空间,这时如果文件中间的一些数据没有用了,我们为了减小文件所占用的磁盘空间,就只能通过文件打洞 (Hole Punching)的方式将非稀疏文件转为稀疏文件。

具体方法是通过fallocate系统调用。通过man 2 fallocate,我们可以看到fallocate调用的原型如下:[1]

此调用的常规用法可以称为“allocation”: 指定mode为0,此时会将文件的[offset, offset+len)区域的内容写为0。

我们要用它做文件打洞,这种用法可以对应地称为“deallocation”:我们指定modeFALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,这时[offset, offset+len)区域中的块就会被“打洞”,从而减小文件的磁盘占用。

正是由于fallocate系统调用的“allocation”模式将的文件的一段全置零,所以我们正好可以用这一个调用先“allocation”,再“deallocation”测试文件打洞功能。

注意:虽然man page中写明只包含fcntl.h即可,但是在我的CentOS 7系统中还需要包含linux/falloc.h,否则会出现以下编译错误:

我们测试的例程如下:

运行结果:

可以看到无论是用struct stat中的st_blocks字段还是ls-s选项,都告诉我们”file_withhole”这个文件被打出了一个600 KB的洞(1000 K –> 400 K)。

打洞在MySQL页压缩中的应用

在我以前的文章中曾经分析过MySQL InnoDB的透明页压缩 [2],这种压缩机制就是基于文件打洞的。详细可以看下那篇博客,下边在简单说明下:

InnoDB以InnoDB页为单元进行存储,对于一般的情况,InnoDB页默认为16KB,文件系统默认为4KB。当InnoDB要存储一个页时,会对16KB进行压缩,压缩后大小为12KB,那么12KB到16KB之间的内容会首先被填零,然后用fallocate作“deallocation”打洞,这样额外的一个文件系统块就因压缩而被节约了;同样,若压缩后的页小于8 KB或小于4 KB,那么分别就可以节约8 KB 或 12 KB。


[1] fallocate – manipulate file space, http://man7.org/linux/man-pages/man2/fallocate.2.html

[2] MySQL InnoDB透明页压缩的简单分析, http://blog.jcix.top/2017-04-16/transparent_page_compression/

发表评论

电子邮件地址不会被公开。 必填项已用*标注