NestJSで構築している担当プロダクトのビルドが地味に遅くなっているのは薄々気づいていたのですが、ついにデプロイ時にOOM Killされる事態になったので本腰を上げて調べてみました。
tsc でトランスパイルの効率を調べるtsc はトランスパイルするだけでなく、解析をする機能もいくつかついていて、今回はそれにだいぶお世話になりました。
extendedDiagnostics による解析まず1つ目の extendedDiagnostics はトランスパイルの内訳を時間や行数として出力してくれるもので、このような数値になりました。
1❯ npx tsc -p tsconfig.build.json --extendedDiagnostics 2Files: 4775 3Lines of Library: 41198 4Lines of Definitions: 2211971 5Lines of TypeScript: 82535 6Lines of JavaScript: 0 7Lines of JSON: 0 8Lines of Other: 0 9Identifiers: 2286631 10Symbols: 1440909 11Types: 148758 12Instantiations: 692686 13Memory used: 2035800K 14Assignability cache size: 67932 15Identity cache size: 2622 16Subtype cache size: 490 17Strict subtype cache size: 10926 18I/O Read time: 0.28s 19Parse time: 2.31s 20ResolveModule time: 0.39s 21ResolveLibrary time: 0.01s 22ResolveTypeReference time: 0.02s 23Program time: 3.38s 24Bind time: 1.65s 25transformTime time: 0.02s 26commentTime time: 0.00s 27printTime time: 0.09s 28Emit time: 0.09s 29Check time: 2.44s 30Source Map time: 0.00s 31I/O Write time: 0.01s 32Total time: 7.56s
一般的な数値がわからなかったのでAIに食わせてみると、このような見解でした。
| 指標 | 値 | コメント |
|---|---|---|
| Memory used: | 約2GB(2035800K) | 通常 Nest アプリの tsc では 400〜800MB 程度。明らかに高い。 |
| Lines of Definitions: | 2,212,971 行 | 明らかに外部ライブラリ or 型定義が大量。→ node_modules/@types や SDK類の影響が濃厚。 |
| Identifiers: | 2,286,631 | 通常数十万程度。TypeScript の型システムが相当な負荷に。 |
| Symbols: | 1,440,909 | 同上。依存型の解決コストが大きい。 |
| Types: | 148,758 | 高め。ジェネリクス + DTO/Entity の組み合わせが複雑かも。 |
| Instantiations: | 692,686 | 非常に多い。型推論が深く入り込んでいる可能性大。 |
確かにメモリ2GBは使いすぎですし、型定義も異様に多いのが気になるところです。
すぐに思いついたのが、依存している外部APIのOpenAPI定義からSDKを作って利用していたので、おそらくこれがBarrelで不要なコードを読み込ませてしまっていたのではないか、というものです。
SDKは Hey API で作成していたので、依存していた6つのAPIそれぞれを利用しているパスのみSDKを生成するようにした結果がこちらです。
1❯ npx tsc -p tsconfig.build.json --extendedDiagnostics 2Files: 3907 3Lines of Library: 41836 4Lines of Definitions: 623962 5Lines of TypeScript: 61760 6Lines of JavaScript: 0 7Lines of JSON: 0 8Lines of Other: 0 9Identifiers: 815861 10Symbols: 664803 11Types: 131985 12Instantiations: 591658 13Memory used: 906866K 14Assignability cache size: 68451 15Identity cache size: 2845 16Subtype cache size: 779 17Strict subtype cache size: 10741 18I/O Read time: 0.59s 19Parse time: 0.88s 20ResolveModule time: 0.40s 21ResolveLibrary time: 0.01s 22ResolveTypeReference time: 0.02s 23Program time: 2.13s 24Bind time: 0.61s 25transformTime time: 0.21s 26commentTime time: 0.00s 27printTime time: 0.30s 28Emit time: 0.30s 29Check time: 2.24s 30Source Map time: 0.00s 31I/O Write time: 0.01s 32Total time: 5.27s
ざっと比べるとこのような感じです。 メモリは半分以下になり、型定義は3分の1ほどに減りました。
1# Memory used: 2Before 2,035,800K 3After 906,866K 4 5# Lines of Definitions: 6Before 2,211,971 7After 623,962 8 9# Lines of TypeScript: 10Before 82,535 11After 61,760
generateTrace による解析generateTrace は extendedDiagnostics と同じように tsc のオプションで、これをつけるとトレース情報をファイルとして出力してくれます。
詳細は末尾に記載する記事に任せますが、だいたいこのような実行方法です。
1npx tsc -p tsconfig.build.json --generateTrace ~/Desktop/trace --incremental false
出力されたファイルはChromeの chrome://tracing/ に食わせるとそれぞれのプロセスを可視化してくれます。