LoginSignup
420
425

More than 3 years have passed since last update.

サーバーのメモリが少しずつ圧迫される原因と対策を調べてみた

Last updated at Posted at 2015-11-21

サーバーのメモリが slab_cache で占有される

memory-day.png
サーバーのメモリが数日で slab_cache に占有されるので原因と対策を調査した。

メモリの使用状況の調査

meminfo

meminfo を見ると Slab のメモリ使用量が確認できる。 SReclaimableSUnreclaim を足すと Slab になる。

$ cat /proc/meminfo | grep "Slab\|claim"
Slab:            1654520 kB
SReclaimable:    1631304 kB
SUnreclaim:        23216 kB

slabtop

slabtop コマンドをたたくと top コマンドのように Slab の内訳が表示される。 dentry が最も多いようだ。 --once は1回出力で終了するオプション。 --sort=c はキャッシュサイズ順にソートするオプション。 slabtop -o -s c でも同様。

$ slabtop --once --sort=c | head -n 12
 Active / Total Objects (% used)    : 9220223 / 9293161 (99.2%)
 Active / Total Slabs (% used)      : 437042 / 437042 (100.0%)
 Active / Total Caches (% used)     : 65 / 89 (73.0%)
 Active / Total Size (% used)       : 1745015.88K / 1758078.67K (99.3%)
 Minimum / Average / Maximum Object : 0.01K / 0.19K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
8990394 8990394 100%    0.19K 428114       21   1712456K dentry
 22473  22473 100%    0.95K    681       33     21792K ext4_inode_cache
103740  67048  64%    0.10K   2660       39     10640K buffer_head
 52269  30144  57%    0.19K   2489       21      9956K kmalloc-192
 10475   9953  95%    0.62K    419       25      6704K proc_inode_cache

slabinfo

slabinfo を見ると詳細が分かる。

$ cat /proc/slabinfo | grep "dentry\|obj"
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
dentry            8662626 8662626    192   21    1 : tunables    0    0    0 : slabdata 412506 412506      0

見づらいので awk で加工すると以下のようになる。

$ cat /proc/slabinfo | grep "dentry\|obj" | awk '{ for (i=1; i<=NF; i++) { a[NR,i] = $i } } NF>p { p = NF } END { for(j=1; j<=p; j++) { str=a[1,j]; for(i=2; i<=NR; i++){ str=str"\t"a[i,j-1]; } print str } }' | grep '<'
<active_objs>   8662626
<num_objs>      8662626
<objsize>       192
<objperslab>    21
<pagesperslab>  1
<limit> 0
<batchcount>    0
<sharedfactor>  0
<active_slabs>  412506
<num_slabs>     412506
<sharedavail>   0

sar

sar コマンドに -v オプションを付けると「ディレクトリ・キャッシュ内の未使用キャッシュ・エントリの数」1である dentunusd が確認できる。 dentunusdnegative dentry の数になる。2

$ sar -v 0
12:00:00 AM dentunusd   file-nr  inode-nr    pty-nr
12:00:00 AM   8637647       768     40414         1

dentry-state

Linux 2.2以降。このファイルにはディレクトリキャッシュ (dcache) の状態に関する情報が入っている。3

$ cat /proc/sys/fs/dentry-state
9006589 8998515 45      0       0       0

nr_dentry, nr_unused, age_limit, want_pages, ダミー, ダミー、 という6つの数字が書かれている。

  • nr_dentry は割り当てられた dentry (dcache エントリ) の数である。 このフィールドは Linux 2.2 では使用されない。
  • nr_unused は未使用の dentry 数である。
  • age_limit は、メモリが不足している場合に次に dcache entry を再要求できるように なるまでの残り時間 (秒数) である。
  • want_pages は、システムがリクエストしたページ数。カーネルが shrink_dcache_pages() を呼び出したが dcache がまだ縮小されていない場合に、 0 以外の値となる。

slab キャッシュの解放

とりあえず drop_caches23 を書き込めば slab キャッシュは解放される。 drop_caches に書き込みする前に sync を実行するとより多く解放できる4

ページキャッシュの解放
sync; echo 1 > /proc/sys/vm/drop_caches
slabオブジェクト(dentryとinodeを含む)の解放
sync; echo 2 > /proc/sys/vm/drop_caches
slabオブジェクトとページキャッシュの解放
sync; echo 3 > /proc/sys/vm/drop_caches

しかし、解放後再び順調にキャッシュが増殖する。
memory-day.png

ちなみに drop_caches0 を書き込んでデフォルト状態に戻すという趣旨の記事5が散見されるが、無視されるだけで意味がないようだ6

解決策

cron で上記のキャッシュ解放を定期実行させる記事7が多かったが、そもそもそんなんでいいのか。
もう少し根本的な対応はないのかとさらに調べた結果、 /etc/sysconfig/httpdTMPDIR=/dev/shm を指定するか NSS_SDB_USE_CACHE=YES を指定するかの2択がある模様。

tmpfs

/etc/sysconfig/httpd
export TMPDIR=/dev/shm
$ echo $TMPDIR
/dev/shm
$ php -r 'echo sys_get_temp_dir(),"\n";'
/dev/shm

NSS_SDB_USE_CACHE

NSS_SDB_USE_CACHE は NSS Softoken のバージョンが3.16.0以上である必要があるようなので yum list nss-softokn でバージョンを確認する。
ただ、 NSS_SDB_USE_CACHE は「ソースコードを読むと実はNOのほうが良さそうな気がした。NFSを使ってない限りNOでよい。」8と言っている人もいる。

/etc/sysconfig/httpd
export NSS_SDB_USE_CACHE=YES

そもそも slab_cache ってなに

slab キャッシュ

  • スラブアロケータで使用されている物理メモリの総容量9
  • inode や dentry など、カーネルが使うメモリ。10
  • カーネルは、メモリの利用効率を高めるために、カーネル空間内のさまざまな メモリ資源を、資源ごとにキャッシュをする仕組みを備えています。 これを「スラブキャッシュ」と呼びます。11
  • Slab キャッシュはディレクトリのメタデータ情報を格納する dentry やファイルのメタデータ情報を格納する inode 構造体などをキャッシュしているカーネル内のメモリ領域です。これらはページキャッシュとは別にカーネルの中に確保されており、ストレージと同期していれば解放することができます。12
  • kernel の slab キャッシュには SReclaimable (回収可能)と SUnreclaim (回収不可)なものがある。 /proc/meminfo を cat すると現在のスラブの使用状況がわかる。13
  • Linux において、ページサイズ未満のメモリは SLAB (スラブ)という仕組みで管理されています。 SLAB は、メモリ割り当て時、部分的に使用中のページがあればそこから優先的にメモリを割り当てる仕組みを持っています。このため、 SLAB を用いた確保と解放が交互に動いているような状況では、無駄な領域はほとんど発生しません。しかし、メモリ確保が非常にたくさん連続して発生した後に、メモリ解放がこれまた連続して発生すると、部分的に使用中のメモリが大量にできてしまうことになります。これが SLAB のフラグメンテーションと呼ばれる問題です。 SLAB のヘビーユーザーには、 inode キャッシュと dentry キャッシュがあります。それぞれ、ファイルのメタデータとパス名をキャッシュするものですが、不幸なことにこの2つでは、 find や updatedb コマンドによって簡単に上記のワーストケースが発生してしまいます。14
  • オブジェクトはカーネルが使用する構造体(単に普通の変数でもかまいません)となります。カーネルが実際にプログラムで使用するのが”オブジェクト”単位となり、スラブアロケーターの割り当て/解放を行う単位もオブジェクト単位となります。スラブアロケーターでは一番小さい括りとなります。オブジェクトを一つのグループに一括りにしたものが”スラブ”となります。スラブアロケーターではスラブ単位でスラブ内のオブジェクトは完全に割り当て済み、オブジェクトの一部が割り当て済み、オブジェクトの全てが完全にフリーといった状態管理を行います。15

スラブアロケータ (slab allocator)

  • SunOS のためにジェフ・ボンウィック(Jeff Bonwick)が導入したアルゴリズム。ジェフは通常オブジェクトの割り振りと廃棄に多くの時間が費やされていることを発見。メモリーを解放してグローバルプールに返却するのではなく、オブジェクトを、その用途で初期化されたままの状態で保持して再利用する方式を発案。13

inode

dentry キャッシュ

  • ディレクトリエントリキャッシュ。16
  • dentry キャッシュは、 RAM より低速な HDD や SSD などの、二次記憶装置からのディレクトリエントリの読み取りをキャッシュしておき、高速化するものです。17
  • ファイル名やディレクトリの階層構造、またディレクトリ名と inode 情報を関連付けるもの(構造体)らしい。 dentry_cache は、それらの情報のキャッシュだと思って良さそう。また dentry_cache は存在しないディレクトリ情報(inode を持たない dentry)なんかもキャッシュする(これを negative dentry というらしい)。 negative dentry は、 sar コマンド(-vオプション)で確認する事ができる。18
  • dentry cache はディレクトリやファイル名と inode とを結びつけに使われるキャッシュです。 dentry cache がどの程度あるのかは、 slabtop コマンドを使うと確認できます。19

参照情報

//www.kernel.org/doc/Documentation/sysctl/vm.txt
drop_caches

Writing to this will cause the kernel to drop clean caches, as well as
reclaimable slab objects like dentries and inodes.  Once dropped, their
memory becomes free.

To free pagecache:
  echo 1 > /proc/sys/vm/drop_caches
To free reclaimable slab objects (includes dentries and inodes):
  echo 2 > /proc/sys/vm/drop_caches
To free slab objects and pagecache:
  echo 3 > /proc/sys/vm/drop_caches

This is a non-destructive operation and will not free any dirty objects.
To increase the number of objects freed by this operation, the user may run
`sync' prior to writing to /proc/sys/vm/drop_caches.  This will minimize the
number of dirty objects on the system and create more candidates to be
dropped.

This file is not a means to control the growth of the various kernel caches
(inodes, dentries, pagecache, etc...)  These objects are automatically
reclaimed by the kernel when memory is needed elsewhere on the system.

Use of this file can cause performance problems.  Since it discards cached
objects, it may cost a significant amount of I/O and CPU to recreate the
dropped objects, especially if they were under heavy use.  Because of this,
use outside of a testing or debugging environment is not recommended.

You may see informational messages in your kernel log when this file is
used:

  cat (1234): drop_caches: 3

These are informational only.  They do not mean that anything is wrong
with your system.  To disable them, echo 4 (bit 3) into drop_caches.

Delete clean cache to free up memory on your slow Linux server, VPS - blackMORE Ops

# crontab -e
0 * * *  * sync; echo 3 > /proc/sys/vm/drop_caches

Linux の top コマンドや free コマンドで表示される cached は、勝手にどんどん増えていきます。 free がなくなるとこの cached が少しずつ解放されて使われます。

cached も buffers も空きメモリの一部ですので、これらが溜まっているからといって無理やり解放させる必要は通常はありません。cached を溜めているのはパフォーマンスのためなので、解放させると通常使用時のパフォーマンスが落ちます。逆にこれを解放しておかないと、パフォーマンスの測定などで、正しい計測ができません。

kernel の 2.6.16 以降では、解放をコマンドから解放できるようになりました。

dentry cache はディレクトリやファイル名と inode とを結びつけに使われるキャッシュです。

dentry cache がどの程度あるのかは、 slabtop コマンドを使うと確認できます。

dentry cache はメモリが必要になれば解放されるらしいので、その検証のため、もう一つメモリを大量に使うプロセスを起動します。
そしてまた、dstat で観察すると、なんと、 swap してしまいました。
dentry cache を解放するスピードが間に合わないのか、一部のメモリが swap に追い出され、さらに徐々に swap が増えて行くる結果となりました。

vm.vfs_cache_pressure を設定する方法

$ sudo /sbin/sysctl -w vm.vfs_cache_pressure=10000

デフォルトは100で、数字を大きくすると早めにキャッシュを解放するようです。しかし、かなり大きな数字にしてもサービスの本番環境では目立った変化はありませんでした。

tmpfs だと、 dentry cache は溜まらないようです。
環境変数 TMPDIR に tmpfs を指定して先ほどの script を実行します。

$ TMPDIR=/dev/shm perl mktmp.pl

また dstat で観察すると、メモリが増えて行かないのが分かります。
これは、 tmpfs が Linux の VFS を使わないからでしょうか。
ここまで分かったので、 Apache の起動スクリプトに

export TMPDIR=/dev/shm

を追加して再起動したところ、サーバが安定して動くようになりました。

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

negative dentry とは 存在しない inode に対応する dentry です。

dentry キャッシュは、 RAM より低速な HDD や SSD などの、二次記憶装置からのディレクトリエントリの読み取りをキャッシュしておき、高速化するものです。

一方、 negative dentry は 存在しないディレクトリエントリの読み取りをキャッシュし、高速化します。

「存在しないのにキャッシュ?」がしばらくイミフだったのでしたが、 DNS のネガティブキャッシュの動作に似ているなと思いました (存在しないレコード情報をキャッシュして問い合わせを減らし高速化する)

slabtop では通常の dentry なのか negative dentry なのか判別できません ...

sar -w でも確認できます (negative entry は dentunusd にカウントされます)

/tmp に同じパス名でファイル作成や削除を繰り返すようなケースや、PerlやRubyのような言語のライブラリ/モジュールの探索 (例: Perl がモジュールを探すために @INC配列のディレクトリ群を走査する際、存在しないパスにも open(2) しまくって探している ) を考えてみましょう。

存在しないディレクトリエントリを毎回 HDD に問い合わせていると I/O まくりで遅そうですよね。ここで、 negative dentry のキャッシュが有効に作用してきそうです。

一方で File::Temp の tmpfile() のようにランダムなパスを生成し同一パスの再利用を避けるケースと相性が悪いことは kazeburo さんのブログでも実証されています。

tmpfs では negative dentry はキャッシュされません。 unlink/rmdir すると dentry はすぐに破棄されます。

tmpfs では ファイルシステムのデータ(inode 情報など) はメモリ(ページ)上に保持されていて高速に探索できます。二次記憶装置が無いので、存在しないディレクトリエントリを速く探すための negative dentry は無くてもいいということなのですね。

ext3 や ext4 などでは .d_delete は特に実装されておらず、negative dentry がキャッシュされるということになるようです

アプリケーション内で https による外部 API を叩いているサーバのメモリ使用量が増加し続ける件について調べた。

調査すると、このバグ仕様を踏んでいるのではないかと思われるページを見つけた。
https://bugzilla.redhat.com/show_bug.cgi?id=1044666

内容としては、 curl を実行した際に
/etc/pki/nssdb/ 以下の存在しないファイル(毎回違うパス)に対して access システムコールが大量にコールされ、
negative dentry cache が溜まっていき、メモリ使用量が圧迫されるというもの。

なぜこんなことになるのかというと、
https://hg.mozilla.org/projects/nss/file/4d876e0ee318/security/nss/lib/softoken/sdb.c#l372
ファイルシステムの性能を検査して、キャッシュを使うかどうかの判断をしているらしい。

対応としては、
https://bugzilla.redhat.com/show_bug.cgi?id=1044666#c8
このパッチで使えるようになった、 NSS_SDB_USE_CACHE のオプションを有効にして、
ファイルシステムの検査をせずにキャッシュを使うようにすればいいようだ。

$ export NSS_SDB_USE_CACHE=yes ※phpのコード内でputenv("NSS_SDB_USE_CACHE=yes”)でもよい。
$ strace -fc -e trace=access php test.php
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  -nan    0.000000           0        12         7 access
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    12         7 total

改善された。

dentry は4つの状態をもつ
* free
* nonused
* used
* negative
negative は DNS のネガティブキャッシュとよく似ている (存在しないレコードの lookup を高速化)

dentry の使用量/数を調べる

slabtop
/proc/sys/fs/dentry-state

/proc/sys/fs/dentry-state (Linux 2.2 以降)
このファイルには、ディレクトリキャッシュ (dcache) の状態に関する情報が入っている。
ファイルには、 nr_dentry, nr_unused, age_limit (秒単位の age), want_pages (システムがリクエストしたページ数), ダミーの 2 つの値、 という 6 つの数字が書かれている。

  • nr_dentry は割り当てられた dentry (dcache エントリ) の数である。 このフィールドは Linux 2.2 では使用されない。
  • nr_unused は未使用の dentry 数である。
  • age_limit は、メモリが不足している場合に次に dcache entry を再要求できるように なるまでの残り時間 (秒数) である。
  • want_pages は、カーネルが shrink_dcache_pages() を呼び出したが dcache がまだ縮小されていない場合に、0 以外の値となる。

dentry cache 疑問

dentry cache のサイズに制限をかけられるか ?
できなそう
vfs_cache_pressure を高い数値にしておくしかないのかな?

procfs の drop_cache

/proc/sys/vm/drop_caches
sysctl -w vm.drop_caches=N でも同じ
下記のビット演算で 1 と 2 と 3 に対応する。なるほど
invalidate_mapping_pages ... 全部破棄
shrink_slab ... 全破棄する訳じゃない

linux/fs/drop_caches.c
        if (sysctl_drop_caches & 1)
            drop_pagecache();
        if (sysctl_drop_caches & 2)
            drop_slab();

tmpfs は .lookup する際に dentry_operations に .d_delete をセットしている
backing store が RAM の場合は、削除済みファイルの dentry をキッシュしてもメモリと探索時間の無駄になるので、すぐに消すとの事
backing store からの lookup を速くするキャッシュとしての意味は無いし、dentry 増えてハッシュ値の比較回数が無駄に増える
filename と Inode の結びつけ以外の働きはしない

slab_cache inode や dentry など、カーネルが使うメモリ。[Slab]スラブアロケータのメモリ使用量。
cache 開放間近の分&デバイス(HDD 等)用のを引いたキャッシュ量。 キャッシュ-(SwapCache+buffers)
buffers HDD とかのキャッシュ。まだ HDD に書き込まれていないデータのキャッシュも含む。 > 各デバイスのbdev->bd_inode->i_mapping-nrpagesの総和

committed 割り当てられたすべてのメモリが使用されることであるなら使用されるメモリの量。

active 最近アクセスされたページ。基本的に開放の対象外。

inactive 最近アクセスが無い。空きが無くなると、この部分のページから開放されていく。

slab_cache
スラブアロケータで使用されている物理メモリの総容量

cache
ファイルデータのキャッシュなどに使用している物理メモリの総容量。共有メモリは Cached に加算される。 SwapCached は含まない
cache-(SwapCache+buffers)

buffers
ファイルなどのメタデータとして使用している物理メモリの総容量

committed
全プロセスによって確保された仮想メモリの総容量。

active
最近アクセスした物理メモリの容量

inactive
最近アクセスしていない解放してよい物理メモリの容量

まず、 Active と Inactive の違いは、メモリ不足発生時に Inactive のほうが先に回収可能性チェックをされる。という意味しかない。
Inactive にもダーティーページ(ストレージが同期がとれていないページ)や mlock されたページなど捨てられないページは大量に混じっている。
また、 Inactive をチェックして、回収したり Active に入れなおしたりして減った Inactive のページ数は Active から Inactive に落とすことにより、補充されるので、 Active であることも何も保障してない。

dentry には、主に以下のような3つの役割があります。

ファイル名の管理
ディレクトリ階層構造の管理
キャッシュ管理

420
425
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
420
425