この記事は「エムスリー Advent Calendar 2015」の 15 日目の記事です。
きっかけはこれ
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg01-lv_apps
18G 18G 184M 99% /apps
は!?毎分監視してるのにいきなり90%や95%をすっ飛ばして99%のアラートが!
# du -sh .
17G .
# ls -lh
total 17G
-rw-r--r-- 1 root root 8.4G Sep 29 23:41 20150929-www-access.ltsv
-rw-r--r-- 1 root root 93K Sep 29 23:40 20150929-www-error.log
どいうこと? 8.4GB のファイルがあるだけなのになんで 17GB 使ってることになってんの?
ls や find で見つからないのにディスクが溢れそうっていうのは通常削除済みのファイルを開いているプロセスが存在してるパターンなのだが今回はそれではない。
なにかのバグでファイルがおかしくなっているのだろうか?
# ls -lh
total 17G
-rw-r--r-- 1 root root 8.5G Sep 29 23:59 20150929-www-access.ltsv
-rw-r--r-- 1 root root 95K Sep 29 23:56 20150929-www-error.log
-rw-r--r-- 1 root root 21M Sep 30 00:02 20150930-www-access.ltsv
# du -sh *
17G 20150929-www-access.ltsv
96K 20150929-www-error.log
31M 20150930-www-access.ltsv
しばらくすると戻った。なんじゃこりゃ?(日付が変わってファイルが close されたのも関係ある?)
# ls -lh
total 8.6G
-rw-r--r-- 1 root root 8.5G Sep 29 23:59 20150929-www-access.ltsv
-rw-r--r-- 1 root root 95K Sep 29 23:56 20150929-www-error.log
-rw-r--r-- 1 root root 55M Sep 30 00:07 20150930-www-access.ltsv
-rw-r--r-- 1 root root 158 Sep 30 00:04 20150930-www-error.log
# du -sh *
8.5G 20150929-www-access.ltsv
96K 20150929-www-error.log
63M 20150930-www-access.ltsv
4.0K 20150930-www-error.log
RHEL も 7 から xfs がデフォルトになったし RDBMS in the Cloud: PostgreSQL on AWS (PDF) にもデータ領域には xfs 使えって書いてあるし時代は xfs だろうということで選択していた xfs が怪しいんだろうなと思ったもののよくわからない、この後も再発したため、とりあえず ext4 で作りなおすことで回避していたものの数カ月後、別のサーバーでも発生したのでこれは xfs に仕掛けがあるんだろうと調べてみた。
ググってみると「なんじゃこりゃ?」と思ったのは私だけではなかったっぽい。 xfs には speculative preallocation というファイル拡張時に大きめの連続した領域を確保することでフラグメントを抑えてパフォーマンスの低下を防ごうという機能が存在するようです。
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=055388a3188f56676c21e92962fc366ac8b5cb72
この commit 後にも修正されているのでこれは最新のコードではありません。 kernel-2.6.32-573.8.1.el6.src.rpm を見てみました。
では、この preallocation で同様の状況が再現可能か試してみます。
次の簡単なスクリプトを用意します。
#!/usr/bin/perl
use strict;
use warnings;
sub bytes {
my ($size) = @_;
if ($size =~ s/g\z//i) {
$size *= 1024 * 1024 * 1024;
}
elsif ($size =~ s/m\z//i) {
$size *= 1024 * 1024;
}
elsif ($size =~ s/k\z//i) {
$size *= 1024;
}
return $size;
}
if (scalar(@ARGV) != 4) {
print "Usage: $0 /path/to/src_file /path/to/out_file bs size\n";
exit(1);
}
my ($src_file, $out_file, $bs, $size) = @ARGV;
my $buf;
my $wrote = 0;
$bs = bytes($bs);
$size = bytes($size);
my $w;
open($w, ">>", $out_file)
or die "cannot open '$out_file': $!";
my $r;
open($r, "<", $src_file)
or die "cannot open $src_file: $!";
sleep 3;
while ($wrote < $size) {
sysread($r, $buf, $bs);
syswrite($w, $buf, length($buf));
$wrote += length($buf);
}
close($r);
close($w);
DigitalOcean の CentOS 6.7 x86_64 サーバーで 30GB の xfs パーティションを作ってテストしてみます。
truncate -s 30G /xfs-disk
mkfs -t xfs /xfs-disk
mkdir /xfs
mount -o loop -t xfs /xfs-disk /xfs
perl xfs-preallocation-test.pl /dev/zero /xfs/test 8k 8.5g &
watch -n 1 'ls -l /xfs/test; ls -lh /xfs/test; du -sh /xfs/test; xfs_bmap -v /xfs/test'
この処理の出力を asciinema にアップしました。ファイルサイズ拡張時に一気に 2GB や 4GB 増える様子が確認できます。
https://asciinema.org/a/e8mbpx26cxkqvr1zkad30i9ht
(ttyrec より共有が楽ちんですね、手元にファイルで保存しておくこともできるし便利)
/dev/zero から 8.5G を書き込んだファイルのフラグメント状態を確認すると今回は次のように 5.8GB, 277MB, 1.7GB, 876MB に分断されていました。
[root@xfstest ~]# xfs_bmap -v /xfs/test
/xfs/test:
EXT: FILE-OFFSET BLOCK-RANGE AG AG-OFFSET TOTAL
0: [0..11936783]: 96..11936879 0 (96..11936879) 11936784
1: [11936784..12505039]: 15728704..16296959 1 (64..568319) 568256
2: [12505040..16031583]: 24117296..27643839 1 (8388656..11915199) 3526544
3: [16031584..17825791]: 19923088..21717295 1 (4194448..5988655) 1794208
このような細切れのファイルでは 8.5GB のファイルなのに 17GB も割り当てられるような状況にならないので状況再現のために xfs_fsr
でデフラグします。
[root@xfstest ~]# xfs_fsr /xfs/test
[root@xfstest ~]# xfs_bmap -v /xfs/test
/xfs/test:
EXT: FILE-OFFSET BLOCK-RANGE AG AG-OFFSET TOTAL
0: [0..3791751]: 11936880..15728631 0 (11936880..15728631) 3791752
1: [3791752..17825791]: 31488064..45522103 2 (30784..14064823) 14034040
[root@xfstest ~]#
1.8GB と 6.8GB にまとまりました。
ここで先ほどと同じスクリプトでファイルに 3GB ほど追記してみます。(3GB も要らなかった)
perl xfs-preallocation-test.pl /dev/zero /xfs/test 8k 3g &
watch -n 1 'ls -l /xfs/test; ls -lh /xfs/test; du -sh /xfs/test; xfs_bmap -v /xfs/test'
8.5GB にちょいと追記しただけで今度は 17GB まで膨れ上がりました、そして、書き込み終了後もしばらく 17GB のままです(なんだか見覚えのある感じです)。
[root@xfstest ~]# date; ls -lh --full-time /xfs/test; du -h /xfs/test
Sun Dec 13 21:51:09 EST 2015
-rw-r--r-- 1 root root 12G 2015-12-13 21:49:56.831999975 -0500 /xfs/test
17G /xfs/test
[root@xfstest ~]# date; ls -lh --full-time /xfs/test; du -h /xfs/test
Sun Dec 13 21:54:30 EST 2015
-rw-r--r-- 1 root root 12G 2015-12-13 21:49:56.831999975 -0500 /xfs/test
17G /xfs/test
[root@xfstest ~]# date; ls -lh --full-time /xfs/test; du -h /xfs/test
Sun Dec 13 21:54:34 EST 2015
-rw-r--r-- 1 root root 12G 2015-12-13 21:49:56.831999975 -0500 /xfs/test
12G /xfs/test
5分近く 17GB をキープしていました。これは fs.xfs.speculative_prealloc_lifetime の値が効いていそうです。
[root@xfstest ~]# sysctl fs.xfs.speculative_prealloc_lifetime
fs.xfs.speculative_prealloc_lifetime = 300
[root@xfstest ~]# cat /proc/sys/fs/xfs/speculative_prealloc_lifetime
300
おそらくこれれすね、あの 99% になった現象は。毎日発生するわけではなかったのでたまたまうまい具合に連続したエクステントを確保できてしまった場合に一気に大きく割り当てられてしまったのでしょう。一度の拡張は最大で8GB (デフォルトの bsize=4096 の場合)。ボリュームの空き容量やクオータ制限での残り書き込み可能サイズでこの値は小さくなります。
freespace max prealloc size
>5% full extent (8GB)
4-5% 2GB (8GB >> 2)
3-4% 1GB (8GB >> 3)
2-3% 512MB (8GB >> 4)
1-2% 256MB (8GB >> 5)
<1% 128MB (8GB >> 6)
こんなのやだよ、無効にしたいよという場合にはどうするかというと、マウントオプションで allocsize を明示的に指定すれば良いようです。パフォーマンスを気にする必要の無いログサーバーなどであれば無駄に確保されるより良さそうです。
allocsize=size
Sets the buffered I/O end-of-file preallocation size when
doing delayed allocation writeout (default size is 64KiB).
Valid values for this option are page size (typically 4KiB)
through to 1GiB, inclusive, in power-of-2 increments.
The default behaviour is for dynamic end-of-file
preallocation size, which uses a set of heuristics to
optimise the preallocation size based on the current
allocation patterns within the file and the access patterns
to the file. Specifying a fixed allocsize value turns off
the dynamic behaviour.
これで少し安心して xfs が使えそうです。