☀️

プロセスにもslabにも記録されないメモリ使用領域を調べてたら、zfsのarcサイズ設定が原因だった話

2021/11/08に公開

概要

自分で構築したProxmox VE(PVE)のwebコンソールを眺めていたら、
4GBメモリのVMを4台しか起動していないのに仮想ホストでは48GB中37GBもメモリが使用されていた。

それの原因を突き止めるために調べていたらzfsが原因ということが判明した。
何も知らず、何も気にせず、zfsでPVEを構築していたら気付かない事象だと感じたのと、
メモリについて知ったことが多いため記事にした。

調査1(ホスト再起動)

困った時はホスト再起動するのが一番ということで、まずは再起動。

再起動後はメモリをほとんど使用されていなく、VM起動してもVMのメモリ領域くらいしか使われていない。
これで解決したと思い放置していたら、いつの間にかメモリ使用量が増えている。

なので変なプロセスがいるとかではなく、何かが起きていることがわかった。

調査2(プロセス確認)

まずはメモリ使用量を確認する。

//現在のメモリ使用量を確認
# free -mw
              total        used        free      shared     buffers       cache   available
Mem:          48040       37157         648         108        9731         503       10185
Swap:             0           0           0

バッファが多いような気もするけど、確かに37GB使用されている。

では何にメモリが使用されているかプロセスを確認してみる。

//プロセス確認(コマンド後shift+Mでメモリ使用率順にソート)
# top
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 8932 root      20   0 5467156   4.0g   6952 S  13.3   8.6 135:47.92 kvm
 8994 root      20   0 6172800   4.0g   7080 S   1.7   8.6 126:44.49 kvm
 9270 root      20   0 5041216   1.8g   7020 S   1.3   3.8  24:11.56 kvm
 8779 root      20   0 5009348 621104   6944 S   0.3   1.3   4:57.46 kvm
25513 www-data  20   0  371460 140604  10096 S   0.0   0.3   0:02.12 pveproxy +

COMMANDのkvmが1VMになり、4台のVMを起動している状態。
メモリ使用率順にソートしているので、メモリを多く使用しているプロセスは上から4つである。
メモリ使用率上位4つの%の合計が22.3%、仮想ホストの搭載メモリは48GBなので、
プロセスでのメモリ使用量は約10GBになる。

プロセスでは10GBしか使用していないのに、ホストは37GB使用していると言っている。
この27GBの差は何なのかわからずインターネットであれこれ調べてみると、
メモリはプロセス以外にもカーネルなどにも使用されていて、カーネルなどで使用されているメモリ使用量はtopでは表示されないことが判明。
なので、次はそちらを調べてみる。

調査3(meminfo確認)

カーネルが管理しているメモリ情報は、/proc/meminfoに記述されているため、それを見てみる。

//meminfo確認
# cat /proc/meminfo
MemTotal:       49193332 kB
MemFree:          667144 kB
MemAvailable:   10436952 kB
Buffers:         9968968 kB
Cached:           192372 kB
SwapCached:            0 kB
Active:         13963108 kB
Inactive:        8678740 kB
Active(anon):   12485468 kB
Inactive(anon):   109624 kB
Active(file):    1477640 kB
Inactive(file):  8569116 kB
Unevictable:        5320 kB
Mlocked:            5320 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                28 kB
Writeback:            12 kB
AnonPages:      12485828 kB
Mapped:           129524 kB
Shmem:            110640 kB
KReclaimable:     322916 kB
Slab:            3195792 kB
SReclaimable:     322916 kB
SUnreclaim:      2872876 kB
KernelStack:        5296 kB
PageTables:        33652 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    24596664 kB
Committed_AS:   22302648 kB
VmallocTotal:   34359738367 kB
VmallocUsed:      681608 kB
VmallocChunk:          0 kB
Percpu:             3120 kB
HardwareCorrupted:     0 kB
AnonHugePages:  11618304 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:      781344 kB
DirectMap2M:    44146688 kB
DirectMap1G:     5242880 kB

情報多すぎて何を見ればいいのかわからない...
大事なのはslabらしいので、それだけを抽出してみる。

//meminfoからslab関係の情報だけ抽出
# cat /proc/meminfo | grep "Slab\|claim"
KReclaimable:     322956 kB
Slab:            3202664 kB
SReclaimable:     322956 kB
SUnreclaim:      2879708 kB

slab領域というのがあり、それがカーネルが使用するメモリの総容量になる。
カーネルは、メモリの利用効率を高めるために、カーネル空間内のさまざまな メモリ資源を、
資源ごとにキャッシュをする仕組みを備えていてこれを「slabキャッシュ」と呼ぶそう。

Slab = SReclaimable + SUnreclaim になるので、slabキャッシュは3GBほど。
SReclaimable (回収可能)と SUnreclaim (回収不可)になるので、slabキャッシュクリアを仮にしたとしても、300MBしか解放されない。
バッファの10GBと合わせても、謎の使用量27GBには及ばない。
ということは、メモリを大量に使用しているのはslabキャッシュでもなさそう。

余談だが、slabがメモリ使用量に影響している場合の大半は、dentryというファイルとinodeを紐付けるキャッシュがメモリ圧迫の原因となっていることが多いよう。
slabtopコマンドや、cat /proc/slabinfoで確認することができる。

対応1(キャッシュクリア)

バッファやslabのキャッシュをクリアしたとしても解決にはならないと思いつつも、
何も思いつくことはないし、キャッシュクリアしてみる。
/proc/sys/vm/drop_caches に1 or 2 or 3 を書き込むことで、対応する領域のキャッシュがクリアされる。
1 : ページキャッシュ
2 : slabキャッシュ
3 : ページ + slabキャッシュ

ついでだけどこのキャッシュクリア方法は、パフォーマンスに影響が出る可能性があるので、本番機ではやらないとかタイミングを見てやるとかしたほうが良い。

//ページキャッシュ(バッファ)のクリア
# echo 1 >/proc/sys/vm/drop_caches

//メモリ使用量確認
# free -m
              total        used        free      shared  buff/cache   available
Mem:          48040       37231       10631         108         177       10306
Swap:             0           0           0

確かにバッファは消えたが、usedは変わらず

//slabキャッシュのクリア
# echo 2 > /proc/sys/vm/drop_caches

# free -m
              total        used        free      shared  buff/cache   available
Mem:          48040       14282       33585         108         172       33257
Swap:             0           0           0

//meminfo確認
# cat /proc/meminfo
MemTotal:       49193332 kB
MemFree:        34541064 kB
MemAvailable:   34207944 kB
Buffers:            1404 kB
Cached:           147228 kB
SwapCached:            0 kB
Active:         12492872 kB
Inactive:         119712 kB
Active(anon):   12468284 kB
Inactive(anon):   110252 kB
Active(file):      24588 kB
Inactive(file):     9460 kB
Unevictable:        5320 kB
Mlocked:            5320 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                36 kB
Writeback:             0 kB
AnonPages:      12469276 kB
Mapped:           129648 kB
Shmem:            110640 kB
KReclaimable:      32620 kB
Slab:             866920 kB
SReclaimable:      32620 kB
SUnreclaim:       834300 kB
KernelStack:        4848 kB
PageTables:        33592 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    24596664 kB
Committed_AS:   22260120 kB
VmallocTotal:   34359738367 kB
VmallocUsed:      494664 kB
VmallocChunk:          0 kB
Percpu:             3120 kB
HardwareCorrupted:     0 kB
AnonHugePages:  11618304 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:      781344 kB
DirectMap2M:    44146688 kB
DirectMap1G:     5242880 kB

なんでかusedが減った。
起動VMのプロセスの使用分とほぼ同じ値のため、今回の事象はslabキャッシュが影響していそうなことがわかった。

meminfoをコマンド実行前と後で見比べて、キャッシュクリア実行後に大きく減った値は以下
Buffers : 約10GB減
Active(file) : 約1.5GB減
Inactive(file) : 約8.6GB減
Slab : 約2.4GB減

slabキャッシュ以外の領域もクリアされたと考えるべきだが、調べても2を指定するとslabキャッシュが解放されるとしか出てこない。

ただしばらく放っておくとまたメモリ使用量はじわじわの増えていく。

こうなると根本原因はよくわからないけど、cronでslabキャッシュのクリアを定期的に実行するようにしないといけないかなとか思っていた。

調査3(zfsのarcサイズ確認)

メモリーの理解を深めるため、確か/proc/meminfoに出てくる何かの単語を調べていたところ、
急にzfsという単語が登場してきた(何の単語かは思い出せないし、この先全く登場せず関わってこないのでその単語自体は全然重要じゃない)。

確かに今回問題が発生しているホストはzfsのRAIDZ-1で構築をしている。
それが原因なのではないかということでそこを重点的に調べてみる。

zfsにはarcキャッシュというものが存在しているそう。
arcはzfsのファイルキャッシュのようなものでそのおかげで高速に読み書きができる。

//arcサイズ確認1
# arc_summary

------------------------------------------------------------------------
ZFS Subsystem Report                            Sun Nov 07 16:42:09 2021
Linux 5.4.106-1-pve                                           2.0.4-pve1
Machine: pve01 (x86_64)                                       2.0.4-pve1

ARC status:                                                      HEALTHY
        Memory throttle count:                                         0

ARC size (current):                                     6.2 %    1.5 GiB
        Target size (adaptive):                         6.3 %    1.5 GiB
        Min size (hard limit):                          6.2 %    1.5 GiB
        Max size (high water):                           16:1   23.5 GiB
        Most Frequently Used (MFU) cache size:         23.0 %  328.3 MiB
        Most Recently Used (MRU) cache size:           77.0 %    1.1 GiB
        Metadata cache size (hard limit):              75.0 %   17.6 GiB
        Metadata cache size (current):                  1.1 %  194.5 MiB
        Dnode cache size (hard limit):                 10.0 %    1.8 GiB
        Dnode cache size (current):                     0.1 %    2.2 MiB

//arcサイズ確認2
# cat /proc/spl/kstat/zfs/arcstats
c                               4    1735374464
c_min                           4    1574186624
c_max                           4    25186985984
size                            4    1668539664

c : Target size of the ARC
c_max : Maximum size of the ARC.
c_min : Minimum size of the ARC
size : Current size of the ARC
となっている。

arc最大値が23.5GiBって、これslabキャッシュクリアで消えたキャッシュ分とほぼ同等ということになる。

arcサイズのそれぞれのデフォルト値は以下
arc最大値 : ホストの物理メモリの半分
arc最小値 : ホストの物理メモリの1/32

48GBメモリ搭載ホストなので、arc最大値は24GB, arc最小値は1.5GBが現在の設定値になる。
PVE構築した際にzfsの設定変更なんてしていないので、arcサイズはデフォルト値が適用されている。
arcキャッシュはじわじわと増えていくようで、どうして無限にメモリが増えていかないのかとか、ホスト再起動直後はメモリを使わないのかなど謎が色々解けた。

arcキャッシュはtopやpsには出てこないため、なぜかメモリが使用されているという状態に陥る可能性がある。
理論的には、他のアプリケーションが使用するメモリが足りなくなったらarcキャッシュ領域は解放されるようだし、
逆にarcキャッシュするために他のアプリケーションからメモリを奪うことはないらしい。

何か不具合が起きていたわけでもなく、今回の事象はzfsの仕様だったということが判明した。

対応2(arcサイズの最大値と最小値設定)

メモリ使用量が増え続けると、あとどれだけVMを増やすことができるかとか、色々予測や考察することができなくなるため、arcサイズの最大値と最小値を設定する。

推奨値としては、ディスク容量1TBごとに1GBのメモリ、もしくはこの値に2GBを足した値になる(この推奨値も個人的なものかもしれないが指標として)。
現在の自分のマシンがRAIDZ-1で1TBなので、1GBもしくは3GBに設定するのが望ましいと考えられる。

ただしarc最大値がarc最小値を下回る場合、arc最大値は無視されてしまう。
arc最小値のデフォルトはシステムメモリの1/32になる。
現時点で48GBメモリを搭載しているホストなので、arc最小値のデフォルトは1.5GiBとなる。
この状態でarc最大値を1GBと設定したら無視されてしまうため、最小値も設定する必要がある。

なので今回はarc最大値を1GB, arc最小値を500MBにする(なぜなら検証用だし読み書きの速さは求めていないため)
バイトで設定しなきゃいけないため以下の計算になる
最大値 : 1×1024×1024×1024 = 1073741824
最小値 : 0.5×1024×1024×1024 = 536870912

//zfs.confの作成と編集
# nano /etc/modprobe.d/zfs.conf

+ options zfs zfs_arc_min=536870912
+ options zfs zfs_arc_max=1073741824

//保存
ctrl + x

//設定の反映
# update-initramfs -u

なぜかこれで設定が反映されなかったため、ホスト再起動した。
再起動後は設定が反映された。

//arcサイズ確認1
# arc_summary

------------------------------------------------------------------------
ZFS Subsystem Report                            Sun Nov 07 18:04:11 2021
Linux 5.4.106-1-pve                                           2.0.4-pve1
Machine: pve01 (x86_64)                                       2.0.4-pve1

ARC status:                                                      HEALTHY
        Memory throttle count:                                         0

ARC size (current):                                    13.7 %  140.6 MiB
        Target size (adaptive):                       100.0 %    1.0 GiB
        Min size (hard limit):                         50.0 %  512.0 MiB
        Max size (high water):                            2:1    1.0 GiB
        Most Frequently Used (MFU) cache size:         58.9 %   77.8 MiB
        Most Recently Used (MRU) cache size:           41.1 %   54.3 MiB
        Metadata cache size (hard limit):              75.0 %  768.0 MiB
        Metadata cache size (current):                  4.8 %   37.2 MiB
        Dnode cache size (hard limit):                 10.0 %   76.8 MiB
        Dnode cache size (current):                     4.2 %    3.2 MiB

//arcサイズ確認2
# cat /proc/spl/kstat/zfs/arcstats
c                               4    1073741824
c_min                           4    536870912
c_max                           4    1073741824
size                            4    143049608

この後、しばらく放っておいてもメモリ使用量は増えていかないため、根本原因の解決とする。

参考(メモリ調査)

https://qiita.com/toshihirock/items/e2d187e91ee5446c7a69
https://enakai00.hatenablog.com/entry/20110906/1315315488
https://qiita.com/bezeklik/items/7e1ac9e5da39261be7bd
https://dev.classmethod.jp/articles/dentry-cache/
https://qiita.com/taka0125/items/ac69359844998d67d0ae

参考(arcキャッシュ)

https://pve.proxmox.com/wiki/ZFS_on_Linux
https://blog.ohgaki.net/fedora-and-zfs
https://icat.hatenablog.com/entry/2020/06/05/154856
https://www.dlford.io/memory-tuning-proxmox-zfs/
https://kusanagi.dht-jpn.co.jp/2020/10/miyazaki-2020-10-27/
http://solaris-user.com/zfs/zfs_arc_max.html
http://mor-s.seesaa.net/article/470751080.html

所感

slabキャッシュクリアでarcキャッシュもクリアされるのは正直不思議というか、なんでなんだという疑問が残る。
ハードウェアRAIDが組めないからzfsで構築したら、こんなことになるとは思っていなかったが、色々な気づきや勉強になった。
メモリの動きや考え方が難しくて全然理解できなかったという感情が残ってる...

ちらっと書かれていたzfsという単語から解決まで進めたのは奇跡だった。

Discussion