この記事は Cybozu Advent Calendar 2021 および 開発者体験:DXをめちゃくちゃ改善した話 Advent Calendar 2021 の13日目です。
こんにちは、@tasshiです。
最近は麺入りお好み焼きがマイブームです。
概要
この記事では、IntelliJ IDEAの共有インデックスという機能を使って、開発の待ち時間を削減する方法を紹介します。また、実際にCI上で共有インデックスを作成してみます。
この記事で紹介している共有インデックス機能はJetBrains社製IDEの有料ライセンスが必要です。(2021年12月現在)
※IntelliJ IDEA Communityでも期間限定(30日間)で利用できます。
実際に運用する際にはCI上のIntelliJ IDEA Ultimateについてライセンスのアクティベーションが必要です。
Floating License Server、もしくはオフラインアクティベーションコードなどでアクティベーションしてください。
IntelliJ IDEAのインデックスについて
IntelliJ IDEAでは全文検索、リファクタリング、コードジャンプなどの支援機能を使用することができます。これらの機能はプロジェクトコード全体を対象としながら瞬時に実行されます。これを実現しているのがインデックスと呼ばれるキャッシュです。
インデックスはプロジェクトのコードに含まれるクラスやメソッド、その他コード要素に対する様々な仮想マップです。1IntelliJ IDEAの場合、1つのプロジェクトに対して異なる208種のインデックスを保持しています。2
インデックス作成について
このようにインデックスはコーディング支援機能のために欠かせないものですが、その作成時間は開発体験を損なうことがあります。
インデックス作成はプロジェクトのオープンやブランチの切り替え、プロジェクト内で大きな差分が出た場合などに実行され、その所要時間はプロジェクトの規模に応じて増加します。
大規模なプロジェクトでは作成時間は5分を超えることもあり、インデックス作成中はコードジャンプなどの支援機能はほぼ利用できない3ため、開発者は支援機能なしに開発を行うことになります。プロジェクト・開発メンバーによりますが、これは非常に困難なことです。私の場合は諦めてコーヒーを飲みながら通知の確認をしています。
そのため、インデックス作成時間の短縮は、開発体験・生産性向上のために重要であると言えます。
インデックス作成時間を短縮するには、以下のような方法があります。
- 共有インデックスの使用
- ファイルとフォルダーの除外
- モジュールのアンロード
- マシンスペックの増強
- コードベースの改善
これらのうち、今回は共有インデックス(Shared indexes)を紹介します。
共有インデックス(Shared indexes)について
IntelliJ IDEA 2020.3から共有インデックスというものが利用できるようになりました。4
これは作成済みのインデックスをCDNから自動でダウンロードして、ローカルのインデックス作成時間を短縮するものです。
従来のインデックス作成の問題点として、複数の開発者が、同じプロジェクトを、別々の端末で開いて、それぞれにインデックス作成が行われ、同じインデックスが作成されていました。
これはチーム・コミュニティが大きければ大きいほど、計算リソースの無駄を生む状態です。
共有インデックスではインデックス作成を行うのは1つのコンピューターだけで、他のコンピューターはその結果を受け取るだけで済みます。
開発者は十分なネットワーク帯域さえあれば高速にインデックスを得ることができ、またチーム・コミュニティ全体では大幅な計算リソースの削減になります。
詳しく知りたい方は以下のセッションを見るのがおすすめです。
共有インデックスの種類
共有インデックスには「JDKおよびMavenライブラリの共有インデックス」と「共有プロジェクトインデックス」の2種類があります。
どちらも使用にはShared Project Indexesプラグイン5のインストールが必要です。
JDKおよびMavenライブラリの共有インデックス
「JDKおよびMavenライブラリの共有インデックス」はJDKやMavenリポジトリ上の人気ライブラリに対する共有インデックスです。
JDK および Maven ライブラリの共有インデックス - 共有インデックス | IntelliJ IDEA
これはJetBrainsが専用の公開CDN6から提供しており、必要に応じて自動でダウンロードされるように設定できます。
利用するにはPreferences
→ Tools
→ Shared Indexes
から設定できます。
またポップアップ通知からも設定できます。
- Wait for shared indexes: 有効にする
- 共有インデックスをダウンロードするまでローカルのインデックス作成を待つようにできます。特に理由がなければ有効にしておいて良いと思います。
- Public Shared Indexes:
- JDKs:
Download automatically
を選ぶ - Maven Libraries:
Download automatically
を選ぶ
- JDKs:
- Shared Project Indexes
- 後述の共有プロジェクトインデックスの設定です。ここでは設定しません。
共有プロジェクトインデックス
「共有プロジェクトインデックス」はプロジェクトに対する共有インデックスで、プロジェクト内のソースファイル、ライブラリ、SDKがインデックスの対象になります。
これをプロジェクトに関わる開発メンバー全員で共有することで開発チーム全体のインデックス作成時間を短縮することができます。
JetBrainsからはインデックスの作成に必要なツールが提供されているため、開発チームでサーバーを用意し独自のインデックス用CDNをホスティングすることが可能です。
インデックスはプロジェクトの状態、具体的にはVCSリビジョン識別子(Gitのコミットハッシュなど)ごとに作成・管理されます。
共有プロジェクトインデックスおよびShared Project Indexesプラグインに関する詳細な仕様は以下のドキュメントを参照してください。
Shared Indexes Plugin - Google ドキュメント
共有プロジェクトインデックスをCI上で作成する
それでは共有プロジェクトインデックスを作成・公開してみましょう。
共有インデックスが作成されて使用されるまでの流れは以下のようになります。
- ローカルPCのIntelliJで開発を行う
- GitHubにCommitをPushする
- GitHub Actions上で共有インデックスを作成し、gh-pagesブランチにcommit&pushする
- 共有インデックスがGitHub Pagesで公開される
- 次回インデックス作成時に共有インデックスがローカルPCのIntelliJにダウンロードされる
実際に作成したリポジトリはこちらです。
tasshi-playground/idea-shared-indexes-demo: Demo project of Shared indexes
基本的には公式ドキュメントの手順に従って作成していきます。
0. プロジェクトを作成する
まずはJavaのプロジェクトを用意します。
今回はMavenのテンプレートプロジェクト8を使用しました。
$ mvn archetype:generate \
-DgroupId=com.mycompany.app \
-DartifactId=idea-shared-indexes-demo \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false
1. ファイルストレージを用意する
共有インデックスを公開するためのCDN・ファイルストレージを用意します。
今回はリポジトリのgh-pages
ブランチにコミットした共有インデックスをGitHub Pagesで公開します。
2. インデックス作成用のDockerコンテナを用意する
コンテナをビルドします。この中では以下のことを行います。
- 依存パッケージのインストール
- Amazon Corretto 11のインストール
- cdn-layout-toolのインストール
- IntelliJ IDEA Ultimateのインストールと設定
詳細はDockerfileを参照してください。
### cdn-layout-tool を解凍する
FROM ubuntu:20.04 as cdn-layout-tool-extractor
RUN set -ex \
&& apt-get update \
&& apt-get install -y curl unzip ca-certificates --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/cache/apt
ARG CDN_LAYOUT_TOOL_VERSION=0.8.60
RUN curl -LO https://packages.jetbrains.team/maven/p/ij/intellij-shared-indexes-public/com/jetbrains/intellij/indexing/shared/cdn-layout-tool/$CDN_LAYOUT_TOOL_VERSION/cdn-layout-tool-$CDN_LAYOUT_TOOL_VERSION.zip
RUN unzip cdn-layout-tool-$CDN_LAYOUT_TOOL_VERSION.zip -d /opt/ && mv /opt/cdn-layout-tool-$CDN_LAYOUT_TOOL_VERSION /opt/cdn-layout-tool
FROM ubuntu:20.04 as ubuntu-java-node-intellij-ja-base
ENV DEBIAN_FRONTEND=noninteractive
RUN set -ex \
&& apt-get update \
&& apt-get install -y curl \
&& apt-get install -y --no-install-recommends \
language-pack-ja-base language-pack-ja locales \
gnupg build-essential git apt-transport-https \
&& locale-gen ja_JP.UTF-8 \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/cache/apt
# https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/generic-linux-install.html
RUN echo "deb https://apt.corretto.aws stable main" >> /etc/apt/sources.list.d/corretto.list
RUN set -ex \
&& curl https://apt.corretto.aws/corretto.key | apt-key add - \
&& apt-get update \
&& apt-get install -y --no-install-recommends java-11-amazon-corretto-jdk \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/cache/apt
# IntelliJ IDEA Ultimateをインストール
RUN set -ex \
&& mkdir /opt/idea \
&& curl -L https://download.jetbrains.com/idea/ideaIU-2021.2.1.tar.gz | tar -xz -C /opt/idea --strip-components 1
# https://www.jetbrains.com/help/idea/shared-indexes.html#install-cdn-layout-tool
# cdn-layout-tool をインストール
COPY --from=cdn-layout-tool-extractor /opt/cdn-layout-tool /opt/cdn-layout-tool
# https://www.jetbrains.com/help/idea/shared-indexes.html#prepare-ide
# 共有インデックス作成のための設定
ENV TEMP /tmp/idea-indexes
RUN set -ex \
&& mkdir -p $TEMP/ide-system \
&& mkdir -p $TEMP/ide-config \
&& mkdir -p $TEMP/ide-log
RUN cp /opt/idea/bin/idea.properties $TEMP/ide.properties
RUN set -ex \
&& echo "idea.system.path=$TEMP/ide-system" >> $TEMP/ide.properties \
&& echo "idea.config.path=$TEMP/ide-config" >> $TEMP/ide.properties \
&& echo "idea.log.path=$TEMP/ide-log" >> $TEMP/ide.properties \
&& echo "idea.plugins.path=$TEMP/ide-plugin" >> $TEMP/ide.properties
ENV IDEA_PROPERTIES=$TEMP/ide.properties
# 共有インデックス作成スクリプトを追加
COPY generate-shared-indexes.sh /generate-shared-indexes.sh
CMD ["bash", "-c", "/generate-shared-indexes.sh"]
また、インデックスを行うスクリプトも用意します。
この中では以下のことを行います。
- プロジェクト内の依存関係の解消(Maven)
- 共有インデックスの作成
- 共有インデックスのメタデータを更新
- 更新した共有インデックスをcommitしてpushする
#!/bin/bash -ex
set -euo pipefail
shopt -s nullglob
export WORKSPACE=$GITHUB_WORKSPACE
export COMMIT_HASH=$(git -C $WORKSPACE rev-parse --short HEAD)
export FILE_STORAGE_REPO="https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git"
# プロジェクト内の依存関係を解消しておく
$WORKSPACE/mvnw dependency:resolve
# https://www.jetbrains.com/help/idea/shared-indexes.html#export-project-indexes-command-line
# 共有インデックスの生成
/opt/idea/bin/idea.sh dump-shared-index project --output=$TEMP/generate-output --tmp=$TEMP/temp --project-dir=$WORKSPACE --project-id=$PROJECT_NAME
ls -la $TEMP/generate-output
echo "save to $TEMP/indexes/project/$PROJECT_NAME/$COMMIT_HASH/share"
# https://www.jetbrains.com/help/idea/shared-indexes.html#copy-files-to-folder
git clone --single-branch -b gh-pages --depth=1 $FILE_STORAGE_REPO $TEMP/file-storage/
mkdir -p $TEMP/file-storage/indexes/project/$PROJECT_NAME/$COMMIT_HASH/share
cp $TEMP/generate-output/* $TEMP/file-storage/indexes/project/$PROJECT_NAME/$COMMIT_HASH/share/
ls -la $TEMP/file-storage/indexes/project/$PROJECT_NAME/$COMMIT_HASH/share/
# https://www.jetbrains.com/help/idea/shared-indexes.html#generate-metadata
# 共有インデックスのメタデータを更新
/opt/cdn-layout-tool/bin/cdn-layout-tool --indexes-dir=$TEMP/file-storage/indexes --url=$FILE_STORAGE_ROOT_URL
# 更新した共有インデックスをcommitしてpushする
git -C $TEMP/file-storage/ config --local user.email "github-actions[bot]@users.noreply.github.com"
git -C $TEMP/file-storage/ config --local user.name "github-actions[bot]"
git -C $TEMP/file-storage/ add -A
git -C $TEMP/file-storage/ commit -m "update shared indexes by $COMMIT_HASH"
git -C $TEMP/file-storage/ push
注意事項として、共有インデックスの作成前にプロジェクト内の依存関係(maven, npmなど)を解消しておく必要があります。
これを行なっていないと...keep running open project / open project idea...
と表示されてインデクシングが進みませんでした。
また、cdn-layout-toolのメタデータ更新は、指定したディレクトリ内の共有インデックス以外の全てのファイルを削除してしまうので注意が必要です。
リポジトリの直下に共有インデックスを保存したところ、.git
ディレクトリが削除されてしまいました。
+ /opt/cdn-layout-tool/bin/cdn-layout-tool --indexes-dir=/tmp/idea-indexes/indexes --url=***
Running the 'cdn-layout-tool' tool 0.8.60
Collected 2 index.json groups
Collected 2 index.json groups
Removing 24 items...
Removing .git/HEAD with size 25 b
Removing .git/config with size 298 b
Removing .git/description with size 73 b
Removing .git/hooks/applypatch-msg.sample with size 478 b
Removing .git/hooks/commit-msg.sample with size 896 b
Removing .git/hooks/fsmonitor-watchman.sample with size 3079 b
Removing .git/hooks/post-update.sample with size 189 b
Removing .git/hooks/pre-applypatch.sample with size 424 b
Removing .git/hooks/pre-commit.sample with size 1638 b
Removing .git/hooks/pre-merge-commit.sample with size 416 b
Removing .git/hooks/pre-push.sample with size 1348 b
Removing .git/hooks/pre-rebase.sample with size 4898 b
Removing .git/hooks/pre-receive.sample with size 544 b
Removing .git/hooks/prepare-commit-msg.sample with size 1492 b
Removing .git/hooks/update.sample with size 3610 b
Removing .git/index with size 65 b
Removing .git/info/exclude with size 240 b
Removing .git/logs/HEAD with size 199 b
Removing .git/logs/refs/heads/gh-pages with size 199 b
Removing .git/objects/04/4420385f2d73aa989c994d9cffeddbdcb98e76 with size 141 b
Removing .git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 with size 15 b
Removing .git/packed-refs with size 116 b
Removing .git/refs/heads/gh-pages with size 41 b
Removing .git/shallow with size 41 b
Adding 4 items...
3. GitHub Actionsで共有インデックス作成を実行する
GitHub ActionsのWorkflowを設定します。
手順2. で作成したDockerコンテナを使用します。
実行条件はgh-pages
以外のブランチへのpushです。
環境変数は以下のとおりです
変数名 | 説明 |
---|---|
PROJECT_NAME | 共有インデックスのキーとなるプロジェクト名 |
FILE_STORAGE_ROOT_URL | 共有インデックスを公開する際のベースURL |
GITHUB_TOKEN | 共有インデックスをPushするレポジトリの書き込み権限のあるトークン 今回は同一リポジトリなので secrets.GITHUB_TOKEN をそのまま入力 |
JETBRAINS_LICENSE_SERVER | JetBrainsのFloating License Server9のインスタンスのURL CI上のIntelliJ IDEA Ultimateのアクティベートに必要です |
# Generate and upload IDEA Shared indexes
name: 'Indexing'
on:
push:
branches-ignore:
- 'gh-pages'
jobs:
indexing:
runs-on: ubuntu-latest
container:
image: tasshi-playground/idea-shared-indexes-demo:latest
steps:
- uses: actions/checkout@v2
- name: generate indexes
run: |
/generate-shared-indexes.sh
env:
PROJECT_NAME: idea-shared-indexes-demo
FILE_STORAGE_ROOT_URL: https://tasshi-playground.github.io/idea-shared-indexes-demo/indexes
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JETBRAINS_LICENSE_SERVER: ${{ secrets.JETBRAINS_LICENSE_SERVER }}
これでpushするとGitHub Actionsで共有インデックスが作成されるようになりました。
GitHub Actions経由でgh-pages
ブランチにコミットされていることが分かります。10
プロジェクトで共有インデックスを使用するようにする
いよいよプロジェクトで共有インデックスを使用できるようにします。
プロジェクトルートにintellij.yaml
を追加します。
sharedIndex
→project
→url
に共有インデックスの提供URLを入れます。
sharedIndex:
project:
- url: https://tasshi-playground.github.io/idea-shared-indexes-demo/indexes/project/idea-shared-indexes-demo
最後にPreferences
→ Tools
→ Shared Indexes
からダウンロード設定したら完了です。
ログを見ると、共有インデックスがダウンロードされて、インデクシングが完了したことが分かります。
15:17 Shared indexes for the project are downloaded (2.21 MB in 1 sec, 787 ms)
速度比較
Invalidate cache
でキャッシュを破棄してから、再度インデックス作成が完了するまでの時間を計測した結果、以下のようになりました。
検証環境はMacBook Pro (16-inch, 2019)です。
- 共有インデックスなし: 58.31秒
- 共有インデックスあり: 40.95秒11
インデックスありは約29.8%の所要時間減となりました。
公式の資料に記載の結果が37%だったので、少し低い結果となりました。
確認したところ、JDKに対してはローカルのインデックス作成が実行されていて、それが時間増の原因となっていました。ローカル環境とGitHub Actions上でJDKのバージョンが異なっていたのかもしれないです。
まとめ
いかがだったでしょうか。
IntelliJ IDEAのインデックス作成を高速化させる共有インデックスを紹介しました。
今回は軽量なテンプレートプロジェクトで試しましたが、実際の製品リポジトリなどではより効果を発揮すると思います。
うまく活用すればチーム全体の待ち時間を減らし、生産性を向上させることができると考えられます。
ぜひ試してみてください。
私自身もまだまだ探求していきたいと思うので、知見のある方のコメントお待ちしています!
-
How Many Indexes are There? - 2021-01 - IJ 20 - Shared Indexes & Indexes - Google スライド
上記支援機能群を実行時にこの仮想マップを参照することで高速に結果を返すことができます。 ↩ -
部分的に利用可能な機能もあります。 ↩
-
GitHub Actionsのボットがコミットしたようにアイコンをつけるにはメールに「github-actions[bot]@users.noreply.github.com」を指定すれば良い - nwtgck / Ryo Ota ↩