swap スラッシング(thrashing)の診断と対処 - メモリ逼迫で遅い

swap スラッシング(thrashing)の診断と対処 - メモリ逼迫で遅い

swap スラッシング(thrashing)とは何が起きているのか?

結論: 物理メモリが足りず、カーネルがページを swap(ディスク)へ追い出し/読み戻しを絶え間なく繰り返す状態。CPU はほぼアイドルなのにディスク I/O だけが飽和し、システム全体が極端に遅くなる。原因は「メモリ不足」だが、症状は「ディスクが遅い」「load average だけ高い」に化けて見える。

メモリが逼迫すると、Linux は使用頻度の低いページを swap 領域へ退避(swap-out)してメモリを空ける。ここまでは正常な動作だ。問題は、退避したページがすぐに必要になり、読み戻し(swap-in)→ 別ページを追い出し → またすぐ必要に…という循環に陥ったとき。これがスラッシング(thrashing)で、CPU は本来の計算ではなくページの出し入れの待ちに時間を費やす。

$ uptime
 14:32:10 up 8 days,  3:11,  2 users,  load average: 18.40, 16.92, 12.05

load average は跳ね上がるのに top の CPU 使用率(us/sy)は低く、wa(I/O 待ち)ばかりが高い。「CPU は暇なのに遅い」というちぐはぐな症状がスラッシングの典型だ。

前提(対象環境)

  • OS: Ubuntu / 一般的な Linux
  • 症状: サーバが急激に遅くなる・反応が返らない・SSH すら重い
  • swap が有効(free の Swap 行が 0 でない)
  • vmstat / free / /proc を参照できる前提(恒久設定は sudo 必須)

なぜ swap 使用量ではなく「入れ替え速度」を見るのか?

結論: swap が使われていること自体は問題ではない。アイドルなプロセスのページが swap に置かれたまま静かに眠っているのは健全。危険なのは swap-in / swap-out が継続的に高速で流れている状態で、これだけが体感の遅さに直結する。判断は使用量(free)ではなく速度(vmstat の si/so)で行う。

free -h で Swap が埋まっていても、それが「過去に追い出されてそのまま」なら無害だ。逆に Swap 使用量が中程度でも、毎秒大量に出し入れしていればシステムは止まったように遅くなる。

観点 健全 スラッシング
swap 使用量(free 多くても安定 増減を激しく繰り返す
si/so(vmstat ほぼ 0 継続的に大きい
CPU 通常通り wa(I/O 待ち)が高い
体感 問題なし 全操作が遅い

スラッシングの一次指標は「swap がいくつ埋まっているか」ではなく「いま毎秒どれだけ出し入れしているか」。最初に見るべきは vmstatsi / so 列だと覚えておく。

まず何で観測するのか?(vmstat / free)

結論: vmstat 1 を実行し、si(swap-in KB/s)と so(swap-out KB/s)が継続的に大きい値を示すならスラッシング確定。free -h で残量を、topwa の高さを併せて確認する。

最初に vmstat を 1 秒間隔で流す。各行が直近 1 秒の状況なので、si/so の「流れ」が見える。

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2 14 2087600  51200  10240 102400 4096 5120  4200 5300 3100 8900  3  4  8 85  0
 1 12 2091200  48300  10100 101800 3800 4900  3900 5100 2980 8500  2  3 10 85  0

si / so が毎秒数 MB 規模で流れ続け、wa(I/O 待ち)が 80% 超。b(ブロック中プロセス数)も多い。これがスラッシングの動かぬ証拠だ。次に残量を見る。

$ free -h
               total        used        free      shared  buff/cache   available
Mem:            3.8Gi       3.5Gi       120Mi        12Mi       180Mi        90Mi
Swap:           2.0Gi       1.9Gi        80Mi

available が極端に少なく、Swap もほぼ満杯。物理メモリが枯渇し swap に逃がしきれなくなっている。top でも裏取りする。

$ top -bn1 | head -5
top - 14:32:40 up 8 days,  load average: 18.40, 16.92, 12.05
Tasks: 210 total,   1 running, 209 sleeping
%Cpu(s):  3.0 us,  4.0 sy,  0.0 ni,  8.0 id, 85.0 wa,  0.0 hi,  0.0 si
MiB Mem :   3891.0 total,    120.0 free,   3591.0 used,    180.0 buff/cache
MiB Swap:   2048.0 total,     80.0 free,   1968.0 used

wa が支配的で id(アイドル)はわずか。CPU は計算ではなく I/O 完了を待っている。

sar -Bsysstat パッケージ)が使えるなら pgpgin/s pgpgout/s pswpin/s pswpout/s の履歴で「いつから始まったか」を遡れる。リアルタイムの vmstat と過去ログの sar を併用すると発生時刻を特定しやすい。

どのプロセスが swap を食っているのか?

結論: プロセス別の swap 使用量は smem -s swap -r が最も読みやすい。smem が無ければ /proc/<pid>/statusVmSwap を集計する。最大の swap 消費プロセスがスラッシングの主因であることが多い。

smem はプロセスごとの swap 使用量を直接表示できる(sudo apt install smem)。

$ sudo smem -s swap -r | head
  PID User     Command                         Swap      USS      PSS      RSS
 2314 www-data java -Xmx3g -jar app.jar      1245184   210432   215300   240128
 1190 mysql    /usr/sbin/mysqld               412300    98200   101400   130560
 1532 root     /usr/bin/dockerd               120400    40100    42300    61440

Swap 列が突出したプロセスが犯人だ。smem を入れられない(新規パッケージ導入が憚られる)場合は、/proc を直接集計する。

# 全プロセスの VmSwap を多い順に表示(KB)
$ for f in /proc/[0-9]*/status; do
    awk '/^Name:/{n=$2} /^VmSwap:/{print $2, n, FILENAME}' "$f"
  done 2>/dev/null | sort -rn | head
1245184 java /proc/2314/status
412300 mysqld /proc/1190/status
120400 dockerd /proc/1532/status

メモリ確保量の設定ミス(JVM の -Xmx が物理メモリ超過、DB のバッファプール過大など)が典型的な原因。上限を物理メモリに見合う値へ下げるのが根本対処になることが多い。

swap 使用量が多い = 即犯人、とは限らない。「いま激しく出し入れしているプロセス」を見るには、vmstat でスラッシング中に smem を複数回取り、Swap 値が変動しているプロセスに注目する。静止しているなら眠っているだけのページだ。

今すぐ遅さを止めるには?(応急処置)

結論: 暴走・過大なプロセスを止める/メモリ上限を下げて再起動するのが最短。swap の溜まりをリセットしたいなら swapoff -a && swapon -a だが、空きメモリが無い状態での実行は OOM を誘発するため危険。応急処置は「メモリを空ける」方向に限る。

最も安全で効くのは、smem で特定した過大プロセスを停止/再設定すること。

# 原因プロセスを正規の手順で再起動(設定を見直してから)
$ sudo systemctl restart myapp.service

swap に溜まったページを物理メモリへ戻して「リセット」したい場合は swapoffswapon を使う。ただし swap の中身を一旦すべてメモリに載せ直すため、空き物理メモリが swap 使用量を下回ると OOM killer が発動する。

# 危険: 空きメモリが swap 使用量より少ないと OOM を招く
$ free -h          # available > Swap used を確認してから
$ sudo swapoff -a && sudo swapon -a

「とりあえず再起動」で症状は消えるが、メモリ設定やリークを直さなければ必ず再発する。応急処置の後に必ず原因(過大設定 / リーク / 単純な物理メモリ不足)の特定へ進む。

swappiness を調整すべきか?

結論: vm.swappiness は「どれだけ積極的に swap を使うか」の傾向値(0〜100、既定 60)。下げるとアプリのページを swap に追い出しにくくなり、対話的サーバの体感が改善することがある。ただし物理メモリ不足そのものは解決しない。根本対処はメモリの増設か使用量の削減。

現在値を確認する。

$ cat /proc/sys/vm/swappiness
60

一時的に下げて挙動を見る(再起動で戻る)。

# 0 ではなく 10 程度から試すのが無難
$ sudo sysctl -w vm.swappiness=10

効果を確認できたら恒久化する。

$ echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf
$ sudo sysctl --system
swappiness 傾向 向くケース
0〜10 swap をできるだけ避ける 対話サーバ・低レイテンシ重視
60(既定) バランス 汎用
100 積極的に swap バッチ・スループット重視

swappiness を 0 にしても swap が完全に無効になるわけではない(メモリ逼迫時には依然 swap する)。また下げ過ぎるとファイルキャッシュとのバランスが崩れ、別の遅さを生むことがある。極端値は避け、vmstat で si/so が落ち着くかを観測しながら調整する。

特定サービスの swap を cgroup で抑えるには?

結論: systemd 管理下のサービスなら MemoryMax / MemoryHigh でメモリ上限を課し、1 サービスがホスト全体を巻き込むスラッシングを封じ込められる。上限超過時はそのサービスだけが reclaim / OOM の対象になり、他は守られる。

特定サービス(例: 過大な Java アプリ)にメモリ上限を設定する。

$ sudo systemctl edit myapp.service

[Service] セクションに上限を書く。

[Service]
MemoryHigh=2G
MemoryMax=2.5G

MemoryHigh は超えると積極的に reclaim される「ソフト上限」、MemoryMax は超えると OOM kill される「ハード上限」。保存後に反映する。

$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp.service
$ systemctl show -p MemoryMax myapp.service

これで該当サービスのメモリ使用が cgroup で頭打ちになり、システム全体のスラッシングへ波及しなくなる。

MemoryHighMemoryMax より少し低く設定すると、ハード上限で突然 kill される前に「絞られて遅くなる」緩衝帯ができる。本番では MemoryMax 単独より両方指定が安全。

物理メモリ不足が真因なら?(swap 増設・増設判断)

結論: 設定ミスもリークも無く、単に常時メモリが足りないならハードの増設(RAM 追加)が本筋。swap を増やすのは「OOM で落ちるよりはマシ」な緩衝に過ぎず、増やしてもスラッシングは速度が遅いまま続く。swap 追加は延命、RAM 追加が解決。

swap ファイルを追加して当座の OOM を避ける手順(クラウド等で即時 RAM 増設できない場合)。

# 2GB の swap ファイルを作成
$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

恒久化は /etc/fstab に追記する。

/swapfile  none  swap  sw  0  0

swap を増やしても、メモリが足りない限り出し入れは続き「遅い」ままだ。swap 増設は OOM kill を避ける延命策であって、スラッシングの遅さそのものは解消しない。慢性的にスラッシングするなら、RAM 増設かワークロード削減(プロセスのメモリ上限引き下げ・同時実行数の削減)が正攻法。

それでも直らないときのチェックリスト

結論: スラッシングは「物理メモリ不足」が確定した状態。vmstat で si/so を確認 → smem で犯人プロセスを特定 → 過大設定 / リークを直す → 足りなければ RAM 増設、の順で原因はこの層に収束する。swappiness や cgroup は対症であって、根本はメモリ量とのバランス。

  • [ ] vmstat 1si / so が継続的に大きいことを確認したか(使用量ではなく速度)
  • [ ] topwa(I/O 待ち)が高く us/sy が低いことを確認したか
  • [ ] free -havailable と Swap 残量を確認したか
  • [ ] smem -s swap -r または /proc/*/status の VmSwap で犯人プロセスを特定したか
  • [ ] そのプロセスのメモリ設定(JVM -Xmx・DB バッファ等)は物理メモリに見合うか
  • [ ] メモリリークの可能性を排除したか(時間経過で単調増加していないか)
  • [ ] 応急処置(swapoff)の前に available > Swap used を確認したか
  • [ ] 慢性的なら RAM 増設 / 同時実行数削減を検討したか

次に読む