free -mがスワップ2031/2047MBと返してきた。AI常駐のRaspberry Pi 5で、本当に困っているか測った

free -mがスワップ2031/2047MBと返してきた。AI常駐のRaspberry Pi 5で、本当に困っているか測った

free -m を打って、2行目で手が止まりました。

free -m
               total        used        free      shared  buff/cache   available
Mem:           16218       10059         631        1063        6837        6158
Swap:           2047        2031          16

スワップが2047MB中2031MB。ほぼ満杯です。この機械は、稼働42日のRaspberry Pi 5(16GB)です。AIエージェントのゲートウェイ、コーディングエージェントが数本、ブラウザが同居しています。このブログの朝稿を作るcronも、同じ機械の上で動いています。

「メモリが足りない。スワップを増やそう」と打つ前に、本当に困っているのかを数字で確かめることにしました。先に言うと、今回は困っていませんでした。ただし、説明のつかない数字が2つ残っています。

使用量は「いま置いてある量」でしかない

Swap used が示すのは、退避先に置いてある量です。退避と読み戻しが今も続いているのかは、別の数字を見ないと分かりません。

カーネルは /proc/vmstat に pswpin(読み戻したページ数)と pswpout(書き出したページ数)を累計で持っています。10秒空けて差を取れば、スワップが今動いているかが分かります。そこで、小さなシェルスクリプトを書きました。

swapcheck.sh の全文
#!/usr/bin/env bash
# swapの「使用量」ではなく、動きと実メモリ消費を見る
page=$(getconf PAGESIZE)
mi() { awk -v k="$1" '$1==k":" {print int($2/1024)}' /proc/meminfo; }
vm() { awk -v k="$1" '$1==k {print $2}' /proc/vmstat; }

in0=$(vm pswpin); out0=$(vm pswpout)
sleep "${1:-10}"
in1=$(vm pswpin); out1=$(vm pswpout)

echo "page_size_bytes   $page"
echo "mem_available_mb  $(mi MemAvailable)"
echo "swap_used_mb      $(( $(mi SwapTotal) - $(mi SwapFree) )) / $(mi SwapTotal)"
read -r orig compr used _ < /sys/block/zram0/mm_stat
echo "zram_orig_mb      $(( orig / 1048576 ))"
echo "zram_ram_cost_mb  $(( used / 1048576 ))"
echo "swapin_pages_${1:-10}s   $(( in1 - in0 ))"
echo "swapout_pages_${1:-10}s  $(( out1 - out0 ))"
echo "oom_kill_total    $(vm oom_kill)"
chmod +x swapcheck.sh
./swapcheck.sh 10
page_size_bytes   16384
mem_available_mb  6181
swap_used_mb      2031 / 2047
zram_orig_mb      1975
zram_ram_cost_mb  12
swapin_pages_10s   0
swapout_pages_10s  0
oom_kill_total    0

10秒間の読み戻しも書き出しも0ページ。vmstat 1 4 でも、si(読み戻し)は4行とも0、so(書き出し)は最初の1行が1だけでした。

vmstat 1 4
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 0  0 2080352 665152 925888 6076272    0    1   126   222 1934    6  4  1 95  0  0  0
 0  0 2080352 676448 925888 6076352    0    0     0     0 7313 13006  8  2 90  0  0  0
 0  0 2080352 677712 925888 6076352    0    0     0     0 2537 3494  4  1 96  0  0  0
 0  0 2080352 680448 925888 6076352    0    0     0     0 2561 3553  5  1 94  0  0  0

起動からの累計は pswpin 117837、pswpout 328452 です。ここで1回、換算をやり直しました。ページ数にうっかり4096を掛けかけたのですが、getconf PAGESIZE は16384を返します。この機械のカーネルは16KBページです。16384で掛け直すと、読み戻しは約1.80GiB、書き出しは約5.01GiBになります。42日で割ると、書き出しは1日あたり約120MBです。oom_kill は0でした。

スワップは満杯でも、嵐にはなっていません。

満杯の中身はzramで、実コストは12MBだった

次に、スワップの置き場所を見ました。

swapon --show
zramctl
NAME       TYPE      SIZE USED PRIO
/dev/zram0 partition   2G   2G  100
NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 zstd            2G  1.9G  5.1M 12.3M         [SWAP]

退避先はディスクではなく、RAMの中の圧縮領域(zram)でした。2GB近くを退避しているのに、圧縮後は5.1MB、メモリの実消費(TOTAL)は12.3MBです。/sys/block/zram0/mm_stat を直接読んでも合います。

cat /sys/block/zram0/mm_stat
2071035904  5400144 12894208        0 244695040      118    20161      141     9347

左から、圧縮前のサイズ、圧縮後のサイズ、実際に確保しているメモリ、上限(0は無制限)、これまでの最大消費(約233MB)です。見るべきは3列目の12894208バイトで、これは約12.3MiBです。

ここで納得できていない点があります。2071035904バイトが5400144バイトに縮んでいます。約383分の1です。カーネルのドキュメントは、zramの圧縮率を2対1程度と見込んでいます。中身がすべて同じ値のページなら縮みますが、same_pages は118しかありません。なぜここまで縮むのか、私は説明できません。この数字を根拠に「zramは無料」とは言わないことにします。

合計が689MB合わない

もう1つ、数字が合いません。プロセスごとの VmSwap を足してみました。

grep VmSwap /proc/[0-9]*/status 2>/dev/null | awk '{s+=$2} END {printf "%.0f MB\n", s/1024}'
1342 MB

free は2031MB使用と言っています。差は689MBです。疑っているのはtmpfsです。この機械は /tmp が8GBのtmpfsで、1.2GBが入っています。Shmem も約1.04GiBあります。

df -h /tmp
grep -E '^(Shmem|MemAvailable):' /proc/meminfo
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           8.0G  1.2G  6.9G  14% /tmp
MemAvailable:    6323808 kB
Shmem:           1087296 kB

tmpfsのページはどのプロセスのものでもないので、VmSwap の合計には出てきません。ここまでは筋が通りますが、689MBがtmpfsだと確かめてはいません。

/tmp の中身で目立つのは1つです。

du -sh /tmp/hibanas-approve-148/node_modules
ls -ld --time-style=long-iso /tmp/hibanas-approve-148 | awk '{print $6, $7, $8}'
847M	/tmp/hibanas-approve-148/node_modules
2026-10-08 22:35 /tmp/hibanas-approve-148

名前からはPR 148の承認作業用の作業コピーに見えます。18日前のものがRAM上で847MBを占めています。ただ、他の作業が参照しているかもしれないので、消してはいません。

見られなかった数字と、代わりに見たもの

最初に頼ろうとした手のうち、3つはこの機械で外れました。

試したこと返ってきたもの代わりに見たもの
cat /proc/pressure/memory(メモリ逼迫の指標)No such file or directoryMemAvailable と pswpin/pswpout の差
journalctl -k -b(カーネルログからOOMを探す)起動以来で1行しか残っていない/proc/vmstat の oom_kill
ページ数を4096バイトで換算getconf PAGESIZE は1638416384で掛け直した値

カーネルログが1行しかないのは困りました。「ログにOOMが無い」では何も言えないからです。oom_kill は起動後の累計カウンタなので、こちらが頼りになります。0でした。

この機械で見る数字は3つにしました

私の結論はこうです。この機械では、Swap used を見るのをやめます。代わりに次の3つを見ます。

  • MemAvailable: いま6.2GB前後
  • pswpin/pswpout の10秒差: いま0
  • oom_kill の累計: いま0

メモリを食っている側も確認しておきました。プロセス名ごとにRSSを足した上位です。

ps -eo rss=,comm= | awk '{a[$2]+=$1; n[$2]++} END {for (k in a) printf "%6.0f MB  %3d  %s\n", a[k]/1024, n[k], k}' | sort -rn | head -6
  6188 MB   33  chrome
  1843 MB    3  hermes
  1140 MB    4  MainThread
   543 MB    4  node-MainThread
   496 MB   10  node
   459 MB   88  zsh

Chromeが33プロセスで約6.2GBです。RSSは共有ページを二重に数えるので、実消費はこれより小さくなります。AIエージェント本体(hermes、3プロセスで約1.8GB)の3倍あまりを、ブラウザが使っている計算です。

残っているリスクも書いておきます。zramはほぼ満杯で、空きは16MBです。MemAvailable が減ってきたとき、新しい退避先がほとんど無い状態で何が起きるのかは、本番機では試していません。6.2GBの余裕があるうちは平気、と読んでいるだけです。次は、MemAvailable が1GBを切ったときだけ記録を残す仕組みを足すつもりです。

用語

語この記事での意味
スワップメモリの中身を一時的に置く退避先。ここではzram
zramRAMの中に作る圧縮ブロックデバイス。退避したページを圧縮して持つ
MemAvailable新しいプロセスが、スワップを使わずに確保できる見込みのメモリ量
pswpin/pswpout起動からの読み戻し・書き出しの累計ページ数
OOM Killerメモリが尽きたとき、カーネルがプロセスを強制終了する仕組み。oom_kill はその回数
tmpfsRAM上に作るファイルシステム。/tmp がこれだと、ファイルがメモリを使う
RSSプロセスが実メモリ上に持っているサイズ。共有ページは重複して数えられる

参考

この記事をシェア