🕶️

Node.jsのメモリ制限 (2024年版)

2024/02/17に公開

Node.jsのメモリ制限については以下の記事に記述があります。

https://qiita.com/kawanet/items/cfedd535990b32710c50

しかし、現在の挙動はやや異なるようです。

結論から言うと

デフォルトでは、システム (cgroup等) から取得した制限があればそれがそのまま設定、そうでなければ32bit環境では700MiB, 64bit環境では1400MiBの制限が設定されます。

V8のメモリ制限

Node.jsはJavaScriptエンジンとしてV8を利用しています。

V8のGCは世代別GCになっています。ほとんどのオブジェクトは生成されてすぐに不要となるため、メモリ使用量にはそれほど貢献しません。メモリ使用量に貢献するような長命なオブジェクトは、数回のGCを生き抜いた後old generation領域に移されます。したがって、V8のメモリ使用量の制限は実質的にこのold generation領域のサイズ制限によって決まると考えてよいでしょう。

このold generation領域のサイズ制限については既存記事の記述がほぼ正確ですが、ここであらためて説明をしておきます。

V8のデフォルトの挙動では、

  1. 最も優先されるのは最小サイズと最大サイズの制約です。
  2. 上記の制約の範囲内で、max_old_space_size 引数 が指定されている場合はその値が使われます。
  3. この指定がない場合で、 max_heap_size 引数がある場合は、この値から導出されるold generation領域のサイズが使われます
  4. それも存在しなければ、 max_old_generation_size_in_bytes 制約の値が使われます。
    • これはプログラマブルなAPIであり、コマンドライン引数にはなっていません。
  5. それも存在しなければ、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は国内リーガルテック企業としては特異的な立場にあり、なかなか面白いプロダクトを作っています。メンバーも強者揃いですので、ぜひ興味があれば以下のページを覗いてみてください。

https://legalscape.notion.site/09aeb478072946c18249495b8fb63fcd

Legalscape(リーガルスケープ)

Discussion