🔪

[Node.js] ヒープメモリの割り当て調整におけるOOMキラー対策

に公開

この記事は?

AWSで動かしているNode.jsのアプリで、Node.jsのヒープメモリをECSのメモリサイズによって動的に設定することを考えます。このとき、適切な値を動的に設定するためのベストプラクティスはなんでしょうか?これを考える一つの側面として、OOMキラーを防ぐ という観点があります。

OOMキラーとは何か?

OOMキラーとは何か?

OOMキラー(Out-Of-Memory Killer)は、Linuxカーネルに組み込まれたメモリ管理メカニズムで、システムのメモリが危機的に不足した際に発動します。その役割は、システム全体の安定性を確保するため、一部のプロセスを犠牲にすることです。

例えば、Node.jsアプリケーションを動かすことをイメージしてください。Node.jsにLinuxメモリーの全部を与えてしまうと、OOMキラーに繋がってしまうことがあるので注意が必要です。

OOMキラーの基本動作

監視: Linuxカーネルは、システムの利用可能なメモリを継続的に監視しています。
判断: メモリの空き容量が危機的に少なくなると、カーネルはOOMキラーを起動します。
評価: OOMキラーは、各プロセスに「badness score(悪さスコア)」を割り当てます。このスコアは以下の要素に基づいて計算されます:

・プロセスの実行時間(長時間実行されているプロセスは優先的に残される傾向がある)
・プロセスの優先度(nice値など)
・プロセスの種類(カーネルプロセスは保護される)

終了: 最も高いbadness scoreを持つプロセスが強制終了されます。これにより、システムに十分なメモリが解放されるまで、または危機的状況が解消されるまで続きます。
このことをOOMキラー というのです。稼働システムでOOMキラーが起こることは避けたいです。

OOMキラーを発生させないヒープメモリ調整

Node.jsアプリケーションでは、特にコンテナ環境において、ヒープメモリの適切な調整がOOMキラーを回避する鍵となります。
Node.jsは主に以下のメモリ領域を使用します:

・ヒープメモリ: JavaScriptオブジェクトやデータ構造が格納される領域で、--max-old-space-sizeオプションで上限を設定できます。
・コードメモリ: JavaScript、C++コード自体が格納される領域。
・スタックメモリ: 関数呼び出しやローカル変数のために使用される領域。
・外部メモリ: バッファやネイティブオブジェクトなど、V8エンジン外で管理されるメモリ。

コンテナメモリの値から動的にヒープメモリ設定方法

コンテナメモリの割合として設定

NODE_MEMORY=$(($CONTAINER_MEMORY_MB * 75 / 100))

コンテナに割り当てられたメモリの75%をNode.jsのヒープに割り当てることで、残りの25%をシステムやその他のプロセスのために確保します。(75%はケースバイケースで変更してください)
上限と下限の設定

MIN_NODE_MEMORY=512  # 最小ヒープサイズ: 512MB
MAX_NODE_MEMORY=8192 # 最大ヒープサイズ: 8GB

if [ $NODE_MEMORY -lt $MIN_NODE_MEMORY ]; then
  NODE_MEMORY=$MIN_NODE_MEMORY
elif [ $NODE_MEMORY -gt $MAX_NODE_MEMORY ]; then
  NODE_MEMORY=$MAX_NODE_MEMORY
fi

小さすぎるメモリではアプリケーションのパフォーマンスが低下し、大きすぎるメモリでは効率が悪化するため、適切な上限と下限を設けることが重要です。
NODE_OPTIONSへの適用

export NODE_OPTIONS="--max-old-space-size=$NODE_MEMORY"

計算されたヒープメモリサイズを環境変数として設定することで、Node.jsプロセスに適用します。

コンテナメモリの検出方法

コンテナ環境では、利用可能なメモリを以下の方法で検出できます。

ECS環境変数の利用

if [ ! -z "$ECS_CONTAINER_MEMORY_LIMIT" ]; then
  CONTAINER_MEMORY_BYTES=$((ECS_CONTAINER_MEMORY_LIMIT * 1024 * 1024))

cgroupからの検出

get_memory_from_cgroup() {
  if [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then
    # cgroup v1
    cat /sys/fs/cgroup/memory/memory.limit_in_bytes
  elif [ -f /sys/fs/cgroup/memory.max ]; then
    # cgroup v2
    cat /sys/fs/cgroup/memory.max
  else
    # 検出できない場合はデフォルト値 (1GB)
    echo $((1024 * 1024 * 1024))
  fi
}

OOMキラー発生の仕組みと防止策

この記事で見たことを以下の図にまとめてみました。
OOMキラーの発生の仕組みを理解して防止策を導入することは、アプリケーションの本番運用で意図せぬプロセスの犠牲が起こらないようにするためにもとても大事です。

最後に

現在(2025年4月)手が空いているので、気軽な相談役としてNode.js, TyepScript, インフラ支援をさせていただける会社様があればお声がけください! 気軽にTwitterか私のHPからメッセージください。

以下のような内容でお困りの場合特にお力になれるかもしれません・・
・技術負債の解消(歴20年くらいまでであれば対応可能
・AWS/IaCでの構築、インフラ全般の相談役
・エンジニア採用やプロダクト方針を含むスタートアップでの0-1立ち上げ

HP
https://tijuana-theta.vercel.app/

Discussion