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 がいくつ埋まっているか」ではなく「いま毎秒どれだけ出し入れしているか」。最初に見るべきは vmstat の si / so 列だと覚えておく。
まず何で観測するのか?(vmstat / free)
結論:
vmstat 1を実行し、si(swap-in KB/s)とso(swap-out KB/s)が継続的に大きい値を示すならスラッシング確定。free -hで残量を、topでwaの高さを併せて確認する。
最初に 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 -B(sysstat パッケージ)が使えるなら pgpgin/s pgpgout/s pswpin/s pswpout/s の履歴で「いつから始まったか」を遡れる。リアルタイムの vmstat と過去ログの sar を併用すると発生時刻を特定しやすい。
どのプロセスが swap を食っているのか?
結論: プロセス別の swap 使用量は
smem -s swap -rが最も読みやすい。smemが無ければ/proc/<pid>/statusのVmSwapを集計する。最大の 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 | head1245184 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 に溜まったページを物理メモリへ戻して「リセット」したい場合は swapoff → swapon を使う。ただし swap の中身を一旦すべてメモリに載せ直すため、空き物理メモリが swap 使用量を下回ると OOM killer が発動する。
# 危険: 空きメモリが swap 使用量より少ないと OOM を招く $ free -h # available > Swap used を確認してから $ sudo swapoff -a && sudo swapon -a
スラッシングの最中に安易に swapoff -a を叩かない。swap 上のページを全部メモリに戻そうとして、足りなければカーネルがプロセスを kill する。必ず先に原因プロセスを止めて free に余裕を作ってから実行する。
「とりあえず再起動」で症状は消えるが、メモリ設定やリークを直さなければ必ず再発する。応急処置の後に必ず原因(過大設定 / リーク / 単純な物理メモリ不足)の特定へ進む。
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 で頭打ちになり、システム全体のスラッシングへ波及しなくなる。
MemoryHigh を MemoryMax より少し低く設定すると、ハード上限で突然 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 1のsi/soが継続的に大きいことを確認したか(使用量ではなく速度) - [ ]
topでwa(I/O 待ち)が高くus/syが低いことを確認したか - [ ]
free -hでavailableと Swap 残量を確認したか - [ ]
smem -s swap -rまたは/proc/*/statusの VmSwap で犯人プロセスを特定したか - [ ] そのプロセスのメモリ設定(JVM
-Xmx・DB バッファ等)は物理メモリに見合うか - [ ] メモリリークの可能性を排除したか(時間経過で単調増加していないか)
- [ ] 応急処置(
swapoff)の前にavailable > Swap usedを確認したか - [ ] 慢性的なら RAM 増設 / 同時実行数削減を検討したか