わりと長い間悩んでいたんだけど、最近解決したのでメモ。
サービスで利用しているsmalllightの画像変換サーバが、Apacheが使っているメモリ以上のメモリを使用し、Swapしたりメモリ枯渇でサーバがダウンするなどのことが何度かありました。
↑メモリの動きはこんな感じ
いろいろ調べた結果「dentry cache」なるものがメモリ多くを占めていることがわかりました。dentry cacheはディレクトリやファイル名とinodeとを結びつけに使われるキャッシュです。smalllightでは画像を変換する際に一時ファイルを作成するので、その情報が残るようです。
手元で再現させる
本番で使っているサーバはCentOS5系ですが、手元のVagrant上のCentOS6(ファイルシステムはext4)で、再現させてみました。
use Parallel::Prefork;
use File::Temp qw/tempfile/;
my $pm = Parallel::Prefork->new({
max_workers => 50,
trap_signals => {
TERM => 'TERM',
HUP => 'TERM',
USR1 => undef,
}
});
while ($pm->signal_received ne 'TERM') {
$pm->start(sub {
srand();
select undef, undef, undef, rand(1);
while (1) {
select undef, undef, undef, 0.01;
my ($fh,$fn) = tempfile(UNLINK=>0);
syswrite $fh, 'x'x128;
close $fh;
unlink $fn;
}
});
}
$pm->wait_all_children();
まず、上記のscriptを作成し、100並列で一時ファイルを作りまくります。そしてdstatでメモリ使用量を観察します。
[vagrant@localhost ~]$ dstat -Tcms 30
--epoch--- ----total-cpu-usage---- ------memory-usage----- ----swap---
epoch |usr sys idl wai hiq siq| used buff cach free| used free
1389191041| 44 38 18 1 0 0|48.5M 2424k 10.1M 430M| 0 992M
1389191071| 38 49 13 0 0 0| 132M 3956k 12.5M 342M| 0 992M
1389191101| 46 54 0 0 0 0| 167M 4004k 12.5M 307M| 0 992M
1389191131| 48 52 0 0 0 0| 200M 4052k 12.5M 274M| 0 992M
1389191161| 44 56 0 0 0 0| 236M 4108k 12.5M 238M| 0 992M
1389191191| 45 54 0 0 0 0| 270M 4156k 12.5M 204M| 0 992M
1389191221| 48 52 0 0 0 0| 303M 4204k 12.5M 170M| 0 992M
1389191251| 49 50 0 0 0 0| 337M 4252k 12.5M 137M| 0 992M
1389191281| 49 50 0 0 0 0| 369M 4428k 12.5M 105M| 0 992M
1389191311| 47 53 0 0 0 0| 402M 4476k 12.5M 71.9M| 0 992M
1389191341| 49 51 0 0 0 0| 435M 4524k 12.5M 38.9M| 0 992M
1389191371| 52 48 0 0 0 0| 467M 4572k 12.5M 6348k| 0 992M
1389191401| 44 56 0 0 0 0| 389M 4356k 11.8M 85.4M| 0 992M <- 一回解放されてる?
1389191431| 42 58 0 0 0 0| 425M 4820k 12.6M 48.2M| 0 992M
1389191461| 45 55 0 0 0 0| 460M 4868k 12.6M 12.7M| 0 992M
1389191491| 43 57 0 0 0 0| 467M 4916k 12.6M 6104k| 0 992M
1389191521| 46 54 0 0 0 0| 467M 4964k 12.6M 6600k| 0 992M
1389191551| 44 56 0 0 0 0| 463M 5012k 12.6M 9700k| 0 992M
1389191553| 44 56 0 0 0 0| 466M 5020k 12.6M 7344k| 0 992M
30秒ごとに30MB強、メモリ使用量が増えて行き、物理メモリいっぱいいっぱいまで使います。その後はキャッシュを順次解放するのかメモリ使用量に大きな変化がなくなります。
dentry cacheがどの程度あるのかは、slabtopコマンドを使うと確認できます。
[vagrant@localhost ~]$ slabtop -o
Active / Total Objects (% used) : 1943159 / 1945730 (99.9%)
Active / Total Slabs (% used) : 97116 / 97129 (100.0%)
Active / Total Caches (% used) : 90 / 183 (49.2%)
Active / Total Size (% used) : 369732.02K / 370130.38K (99.9%)
Minimum / Average / Maximum Object : 0.02K / 0.19K / 4096.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
1875580 1875580 100% 0.19K 93779 20 375116K dentry
15680 15600 99% 0.03K 140 112 560K size-32
8732 7813 89% 0.06K 148 59 592K size-64
7102 7064 99% 0.07K 134 53 536K selinux_inode_security
5859 5833 99% 0.14K 217 27 868K sysfs_dir_cache
5776 5757 99% 0.20K 304 19 1216K vm_area_struct
5390 5349 99% 0.05K 70 77 280K anon_vma_chain
4212 4161 98% 0.58K 702 6 2808K inode_cache
3036 2935 96% 0.04K 33 92 132K anon_vma
dentry cacheはメモリが必要になれば解放されるらしいので、その検証のため、もう一つメモリを大量に使うプロセスを起動します。
perl -e 'my $x="x"x200_000_000;sleep(3600);
これは300MBぐらいメモリを確保します。
そしてまた、dstatで観察すると、なんと、swapしてしまいました。
[vagrant@localhost ~]$ dstat -Tcms 30
--epoch--- ----total-cpu-usage---- ------memory-usage----- ----swap---
epoch |usr sys idl wai hiq siq| used buff cach free| used free
1389191593| 45 46 9 0 0 0| 464M 5076k 12.6M 9196k| 0 992M
1389191623| 48 52 0 0 0 0| 467M 5124k 12.6M 6476k| 0 992M
1389191653| 55 45 0 0 0 0| 464M 5172k 12.6M 8936k| 0 992M
1389191683| 55 45 0 0 0 0| 459M 5220k 12.6M 13.6M| 0 992M
1389191713| 53 47 0 0 0 0| 437M 4824k 12.5M 35.9M| 76k 992M <- ここで起動
1389191743| 46 54 0 0 0 0| 482M 104k 2868k 6072k| 27M 965M
1389191773| 46 53 0 0 0 0| 481M 112k 2432k 6792k| 29M 963M
1389191803| 45 55 0 0 0 0| 483M 152k 1956k 5924k| 36M 956M
1389191833| 45 54 0 0 0 0| 482M 204k 2120k 6676k| 48M 944M
...
1389192371| 48 52 0 0 0 0| 475M 1972k 8184k 6156k| 141M 851M
1389192401| 48 52 0 0 0 0| 477M 1544k 6844k 5756k| 142M 850M
1389192431| 48 52 0 0 0 0| 478M 932k 6044k 5760k| 144M 848M
dentry cacheを解放するスピードが間に合わないのか、一部のメモリがswapに追い出され、さらに徐々にswapが増えて行くる結果となりました。
画像変換ではたまに大きなサイズの画像などがあり、メモリを多く必要とする場合があります。その際にswapしてしまい性能がダウンしてしまうことが予想されます。またこのままswapが増え続ければメモリ枯渇の可能性もあります。
dentry cacheの解放方法
swapやメモリ枯渇を防ぐめには、dentry cacheを早めに解放してメモリを空けておくのが良さそうです。その方法は2つあります。
一つ目は vm.drop_caches を使う方法
$ sudo /sbin/sysctl -w vm.drop_caches=2 または 3
# echo 2 > /proc/sys/vm/drop_caches
ディスクキャッシュをクリアする方法と同じです。2をいれるとdentry cacheのクリア。3をいれるとdentry cacheとディスクキャッシュをクリアします。
もう一つが、vm.vfs_cache_pressure を設定する方法
$ sudo /sbin/sysctl -w vm.vfs_cache_pressure=10000
デフォルトは100で、数字を大きくすると早めにキャッシュを解放するようです。しかし、かなり大きな数字にしてもサービスの本番環境では目立った変化はありませんでした。
tmpfsを使う方法
最後に発見したのがtmpfsを使う方法。tmpfsだと、dentry cacheは溜まらないようです。
環境変数TMPDIRにtmpfsを指定して先ほどのscriptを実行します。
[vagrant@localhost vagrant]$ TMPDIR=/dev/shm perl mktmp.pl
またdstatで観察すると、メモリが増えて行かないのが分かります。
[vagrant@localhost ~]$ dstat -Tcms 30
--epoch--- ----total-cpu-usage---- ------memory-usage----- ----swap---
epoch |usr sys idl wai hiq siq| used buff cach free| used free
1389168490| 47 35 17 0 0 0|44.3M 2428k 8804k 435M|8444k 984M
1389168520| 49 37 14 0 0 0| 146M 3924k 11.1M 330M|8444k 984M
1389168550| 60 40 0 0 0 0| 146M 3932k 11.1M 330M|8444k 984M
1389168580| 55 44 0 0 0 0| 146M 3932k 11.1M 330M|8444k 984M
1389168610| 56 44 0 0 0 0| 146M 4228k 12.4M 327M|8444k 984M
1389168640| 57 43 0 0 0 0| 144M 4388k 12.4M 330M|8444k 984M
1389168642| 57 43 0 0 0 0| 144M 4388k 12.4M 330M|8444k 984M^C
これは、tmpfsがLinuxのVFSを使わないからでしょうか。
ここまで分かったので、Apacheの起動スクリプトに
export TMPDIR=/dev/shm
を追加して再起動したところ、サーバが安定して動くようになりました。
↑今のメモリの動き
まとめ
- dentry cacheによりメモリが圧迫される可能性がある
- tmpfsを使うとdentry cacheは溜まらない
- TMPDIR環境変数で一時ファイルの場所は変更出来る
大量に一時ファイルを作成するようなアプリケーションはtmpfsを使うのが良い。そもそも速いので使わない手はないですね。