こんにちは,株式会社 AbemaTV Web browser チームの山﨑です.

私たちのプロダクトでは JavaScript や TypeScript などの静的解析ツールである ESlint を CI で活用しています.しかし,ESlint の実行が不安定になっており開発速度や開発者体験が低下している問題が発生していました.具体的には, eslint を実行している CircleCI のインスタンスクラスを large にしても,job の失敗率が 50% を超えてしまっているような状態でした.

CI 上で実行している ESlint の実行成功率の画像.50 % を超える割合で失敗してしまっている.

この問題を解決すべく,eslint のルール毎の実行時間を計測する TIMIMG オプションを活用することでボトルネックとなっていたルールの特定・精査を行いました.

今回の記事では,不安定だった CI 上での ESlint 実行を TIMIMG オプションを活用し安定化させるに至った話をします.

ESLint とは

ESLint は JavaScript および TypeScript コードの静的解析ツールであり,コードの品質を保つために広く使用されています.ESLint を利用することで,コーディングスタイルの一貫性を保ち,潜在的なバグを事前に検出することができます.特に大規模なプロジェクトでは,開発者全員が同じルールセットを共有することで,コードベースの品質を維持しやすくなります.

ESLint を CI 環境で実行することで,コードの品質を自動的にチェックすることができます.これにより,コードがリポジトリにマージされる前にすべてのコードが一定の品質基準を満たしていることを保証できます.CI に ESLint を組み込むことで,以下のような利点があります:

  • 一貫性の向上:全てのコードが同じルールセットでチェックされるため,コードベースの一貫性が保たれます.
  • 早期の問題発見:開発プロセスの初期段階で問題を発見し,修正することができます.
  • 開発効率の向上:開発者が手動でコードレビューする負担が軽減され,より重要なタスクに集中できます.

TIMING オプションについて

ESLint には,各ルールの実行時間を測定するための TIMING オプションが用意されています.このオプションを使用することで,どのルールが実行に時間を要しているかを確認することができます.TIMING オプションを利用するには,コマンドラインで以下のように指定します:

TIMING=1 eslint lib

TIMING には以下の値を渡すことができます.

  • 10 以下の整数: 時間がかかった上位 10 個のルールを表示します.
  • 11 以上の整数: 時間がかかった順に指定した数のルールを表示します.
  • all: すべてのルールを表示します.

これにより,各ルールにかかる実行時間が詳細に出力されます.

今回成し遂げたい本来の目的は, CI の実行時間を短縮することではなく CPU 稼働率を下げることではありますが,実行時間が掛かっているルールは CPU の稼働率も高くなるという仮定のもと,TIMING=1オプションをつけて ESLint の各ルールの実行時間を計測しました.
実際に得られた出力は以下の通りです.

Rule                                    | Time (ms) | Relative
:---------------------------------------|----------:|--------:
import/namespace                        |  9692.968 |    39.0%
@typescript-eslint/no-misused-promises  |  5499.420 |    22.1%
redos/no-vulnerable                     |  3741.578 |    15.0%
@typescript-eslint/no-floating-promises |  1490.239 |     6.0%
...

この出力は,各ルールがどれだけの時間を消費しているかを示しており,どのルールがパフォーマンスに影響を与えているかを一目で把握することができます.

この出力から,import/namespace というルールに全体実行時間の 40% を占める 約 10 秒もの時間を要していることが分かりました.

不必要なルールを有効にしていないかチェック

ESLint の実行時間を最適化するためには,不必要なルールが有効になっていないかを確認することが重要です.TIMING オプションによりimport/namespace に多くの実行時間を要していることが特定できたので,このルールに関して本当に必要なルールなのか精査しました.

import/namespaceはインポートしたオブジェクトから存在しないプロパティにアクセスしようとするとエラーとして検出してくれるルールです.
このルールは typescript-eslint のドキュメントに記述があるように,TypeScript が行ってくれる静的解析によりチェックできることを二重で確認することになるので,TypeScript を使用している場合は無効化が推奨されているルールとなります.

import/namespaceだけでなく

も同様の理由で無効化が推奨されているので,もし有効になっている場合はパフォーマンス改善の見込みがあります.

結果

TIMINGオプションを使用し実行する esliht ルールの最適化を行ったことで,CI 上の ESlint の実行を安定化させることができました.
最適化前に 50% 以上あった job の失敗率が最適化後では 10% 前後まで改善されています.

TIMINGオプションを使用して実行する esliht ルールの最適化を行ったあとの CI 上での ESlint 実行成功率.最適化前に 50% 以上あった job の失敗率が最適化後では 10% 前後まで改善されている.

また,最適化前では失敗している job に対して多くのクレジットが費やしていましたが,最適化後では意図しない job の失敗が大幅に減ったため,CI に掛かっているコスト削減にも繋がりました.

最適化前後での,成功した CI と失敗した CI にかかっているスレジットの比率推移図. 最適化後では意図しない job の失敗が大幅に減ったため,CI に掛かっているコスト削減にも繋がった.

まとめ

本記事では,CI 上での ESLint の実行を安定化させるための取り組みについてご紹介しました.具体的には,ESlint の TIMING オプションを活用し,不必要なルールの精査することで ESLint の負担の軽減を行いました.
結果的に,CI 上での ESLint 実行が安定化し,job の失敗率が大幅に低減.また CI に掛かるコストの削減にも成功しました.
この取り組みにより CI の安定性が向上し,開発チーム全体の効率も向上しました.今後も定期的なルールの見直しなどを行い,さらに開発環境の最適化を進めていきたいと考えています.

この記事が同様の問題を抱えている方々の参考になれば幸いです.