Linux
server
メモリ
サーバー
Cache

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

More than 1 year has passed since last update.


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

memory-day.png

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


メモリの使用状況の調査


meminfo

meminfoを見るとSlabのメモリ使用量が確認できる。SReclaimableとSUnreclaimを足すと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が確認できる。dentunusdがnegative 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


参照情報

https://www.kernel.org/doc/Documentation/sysctl/vm.txt


//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

http://futuremix.org/2009/09/clear-linux-memory-cach

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

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

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

http://blog.nomadscafe.jp/2014/01/tmpfile-and-dentry-cache.html

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環境変数で一時ファイルの場所は変更出来る

http://d.hatena.ne.jp/hiboma/20140212/1392131530

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 がキャッシュされるということになるようです

http://s-tajima.hateblo.jp/entry/2015/02/20/233615

アプリケーション内で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
改善された。

https://github.com/hiboma/hiboma/blob/master/kernel/dentry_cache.md

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 の結びつけ以外の働きはしない

http://wiki.ducca.org/wiki/Munin_の_Memory_で表示される各グラフの意味

slab_cache  inodeやdentryなど、カーネルが使うメモリ。[Slab]スラブアロケータのメモリ使用量。

cache 開放間近の分&デバイス(HDD等)用のを引いたキャッシュ量。 キャッシュ-(SwapCache+buffers)
buffers HDDとかのキャッシュ。まだHDDに書き込まれていないデータのキャッシュも含む。 各デバイスのbdev->bd_inode->i_mapping-nrpagesの総和

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

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

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

http://www.seeds-std.co.jp/seedsblog/488.html

slab_cache

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

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

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

committed

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

active

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

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

http://mkosaki.blog46.fc2.com/blog-entry-884.html

まず、ActiveとInactiveの違いは、メモリ不足発生時にInactiveのほうが先に回収可能性チェックをされる。という意味しかない。

Inactiveにもダーティーページ(ストレージが同期がとれていないページ)やmlockされたページなど捨てられないページは大量に混じっている。
また、Inactiveをチェックして、回収したりActiveに入れなおしたりして減ったInactiveのページ数はActiveからInactiveに落とすことにより、補充されるので、Activeであることも何も保障してない。

http://monoist.atmarkit.co.jp/mn/articles/1010/06/news107.html

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

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