わりと長い間悩んでいたんだけど、最近解決したのでメモ。

サービスで利用しているsmalllightの画像変換サーバが、Apacheが使っているメモリ以上のメモリを使用し、Swapしたりメモリ枯渇でサーバがダウンするなどのことが何度かありました。

dentry_cache1.png

↑メモリの動きはこんな感じ

いろいろ調べた結果「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_cache2.png

↑今のメモリの動き

まとめ

  • dentry cacheによりメモリが圧迫される可能性がある
  • tmpfsを使うとdentry cacheは溜まらない
  • TMPDIR環境変数で一時ファイルの場所は変更出来る

大量に一時ファイルを作成するようなアプリケーションはtmpfsを使うのが良い。そもそも速いので使わない手はないですね。

このブログ記事について

このページは、Masahiro Naganoが2014年1月 9日 00:13に書いたブログ記事です。

ひとつ前のブログ記事は「CentOS 6.5にDockerいれてGrowthForecastを動かしてみた」です。

次のブログ記事は「ロケールに影響されずにGNU互換のstrftime(3)が使えるPOSIX::strftime::Compilerというモジュールを書きました」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 4.27-ja