Node.jsのメモリ制限 (2024年版)
Node.jsのメモリ制限については以下の記事に記述があります。
しかし、現在の挙動はやや異なるようです。
結論から言うと
デフォルトでは、システム (cgroup等) から取得した制限があればそれがそのまま設定、そうでなければ32bit環境では700MiB, 64bit環境では1400MiBの制限が設定されます。
V8のメモリ制限
Node.jsはJavaScriptエンジンとしてV8を利用しています。
V8のGCは世代別GCになっています。ほとんどのオブジェクトは生成されてすぐに不要となるため、メモリ使用量にはそれほど貢献しません。メモリ使用量に貢献するような長命なオブジェクトは、数回のGCを生き抜いた後old generation領域に移されます。したがって、V8のメモリ使用量の制限は実質的にこのold generation領域のサイズ制限によって決まると考えてよいでしょう。
このold generation領域のサイズ制限については既存記事の記述がほぼ正確ですが、ここであらためて説明をしておきます。
V8のデフォルトの挙動では、
- 最も優先されるのは最小サイズと最大サイズの制約です。
- old generation領域は最小でもページサイズの3倍のサイズを持つ必要があります。
- ポインタ圧縮有効時は、old generation領域は最大でも32bit空間の最大サイズから他の領域用の予約分を除いたサイズまでしか増やすことができません。ポインタ圧縮が無効の場合はサイズ指定の最大値制限はありません。
- 上記の制約の範囲内で、
max_old_space_size
引数 が指定されている場合はその値が使われます。- これはV8の
--max-old-space-size
引数 から指定可能です。
- これはV8の
- この指定がない場合で、
max_heap_size
引数がある場合は、この値から導出されるold generation領域のサイズが使われます。- これはV8の
--max-heap-size
引数 から指定可能です。
- これはV8の
- それも存在しなければ、
max_old_generation_size_in_bytes
制約の値が使われます。- これはプログラマブルなAPIであり、コマンドライン引数にはなっていません。
- それも存在しなければ、32bit環境では700MiB, 64bit環境では1400MiBが設定されます。
したがって、V8のデフォルトは 32bit環境では700MiB, 64bit環境では1400MiBです。
Node.jsのメモリ制限
ところが、Node.jsはV8のデフォルトをそのまま使っているわけではありません。
まず、Node.jsはV8のコマンドライン引数をそのまま受け付けるため、 --max-old-space-size
および --max-heap-size
はそのまま指定可能です。
加えて、Node.jsでは max_old_generation_size_in_bytes
制約を独自に指定しています。ここでは、 total_memory
に有効な値が入っていれば、それを max_old_generation_size_in_bytes
に設定する よう実装されています。
では、 total_memory
とは何でしょうか。この値はlibuvのuv_get_constrained_memory
というAPIから取得されており、Linuxではcgroupの制限から取得していることがわかります。
したがって、Node.jsでのデフォルトはシステム (cgroup等) から取得した制限があればそれがそのまま設定、そうでなければ32bit環境では700MiB, 64bit環境では1400MiBです。
これらはいつ導入されたか
Node.jsにおけるデフォルト挙動の変更はNode.js 12.0.0から導入されています。
まとめ
デフォルトでは、システム (cgroup等) から取得した制限があればそれがそのまま設定、そうでなければ32bit環境では700MiB, 64bit環境では1400MiBの制限が設定されます。
最後に
本記事は私がLegalscapeで業務委託として関わっていたプロジェクトで、CI上でNode.jsのメモリ制限を超えるようになる問題に遭遇したことから執筆しました。
Legalscapeは国内リーガルテック企業としては特異的な立場にあり、なかなか面白いプロダクトを作っています。メンバーも強者揃いですので、ぜひ興味があれば以下のページを覗いてみてください。
Discussion