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/ に食わせるとそれぞれのプロセスを可視化してくれる。