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

## サーバーのメモリが slab_cache で占有される
![memory-day.png](https://qiita-image-store.s3.amazonaws.com/0/15175/72ad2a37-e3e5-b3ea-d772-273f909df955.png)
サーバーのメモリが数日で `slab_cache` に占有されるので原因と対策を調査した。

## メモリの使用状況の調査
### meminfo
`meminfo` を見ると Slab のメモリ使用量が確認できる。 `SReclaimable` と `SUnreclaim` を足すと `Slab` になる。

```bash
$ 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` でも同様。

```bash
$ 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` を見ると詳細が分かる。

```bash
$ 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` で加工すると以下のようになる。

```bash
$ 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` オプションを付けると「ディレクトリ・キャッシュ内の未使用キャッシュ・エントリの数」[^13]である `dentunusd` が確認できる。 `dentunusd` が `negative dentry` の数になる。[^14]

```bash
$ sar -v 0
12:00:00 AM dentunusd   file-nr  inode-nr    pty-nr
12:00:00 AM   8637647       768     40414         1
```
[^13]: http://docs.oracle.com/cd/E39368_01/e48214/ch09s02.html#idm139984131471632
[^14]: http://qiita.com/joe_hrmn/items/f0fb72b4598607403f2f#dentrycache%E3%81%A8%E3%81%AF

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

[^15]: https://github.com/hiboma/hiboma/blob/master/kernel/dentry_cache.md

```bash
$ 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_caches` に `2` か `3` を書き込めば slab キャッシュは解放される。 `drop_caches` に書き込みする前に `sync` を実行するとより多く解放できる[^18]。

[^18]: https://www.kernel.org/doc/Documentation/sysctl/vm.txt

```bash:ページキャッシュの解放
sync; echo 1 > /proc/sys/vm/drop_caches
```
```bash:slabオブジェクト(dentryとinodeを含む)の解放
sync; echo 2 > /proc/sys/vm/drop_caches
```
```bash:slabオブジェクトとページキャッシュの解放
sync; echo 3 > /proc/sys/vm/drop_caches
```
しかし、解放後再び順調にキャッシュが増殖する。
![memory-day.png](https://qiita-image-store.s3.amazonaws.com/0/15175/72ad2a37-e3e5-b3ea-d772-273f909df955.png)

ちなみに `drop_caches` に `0` を書き込んでデフォルト状態に戻すという趣旨の記事[^19]が散見されるが、無視されるだけで意味がないようだ[^20]。
[^19]: http://www.atmarkit.co.jp/ait/articles/0810/01/news134_3.html
[^20]: http://mkosaki.blog46.fc2.com/blog-entry-885.html

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

```ini:/etc/sysconfig/httpd
export TMPDIR=/dev/shm
```
```bash
$ 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でよい。」[^17]と言っている人もいる。

```ini:/etc/sysconfig/httpd
export NSS_SDB_USE_CACHE=YES
```

[^16]: http://tecadmin.net/flush-memory-cache-on-linux-server/
[^17]: https://twitter.com/yuya_presto/status/545898513855291392

## そもそも slab_cache ってなに
### slab キャッシュ

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

[^1]: http://www.seeds-std.co.jp/seedsblog/488.html
[^2]: http://wiki.ducca.org/wiki/Munin_の_Memory_で表示される各グラフの意味
[^3]: http://www.linux-beginner.com/linux_kihon225.html
[^4]: http://www.atmarkit.co.jp/ait/articles/0810/01/news134_3.html
[^5]: http://benzaiten.dyndns.org/roller/ugya/entry/スラブキャッシュ
[^6]: http://www.atmarkit.co.jp/flinux/rensai/watch2008/watchmema.html
[^7]: http://softwaretechnique.jp/OS_Development/kernel_development14_1.html

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

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

### inode

### dentry キャッシュ

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

[^8]: http://d.hatena.ne.jp/hiboma/20140212/1392131530
[^9]: http://qiita.com/joe_hrmn/items/f0fb72b4598607403f2f
[^10]: http://blog.nomadscafe.jp/2014/01/tmpfile-and-dentry-cache.html
[^21]: http://osdn.jp/projects/linux-kernel-docs/wiki/internal22-87-ディレクトリエントリキャッシュ

## 参照情報
https://www.kernel.org/doc/Documentation/sysctl/vm.txt

> ```text://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](http://www.blackmoreops.com/2014/10/28/delete-clean-cache-to-free-up-memory-on-your-slow-linux-server-vps/)

> ```shell-session
> # 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 を設定する方法
> 
> ```bash
> $ sudo /sbin/sysctl -w vm.vfs_cache_pressure=10000
> ```
> デフォルトは100で、数字を大きくすると早めにキャッシュを解放するようです。しかし、かなり大きな数字にしてもサービスの本番環境では目立った変化はありませんでした。

> tmpfs だと、 dentry cache は溜まらないようです。
> 環境変数 TMPDIR に tmpfs を指定して先ほどの script を実行します。
>
> ```bash
> $ TMPDIR=/dev/shm perl mktmp.pl
> ```
> また dstat で観察すると、メモリが増えて行かないのが分かります。
> これは、 tmpfs が Linux の VFS を使わないからでしょうか。
> ここまで分かったので、 Apache の起動スクリプトに
>
> ```bash
> 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 ... 全破棄する訳じゃない

> ```c: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つの役割があります。
>
> ファイル名の管理
> ディレクトリ階層構造の管理
> キャッシュ管理