はじめに
npm パッケージを用いた JavaScript のプロジェクトを運用していると、yarn.lock や pnpm-lock.yaml がどんどん膨らんでいくことに気づくことがあります。
私の場合、当時 yarn v1 で運用していたプロジェクトに新しくアサインされたメンバーから発せられた言葉がきっかけでした。
「yarn install が遅い」と。
開発体験を向上せねば、と調査し、チーム内で話し合った結果、たどり着いた解決策が dedupe コマンドでした。
なぜ yarn install が遅くなってしまったのか
yarn cache を削除した後、私のローカル環境にて yarn install --verbose で実行ログを確認したところ、Copying yarn cache dir で約400秒かかっていることがわかりました。
マシンのスペックやネットワーク速度、キャッシュの有無で実行時間は前後しますが、キャッシュコピーの対象となるパッケージが多いほど、この処理時間は長くなります。
なぜキャッシュコピーの対象となるパッケージが多いだろうか。yarn.lock を読み解いていくと、同じパッケージが異なるバージョンで複数エントリとして記録されている「重複エントリ」があることに気づきました。
なぜ lock ファイルに重複エントリが生まれるのか
yarn や pnpm は、インストールの再現性を保証するために lockfile を使用します。一度解決されたバージョンは、明示的に更新を指示しない限り変更しないのが基本方針のようです。
たとえば、以下のような状況を考えてみます
- ある依存パッケージが
foo@^2.3.4を要求しており、lockfile にfoo@2.3.4として記録されている - その後、自分のプロジェクトで
yarn add foo@2.10.14を実行する -
foo@2.3.4はfoo@2.10.14の要求範囲を満たさないため、Yarn は新たにfoo@2.10.14を別エントリとして追加する
この結果、lockfile には foo@2.3.4 と foo@2.10.14 という 2つのエントリが共存することになります。
実際には ^2.3.4 は 2.10.14 を受け入れられるので、foo@2.10.14 にまとめられるはずですが、この最適化を自動では行いません。
こうした重複が積み重なることで、lockfile が膨らみ、インストール時間の増大につながっていました。
dedupe コマンドとは
dedupe は「範囲は重なっているのに異なるバージョンに解決されている」状態を整理し、できる限り 1 つのバージョンにまとめるコマンドです。
lockfile から古い依存を除去し、新しいバージョンに統合することができます。
以下のようなメリットがあると考えます。
- lockfile のエントリ数が削減される
- インストールされるパッケージ数が減少し、ディスク使用量を抑制できる
- ビルド・インストール時間の短縮にもつながる
yarn v1 で実施したい場合
yarn v1 でできる限り 1 つのバージョンにまとめる場合は、 yarn-deduplicate を使うと整理できます。
変更前後での比較
対応当時は暫定対応として、yarn v1 のまま yarn-deduplicate を用いて lock ファイルを整理しました。
yarn install の総実行時間として、約700秒から約60秒に短縮されました。重複エントリを解消するだけで、インストール時間が大幅に改善されました。
その後、恒久対応として yarn/berry へのアップグレードや仕組みづくりを実施していただきました。
yarn/berry へアップグレードすることで、ビルドインの yarn dedupe が使えるようになりました。
また、renovate でパッケージ更新を管理していたため、postUpdateOptions で dedupe コマンドを実行する設定をしました。ライブラリ更新の Pull Request で出力される lock ファイルがすでに dedupe コマンド実行後のものになり、重複エントリが蓄積し続けることを防ぐようにしました。
運用によっては、yarn dedupe --check のようなチェックコマンドがあるため、CI 等に組み込み、継続的に検査することも考えられるかもしれません。
おわりに
メンバーの一声から、定期的に依存パッケージを最適化することで、開発体験の向上に繋がることを知る良い機会でした。npm, yarn/berry, pnpm に dedupe コマンドがあることを知らなかった方もいらっしゃると思います。
同じようなことで悩んでいる方がいましたら、参考にしていただけると幸いです。