OOM Killerが本番で問題になる局面
Linuxのメモリ管理においてOOM Killer(Out-of-Memory Killer)は最後の安全弁として設計されています。物理メモリとスワップ領域が限界に達したとき、カーネルはプロセスを選定して強制終了することでシステム全体の応答を守ります。開発環境や検証環境では「再起動すれば済む」で終わりますが、本番環境ではそうはいきません。
問題の典型例は「守るべきプロセスが殺される」ケースです。PostgreSQLのバックエンドプロセス、Nginxのワーカー、社内の基幹バッチ——これらが突然終了し、ログに Out of memory: Killed process だけが残っているという状況は、運用担当者なら一度は経験しているはずです。OOM KillerはOOMスコアと呼ばれる指標でプロセスを評価し、スコアが高いものから順に終了対象として選びます。このスコアは調整可能であり、また根本的にはcgroupでメモリ使用量そのものを制約することで発動を防ぐ設計も取れます。本記事では、この両輪をどう組み合わせるかを実務の流れに沿って整理します。
OOMスコアの仕組みとoom_score_adjの使い方
カーネルは各プロセスに対して0〜1000の範囲でOOMスコアを算出します。スコアの主成分はプロセスのメモリ使用量(物理ページ数)であり、使用量が多いほど高スコアになります。この計算結果が /proc/[pid]/oom_score に反映されており、実際の値は cat /proc/$(pidof nginx)/oom_score などで随時確認できます。
運用者がチューニングに使うのは /proc/[pid]/oom_score_adj です。-1000から+1000の範囲で調整値を書き込むことで、カーネルが計算したスコアに加算・減算されます。-1000を指定するとOOM Killerの対象から完全に除外されます(oom_score_adjが-1000のプロセスはどれだけメモリを使っていても選ばれません)。一方、+500を指定するとスコアが500加算され、積極的に終了対象として選ばれやすくなります。
一時的な調整は以下のように行います。
# PostgreSQLメインプロセスを保護する例
PG_PID=$(pidof postgres | awk '{print $NF}')
echo -800 > /proc/${PG_PID}/oom_score_adj
# 設定を確認する
cat /proc/${PG_PID}/oom_score_adj
ただし、この設定はプロセスの再起動(またはOS再起動)で失われます。永続化するにはsystemdのユニットファイルに OOMScoreAdjust= ディレクティブを記述するのが現行標準です。
systemdユニットでの永続設定
たとえばPostgreSQLのサービスを保護したい場合、ドロップインファイルを使うのが最も影響範囲が小さい方法です。
# ドロップインディレクトリを作成し設定を追記する
mkdir -p /etc/systemd/system/postgresql.service.d/
cat > /etc/systemd/system/postgresql.service.d/oom.conf <<'EOF'
[Service]
OOMScoreAdjust=-800
EOF
systemctl daemon-reload
systemctl restart postgresql
再起動後も /proc/[pid]/oom_score_adj が-800になっていれば設定が引き継がれています。-1000は「絶対に殺さない」という強い保証になりますが、その分システム全体のメモリが枯渇したときに他の全プロセスが先に終了することになります。設計判断として、-1000を指定するのはシステムの生死に直結するsshd程度に留め、アプリケーションプロセスは-500〜-800程度にとどめる運用が現場では多く見られます。
cgroupメモリ上限によるOOM発動の予防設計
oom_score_adjによるスコア調整は「どれを殺すか」の優先順位調整であり、OOM Killer自体の発動を防ぐものではありません。根本的な対策として有効なのが、cgroupによるメモリ使用量の上限設定です。プロセスグループ(サービス単位)に上限を設けることで、特定のサービスが暴走してもシステム全体のメモリを食いつぶすことを防げます。
2026年現在、主要なディストリビューション(Ubuntu 22.04以降、RHEL 9系、Debian 12以降)はcgroup v2に統一されており、systemdがcgroup階層を管理するのが標準構成です。cgroup v1の /sys/fs/cgroup/memory/ 以下を直接操作する手法は、現行環境では混乱の元になるため推奨しません。
systemdユニットへのメモリ上限設定
先ほどのドロップインファイルにMemoryMaxを追記するだけで機能します。
[Service]
OOMScoreAdjust=-800
MemoryMax=4G
MemorySwapMax=0
MemoryMax はcgroup v2の memory.max に対応し、このサービスが使用できる物理メモリとスワップを合算した上限を設定します。MemorySwapMax=0 はスワップの使用を禁止する指定で、スワップに逃がさずメモリ制限を厳格に効かせたい場合に使います。なお MemoryHigh という設定も存在し、こちらは超過してもプロセスを即終了させず、カーネルが積極的に回収を試みるソフトリミットとして機能します。MemoryHighとMemoryMaxの組み合わせで「段階的な制限」を表現するのが2026年の推奨パターンです。
[Service]
OOMScoreAdjust=-800
MemoryHigh=3500M # この値を超えたらカーネルが積極回収
MemoryMax=4G # この値を超えたらOOM終了
MemorySwapMax=0
設定を反映した後は systemctl status の Memory: 行と systemd-cgtop コマンドで実際の使用量を確認できます。
設定後の検証とOOMイベントの観測方法
設定が意図通りに機能しているかを確認するには、いくつかの観点から状態を確認します。
まず現在のOOMスコアとoom_score_adjを一覧で確認する方法です。
# 全プロセスのOOMスコアを降順で表示する
for pid in /proc/[0-9]*/; do
pid_num=$(basename $pid)
score=$(cat $pid/oom_score 2>/dev/null)
adj=$(cat $pid/oom_score_adj 2>/dev/null)
comm=$(cat $pid/comm 2>/dev/null)
echo "${score} ${adj} ${pid_num} ${comm}"
done | sort -rn | head -20
OOM Killerが実際に発動したかどうかはカーネルログで追います。
# journaldでOOM関連ログを絞り込む
journalctl -k --since "24 hours ago" | grep -E "oom|Out of memory|Killed process"
# dmesgで直接確認する場合
dmesg -T | grep -E "oom_kill|Out of memory"
cgroupのメモリ上限に関しては、systemctl show でユニットに適用されている値を確認します。
systemctl show postgresql -p MemoryMax -p MemoryHigh -p OOMScoreAdjust
また、/sys/fs/cgroup/system.slice/postgresql.service/memory.events ファイルにはOOM終了回数(oom_kill)やMemoryHighを超過した回数(high)が記録されているため、定期的に監視することで「上限に近づいているサービス」を早期に検出できます。PrometheusとNode Exporterを組み合わせている環境であれば、container_memory_failcnt 相当のメトリクスとして取り込むことも可能です。
設計上の注意点と切り戻し判断
oom_score_adjの設定を誤ると、保護すべきでないプロセスを過剰に保護してしまい、逆にシステムが応答不能に陥るリスクがあります。特に以下の点は現場でよく問題になります。
- -1000の乱用: 複数のサービスに-1000を設定すると、メモリ枯渇時にカーネルはそれらを除外したプロセスの中から選ぶしかなくなります。sshdやsystemd自体が殺されると復旧のために物理コンソールが必要になります。
- MemoryMaxの過小設定: 運用負荷の波を考慮せずに上限を設定すると、通常運用中のピーク時にサービスがOOM終了します。設定前に少なくとも1週間分のメモリ使用量ピークを計測してから上限値を決めるのが基本です。
- 子プロセスへの継承: oom_score_adjはfork時に子プロセスへ継承されます。Apacheのmpm_preforkやPHPのPHP-FPMのように多数の子プロセスを生成する構成では、親プロセスのadj値が全子プロセスに引き継がれる点を意識してください。
切り戻しが必要になった場合、systemdドロップインファイルを削除または編集して systemctl daemon-reload を実行し、サービスを再起動することで元の状態に戻せます。cgroupのMemoryMax設定はサービス再起動なしに動的変更が可能な場合もありますが、oom_score_adjは基本的にプロセスの生成時に引き継がれるため、再起動が確実です。一時的な確認・ロールバックのためにドロップインファイルをバージョン管理(Gitや構成管理ツール)に含めておくことで、差分確認と復元が容易になります。
2026年の現行環境における設計指針まとめ
2026年時点での主要ディストリビューションにおける推奨アプローチを整理すると、以下のような構成が標準的です。
- cgroup v2前提: Ubuntu 22.04/24.04、RHEL 9、Debian 12はいずれもcgroup v2がデフォルト。
mount | grep cgroupでcgroup2が返ることを前提に設計します。 - systemdユニットで一元管理:
/proc/[pid]/への直接書き込みはシェルスクリプトや初期化スクリプトからは排除し、systemdのサービス定義に一本化します。これによりサービス再起動や自動起動時にも設定が失われません。 - MemoryHigh → MemoryMaxの二段設定: MemoryHighをソフトリミットとして先に置くことで、カーネルがガベージコレクションやスワップアウトを試みる猶予を与えます。MemoryMaxは本当の上限として設定します。
- OOMKillPolicy(systemd 253以降): 新しめのsystemd(Fedora 40やUbuntu 24.04に搭載)では
OOMPolicy=continueやOOMPolicy=stopの指定もサポートされ、cgroup内のプロセスがOOMで終了したときのサービス全体の挙動を制御できます。cgroupのOOM設定と合わせて理解しておく価値があります。
OOM Killerの制御は「何かが起きてから対処する」後手の作業になりがちです。しかし、oom_score_adjとcgroupメモリ上限を事前に設計し、ドロップインファイルとして構成管理に含めておけば、インフラ変更時にも一貫した保護が維持できます。本番環境でのメモリ設計は、アプリケーションの特性とピーク負荷の実測値を起点に、systemdの宣言的な設定として表現するというのが2026年時点での現場標準と言えるでしょう。
「コマンドは打てる。次は"現場で通用する型"を最短で身につけたい」——そんなあなたへ。
ネットの断片的なTipsをコピペするだけでは、体系的な運用スキルはなかなか積み上がりません。リナックスマスター.JPの無料メルマガ「リナマガ」では、現場で実際に使うLinuxサーバー運用の考え方を、順を追って実務目線でお届けしています。いま登録された方には、安全なサーバー構築の「型」をまとめた『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼント中です。
>> 図解60P『Linux入門マニュアル』を無料で受け取る
(メルマガ登録は10秒・いつでも解除OK)
※ 「独学の時間がもったいない」「プロから現場の技術を最短で学びたい」という本気の方には、2日で実務レベルが身につく初心者向けハンズオンセミナーもご用意しています。
PR・広告
[試して理解]Linuxのしくみ 増補改訂版(Amazon)
プロセス・権限・ファイルシステムなどLinux内部を図解で理解できる一冊。運用判断の勘所に。
※ Amazonのアソシエイトとして、当サイトは適格販売により収入を得ています。
