Linux の Sparse File について

ファイルシステムには、ゼロで埋められた部分を未使用領域として、実際のディスク容量を使用しない領域とする仕組みがある。Linux ではこれを Sparse File と呼んでいる。

Sparse File をうまく使うと、ディスク領域の節約になる一方で、Sparse File を正しく扱わない場合、無駄にゼロ埋められたファイルをつくることになり、ディスク容量を圧迫する。
ファイルコピーも遅くなる。

raw イメージをつくり、loopback mount して使うような場合に、適当に cp や rsync、tar 圧縮をすると、無駄な領域を使用してしまうことになる。

実験用の file を作る

truncate を使用して任意のサイズの sparse file を作成
$ truncate -s 100M ./sparse.bin

ls で確認すると、100MB のファイルにみえ、du では 0 byte となる。

$ ls -lh sparse.bin 
-rw-rw-r-- 1 user user 100M  1月 13 16:56 sparse.bin
$ du -sh sparse.bin 
0       sparse.bin

ls は論理的なファイルサイズを表し、du は実際に使用しているサイズを表している。
論理的には 100MB あり、実際には領域を使用していない状態となっている。

dd で /dev/zero のデータをコピーして、実領域を消費する file を作成
$ dd if=/dev/zero of=./not-sparse.bin bs=1M count=100

ls、du ともに、ほぼ 100MB のファイルとしてみえる。

$ ls -lh not-sparse.bin 
-rw-rw-r-- 1 user user 100M  1月 13 17:05 not-sparse.bin
$ du -sh not-sparse.bin 
101M    not-sparse.bin
組み合わせて、200MB のうち、半分だけ sparse な file を作成
$ truncate -s 100M ./half-sparse.bin
$ cat not-sparse.bin >> half-sparse.bin

ls で 200MB、du で 100MB の表示のファイルになる。

$ ls -lh half-sparse.bin 
-rw-rw-r-- 1 user user 200M  1月 14 14:42 half-sparse.bin
$ du -sh half-sparse.bin 
101M    half-sparse.bin

cp コマンドを --sparse オプション無しで使用した場合

もともとの sparse 領域の状態を維持してコピーされる。(Ubuntu 22.04 の cp コマンドの場合)

$ cp sparse.bin out1.bin
$ cp not-sparse.bin out2.bin
$ cp half-sparse.bin out3.bin
$ ls -lh out1.bin out2.bin out3.bin 
-rw-rw-r-- 1 user user 100M  1月 14 16:03 out1.bin
-rw-rw-r-- 1 user user 100M  1月 14 16:05 out2.bin
-rw-rw-r-- 1 user user 200M  1月 14 16:07 out3.bin
$ du -sh out1.bin out2.bin out3.bin 
0       out1.bin
101M    out2.bin
101M    out3.bin

cp コマンドを --sparse=always オプションとともに使用した場合

cp --sparse=alway を使うと、ゼロ埋められた領域を sparse 扱いにする。(ブロック単位)
すべてゼロで構成されたファイルであれば、0 byte になる。

$ cp --sparse=always sparse.bin out4.bin
$ cp --sparse=always not-sparse.bin out5.bin
$ cp --sparse=always half-sparse.bin out6.bin
$ ls -lh out4.bin out5.bin out6.bin 
-rw-rw-r-- 1 user user 100M  1月 14 16:15 out4.bin
-rw-rw-r-- 1 user user 100M  1月 14 16:16 out5.bin
-rw-rw-r-- 1 user user 200M  1月 14 16:16 out6.bin
$ du -sh out4.bin out5.bin out6.bin 
0       out4.bin
0       out5.bin
0       out6.bin

rsync コマンドを –sparse オプション無しで使用した場合

sparse 領域が、sparse ではない実領域を使用する状態になり、コピーされる。
(オプション無しの cp コマンドとは動作が異なる)

$ rsync sparse.bin out7.bin
$ rsync not-sparse.bin out8.bin
$ rsync half-sparse.bin out9.bin
$ ls -lh out7.bin out8.bin out9.bin 
-rw-rw-r-- 1 user user 100M  1月 14 17:02 out7.bin
-rw-rw-r-- 1 user user 100M  1月 14 17:02 out8.bin
-rw-rw-r-- 1 user user 200M  1月 14 17:02 out9.bin
$ du -sh out7.bin out8.bin out9.bin 
101M    out7.bin
100M    out8.bin
200M    out9.bin

rsync コマンドを --sparse オプションとともに使用した場合

rsync --sparse を使うと、ゼロ埋められた領域をブロック単位で sparse 扱いにする。
cp --spase 同様、すべてゼロで構成されたファイルであれば、0 byte になる。

$ rsync --sparse sparse.bin out10.bin
$ rsync --sparse not-sparse.bin out11.bin
$ rsync --sparse half-sparse.bin out12.bin
$ ls -lh out10.bin out11.bin out12.bin 
-rw-rw-r-- 1 user user 100M  1月 14 17:07 out10.bin
-rw-rw-r-- 1 user user 100M  1月 14 17:07 out11.bin
-rw-rw-r-- 1 user user 200M  1月 14 17:07 out12.bin
$ du -sh out10.bin out11.bin out12.bin 
0       out10.bin
0       out11.bin
0       out12.bin

tar コマンドの場合

--sparse オプションありと無しで、それぞれ、gzip + tar アーカイブを作成

$ tar zcvf test01.tgz half-sparse.bin 
$ tar zcvf test02.tgz --sparse half-sparse.bin 

サイズを比較すると、–sparse を付けたほうが少しファイルサイズが小さくなっている。

$ ls -lh test01.tgz test02.tgz 
-rw-rw-r-- 1 user user 199K  1月 14 17:21 test01.tgz
-rw-rw-r-- 1 user user 100K  1月 14 17:21 test02.tgz

解凍すると、--spase 無しでは、200 MBの実領域を使用するファイルとして展開されている。
一方、--spase ありでは、デフォルトの cp コマンド同様の動作で、半分だけ sparse の状態を維持して展開されている。(すべてゼロ埋めされたファイルであるものの、完全な sparse file とはならない。もともとの状態を維持している。)

$ mkdir out13 out14
$ tar zxf ./test01.tgz -C ./out13/
$ tar zxf ./test02.tgz -C ./out14/

$ ls -lh out13/half-sparse.bin out14/half-sparse.bin 
-rw-rw-r-- 1 user user 200M  1月 14 14:42 out13/half-sparse.bin
-rw-rw-r-- 1 user user 200M  1月 14 14:42 out14/half-sparse.bin

$ du -sh out13/half-sparse.bin out14/half-sparse.bin 
201M    out13/half-sparse.bin
101M    out14/half-sparse.bin

--sparse オプション無しで tar を使うと、半分は sparse で実領域を消費しないファイルだったものが、すべて実領域を消費するファイルとして展開されてしまう。
gzip 圧縮オプションで容量的にはそれほど変わらなくなるが、展開されたときに無駄なゼロ埋めされた領域を使用することになる。

tar の --sparse オプションは、もともとの sparse 状態を維持する動作であり、副次的な影響も考えにくいため、常に使っておくのが、恐らく無難といえる。
(デフォルトでこの状態になっていると、ありがたいのだが・・)