物理の限界を超えた先にある「奈落」――オーバーコミットメントとスラッシングの正体
エンジニアたるもの、一度は夢見るはずです。「物理メモリが64GBしかないサーバーに、合計128GBのメモリを要求するVMを詰め込めば、コスト効率は倍になるのでは?」と。
クラウドネイティブな現代において、コンテナや仮想マシンのオーバーコミットメントは、コスト最適化の常套手段です。しかし、この「魔法のような詰め込み」には、物理法則に近い残酷な限界が存在します。今日は、ハイパーバイザーがその限界を超えた瞬間に発生する「スラッシング(Thrashing)」という名の悪夢と、それを回避するための現場の知恵について語りましょう。
1. オーバーコミットメントの「甘い罠」
オーバーコミットメントとは、物理リソース(CPUやメモリ)の容量に対し、論理的にそれ以上のリソースを仮想マシン(VM)やコンテナに割り当てる手法です。
例えば、物理メモリ16GBのホストに、8GBのメモリを要求するVMを3つ動かす。これが成立するのは、全VMが同時に最大負荷をかけることが稀であるという「統計的多重化」の原理に基づいています。しかし、すべてのVMが同時に活動を開始した瞬間、ハイパーバイザーは「物理メモリの貸し借り」の限界に突き当たります。
ここで発生するのがメモリのスワップです。ハイパーバイザーは、物理メモリに入り切らないゲストのメモリページをディスク(ストレージ)へ退避させます。これが「スラッシング」の序章です。
2. なぜ「スラッシング」はシステムを殺すのか
スラッシングとは、OSやハイパーバイザーが「計算」よりも「データのスワップ(メモリとディスクの往復)」にリソースの大部分を費やしてしまう現象です。
1. ページングの爆発: メモリが不足し、頻繁にページアウト(メモリ→ディスク)とページイン(ディスク→メモリ)が発生する。
2. I/Oの飽和: ディスクI/Oがボトルネックになり、CPUはI/O待ち(iowait)でスタックする。
3. レスポンスの崩壊: Web APIの処理が遅延し、TCP接続がタイムアウト(504 Gateway Timeout)を量産する。
この状況下では、アプリケーション層でいくらリトライ回数を増やしても、根本的な解決にはなりません。むしろ、リトライの嵐がさらなる負荷を呼び、泥沼化します。
3. 実践:現場でのスラッシング検知と調査
現場で「APIが突然重くなった」というアラートを受けた際、まずは以下のコマンドでホスト側の状況を確認してください。特に si (swap in) と so (swap out) の値が継続的にゼロ以外を指しているなら、それはスラッシングの兆候です。
# vmstat 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 0 10240 45000 12000 500000 0 0 2 5 100 200 5 2 90 3 0
# もし si や so が常時 0 を超えて変動しているなら、メモリ不足のサインです
4. 対策:リソース制限をコードで制御する
Kubernetes環境であれば、ResourceQuota や LimitRange を設定し、安易なオーバーコミットを防ぐのが定石です。以下は、Podに対するリソース制限のYAML例です。
# k8s-resource-limit.yaml
apiVersion: v1
kind: Pod
metadata:
name: api-server
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
memory: "256Mi" # 確実に確保されるメモリ
cpu: "250m"
limits:
memory: "512Mi" # これを超えるとOOMKillされる境界
cpu: "500m" # CPUスロットリングが発生する上限
5. アプリケーション層からの防御策
インフラがスラッシングに陥る前に、アプリケーション側で「負荷」を検知してリクエストを弾く、あるいはスローダウンさせる設計(バックプレッシャー)が重要です。Pythonでシンプルなリクエスト制御を行う例を紹介します。
import time
import psutil
def check_system_health():
# メモリ使用率が90%を超えていたら、APIは新規受付を停止する
mem = psutil.virtual_memory()
if mem.percent > 90:
return False
return True
def handle_request():
if not check_system_health():
# 503 Service Unavailable を返して負荷を下げる
return {"status": 503, "message": "System Overloaded, try later"}
# 本来のビジネスロジック
return {"status": 200, "message": "Success"}
最後に:SREとしてのアドバイス
「物理リソースを極限まで使い切る」ことは、運用者としての矜持かもしれません。しかし、可用性を犠牲にしたコスト削減は、ただの「コストの先送り」です。
もしあなたがWeb APIの設計に携わっているなら、監視ツール(Prometheus/Grafanaなど)で container_memory_working_set_bytes を注視し、limit に対する使用率が80%を超えないようなスケーリング戦略を立ててください。
ネットワークのパケットが物理層で止まってしまうような悲劇は、事前の設計と正しいメトリクスで必ず防げます。教科書を読み終えたら、次は現場の vmstat を叩いてみてください。そこには、あなたのシステムの「呼吸」がリアルに刻まれています。
コメント