タカリ:ステロイドのMaven

背景



かなり長い間、私はMavenビルドシステムを使用するプロジェクトに取り組んできました。 当初、プロジェクトが現在ほど大きくなかったとき、その完全なコンパイルの時間は比較的合理的であり、苦情を引き起こしませんでした。 しかし、時間の経過とともに、コードが増大し、サブプロジェクトの数が劇的に増加し、完全なコンパイルの平均時間は6〜10分に増加しました。 これは、開発者からの非難の絶え間ない情報源となりました。



また、注意する必要があります。 並列アセンブリを使用しなかったのは、 これは定期的にさまざまな問題を引き起こしました。 ローカルストレージ内のアーティファクトが破壊されるか、間違った順序で単純に収集され、古い未コンパイルのコードが最終的なWARアーティファクトに分類されます。 もちろん、一部の開発者は、独自の危険とリスクで並列アセンブリを使用しました。 しかし、遅かれ早かれ、彼らは何が起こっているのか理解できない状況に陥りました。 そして、1つのスレッドへの簡単な再コンパイルがすぐに役立ちました。



これはかなり長い間続いていましたが、Mavenでの作業方法を改善する方法を提供するかなり好奇心の強いTakariのWebサイトに出くわしました。



最も興味深いのは次の3つです。



また、GitHubでMaven Wrapper (Gradleのラッパーのアナログ)をレイアウトしました。



ここで説明するツールは、Mavenの誤動作の問題を解決するだけでなく、ビルド速度を大幅に向上させることに注意してください。



並行安全ローカルリポジトリ



この改善は、ローカルリポジトリの破損したアーティファクトの問題を解決することを目的としています。



実際、Mavenでは、ローカルストレージ(基本的にはファイルシステム上のディレクトリ)での作業は、スレッドセーフではない方法で実装されています。 つまり 並行してアセンブルされているプロジェクトが同時に同じ依存関係を出力し始めた場合、結果は壊れたファイルになります。 このサプリメントが解決するのはまさにこの問題です。



それを使用するには、インストール済みのMavenを直接変更する必要があります。



curl -O https://repo1.maven.org/maven2/io/takari/aether/takari-local-repository/0.10.4/takari-local-repository-0.10.4.jar mv takari-local-repository-0.10.4.jar $M2_HOME/lib/ext curl -O https://repo1.maven.org/maven2/io/takari/takari-filemanager/0.8.2/takari-filemanager-0.8.2.jar mv takari-filemanager-0.8.2.jar $M2_HOME/lib/ext
      
      





それだけです これ以上のアクションは不要です。 これで、ローカルリポジトリでのすべての操作が安全になります。 それ自体では、多くのアセンブリが同時に発生し、スペースを節約するために1つのリポジトリを使用する場合、この拡張機能はCIサーバーでのみ使用できます。 しかし、通常の開発者にとっては、この拡張機能が既にインストールされているという前提で機能するSmart Builderと組み合わせて使用​​する方が興味深いです。



経験からわかるように、このソリューションを使用すると、アセンブリの動作が少し遅くなりますが、より信頼性が高くなります。



タカリスマートビルダー



この拡張機能は、前の拡張機能と同様にインストールされます。



 curl -O https://repo1.maven.org/maven2/io/takari/maven/takari-smart-builder/0.4.0/takari-smart-builder-0.4.0.jar mv takari-smart-builder-0.4.0.jar $M2_HOME/lib/ext
      
      





また、Mavenプロジェクトビルド用のより高度な並列化アルゴリズムを提供します。 標準のMavenアセンブリスケジューラとSmart Builderの動作の違いを次の図に示します。







標準のMaven並列化戦略は単純で単純です。 これは、依存関係の深さの計算に基づいています。 Mavenは、プロジェクトが終了するまで同じレベルですべてのプロジェクトの並列ビルドを実行し、次のレベルに進みます。



一方、Takari Smart Builderは、より高度な戦略を使用します。 彼は依存関係のチェーンを計算し、トポロジカルソートを実行し、その後でのみプロジェクトのアセンブリが必要なシーケンスについて決定を下します。



さらに。 コンパイルプロセス中に、 .mvn /timing.propertiesファイル内の各プロジェクトのコンパイル時間を記憶し、次回のコンパイルをできるだけ早く完了するために追加情報として使用します



この機能を使用するには、Mavenの起動時に追加のキーを指定する必要があります。 例:



 mvn clean install --builder smart -T1.0C
      
      





Maven 3.3.1で物事が簡単に



バージョンMaven 3.3.1では、いくつかの革新が実装されました。 何よりもまず、プロジェクトでMavenカーネル拡張機能を直接宣言する機能。 これを行うには、 .mvn / extensions.xmlファイルを追加します。 前述の付録では、このファイルの形式は次のとおりです。



 <?xml version="1.0" encoding="UTF-8"?> <extensions> <extension> <groupId>io.takari.maven</groupId> <artifactId>takari-smart-builder</artifactId> <version>0.4.1</version> </extension> <extension> <groupId>io.takari.aether</groupId> <artifactId>takari-local-repository</artifactId> <version>0.11.2</version> </extension> </extensions>
      
      





これで、ライブラリをMavenディストリビューションに直接報告する必要がなくなりました。 そうすることで、同じ結果が得られます。



extensions.xmlファイルは、 .mvnディレクトリ内の唯一のファイルではありません。 ここにさらに2つのファイルを配置できます: jvm.configおよびmaven.config



jvm.configには、現在のプロジェクトのコンパイルを開始するJVMオプションが含まれています。 たとえば、このファイルは次のようになります。



 -Xmx2g
 -XX:+ TieredCompilation
 -XX:TieredStopAtLevel = 1


最初のオプションはヒープサイズを2 GBに設定し、次の2つはMavenのニーズに合わせてJVMを最適化します( こちらをご覧ください )。



maven.configは別のパラメーターを持つファイルですが、今回はMaven自体です。 例:



 -ビルダースマート
 -T1.0C
 -e


したがって、デフォルトがスマートビルダーであり、スレッドの数が論理コアの数に等しいことを指定できます。 つまり、私たちがただやれば



 mvn clean install
      
      





その後、アセンブリは複数のスレッドで実行され、すべての拡張機能と最適化が使用されます。 さらに、組み込みモジュールをビルドしても、これらの設定は引き続き適用されます。 Mavenは、現在のディレクトリだけでなく、その親でも.mvnディレクトリを検索します。



本当に一つの注意点があります。 なぜなら アセンブリは複数のスレッドに入るので、アセンブリログはこれらのスレッドによって競合して表示されます。 その結果、問題が発生した場合、行が混在しているという事実により、何が起こるかが常に明確ではありません。 この場合、1つのスレッドでアセンブリを実行し、問題の原因を把握したい場合は、アセンブリを手動でシングルスレッドモードに切り替える必要があります。



 mvn -T1 clean install
      
      





タカリのライフサイクル



Takariライフサイクルは、デフォルトのMavenライフサイクル(JARファイルの構築)の代替です。 その特徴的な機能は、1つの標準ライフサイクルの5つの個別のプラグインの代わりに、同じ機能を備えながら依存関係がはるかに少ない1つの汎用プラグインが使用されることです。 その結果-起動がはるかに速くなり、パフォーマンスが最適化され、リソース消費が少なくなります。 これにより、多数のモジュールを含む複雑なプロジェクトをコンパイルする際に、パフォーマンスが大幅に向上します。



アップグレードされたライフサイクルをアクティブにするには、アセンブリに拡張機能としてtakari-lifecycle-pluginを追加する必要があります。



  <build> <plugins> <plugin> <groupId>io.takari.maven.plugins</groupId> <artifactId>takari-lifecycle-plugin</artifactId> <extensions>true</extensions> </plugin> </plugins> </build>
      
      





また、JARモジュールのアセンブリをtakari-jarとしてオーバーライドします。



  <project> <modelVersion>4.0.0</modelVersion> <groupId>io.takari.lifecycle.its.basic</groupId> <artifactId>basic</artifactId> <version>1.0</version> <packaging>takari-jar</packaging>
      
      





その後、POMなどのすべてのプロジェクト、およびtakari-jarプロジェクトは、新しいライフサイクルを使用してアセンブルされます。



すべてのJARモジュールに対してこのライフサイクルを有効にすることもできます(ドキュメントを参照)。この場合、さまざまなMavenプラグインとの競合が発生し始めました。 その結果、モジュールのパッケージを単純に再定義することが決定されました。アセンブリを損なうことなく実行できます。 実践が示しているように、これは十分すぎることが判明しました。



takari-lifecycle-plugin拡張機能を使用すると、さまざまなビルド設定の場所が変更されることにも注意してください。 これらは、このプラグインの構成セクションに移動します。 例:



  <plugins> <plugin> <groupId>io.takari.maven.plugins</groupId> <artifactId>takari-lifecycle-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins>
      
      





詳細については、 ドキュメントを参照してください



タカリメイベンラッパー



タカリには、 Maven Wrapperという素晴らしいものがあります。 Gradle Wrapperと同様に、クローン作成後すぐにプロジェクトの構築を開始できます。 コンピューターにMavenをインストールおよび構成する必要はありません。 さらに、これにより、Mavenの必要なバージョンをプロジェクトに割り当てることができます。



プロジェクトにラッパーを追加する最も簡単な方法は、アーキタイプを使用することです。 プロジェクトのルートで実行します。



 mvn -N io.takari:maven:wrapper
      
      





その後、現在のディレクトリに2つのスクリプトがあります。





また、 ラッパー自体とその構成ファイルは.mvn / wrapperディレクトリに表示されます。



それだけです その後、次を呼び出すことができます。



 ./mvnw clean install
      
      





また、別のバージョンのMavenが必要な場合は、必要なURLを.mvn / wrapper / maven-wrapper.properties構成で設定できます



繰り返しになりますが、ニュアンスがなければ機能しません。 たとえば、閉じたネットワークを持つ組織は、多くの場合、 NexusArtifactoryなどのプロキシMavenリポジトリを使用します。 この場合、各開発者は、このリポジトリに対して独自のミラー(ミラー)Mavenを個別に構成する必要があります。 ラッパーのイデオロギーとわずかに矛盾するのは、設定の必要がないことです。



次のようにして状況から抜け出すことができます。プロジェクトで、 の形式の.mvn / settings.xmlファイルを作成します。



 <?xml version="1.0" encoding="UTF-8"?> <settings> <mirrors> <mirror> <id>nexus-m2</id> <mirrorOf>*</mirrorOf> <url>http://repo.org.ru/nexus/content/groups/repo-all-m2</url> <name>Nexus M2</name> </mirror> </mirrors> </settings>
      
      







.mvn / maven.configファイルに行を追加します



 --global-settings .mvn / settings.xml


その結果、ミラーが自動的に検出されます。



テストと結果



上記のすべてが、プロジェクトのアセンブリの加速に関して印象的な結果をもたらさなければ意味がありません。 そして、根拠がないように、私たちの作業プロジェクトで得られた結果を引用します。



だから私たちは:



なぜなら 「バニラ」のMavenでマルチスレッドアセンブリの問題が観察された場合、(ほぼ)常に1つのスレッドのみが使用され、最終的にはビルド時間が5:32(5分32秒)以上になりました。 すべての最適化(並列ビルド+ takariライフサイクル)の後、ビルド時間は1:33でした。 ほぼ4回!



中間結果はすべて以下の表にまとめられています。

どうだった スレッド数 費やした時間
デフォルト 1 5:32
デフォルト 4 3:25
スマートビルド 4 3:18
スマートビルド+ takari-jar 1 3:23
スマートビルド+ takari-jar 4 1:33


Smart Buildが2回起動され、2番目の結果が記録されました。 最初の起動後、アセンブリの実行順序が最適化される場合があります(ドキュメントを参照)。



奇妙なことに、シングルスレッドモードでのタカリライフサイクルの追加により、4番目のスレッドのアセンブリと同じパフォーマンスが向上しますが、「バニラ」Mavenでのパフォーマンスが向上します。



結論として





この記事で説明したツールを最近発見しました。 したがって、それらの使用方法は依然として非常に控えめです。 おそらく、いくつかの落とし穴は時間の経過とともにまだ出てくるでしょう。 しかし、いずれにせよ、アセンブリのこのような急進的な加速は、技術プロセスでこれらの機会を使用するリスクを負うのに十分でした。 時間が経てば、それがどうなるかがわかります。



また、 github Takariのリポジトリには、さらに興味深いプロジェクトがいくつかあります。 彼らの説明はこの記事の範囲外ですが、おそらく誰か他の人が何か他のものに興味を持つでしょう。



UPD



コメントですでに述べたように、フィードバックは開発者から寄せられ始めました。 mvnw.batファイルはその機能を実行しないことが判明しました。 簡単な修正が行われ、機能が適切な形式になりました。



修正されたスクリプト
 @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven2 Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM eg to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto chkMHome echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :chkMHome if not "%M2_HOME%"=="" goto valMHome SET "M2_HOME=%~dp0.." if not "%M2_HOME%"=="" goto valMHome echo. echo Error: M2_HOME not found in your environment. >&2 echo Please set the M2_HOME variable in your environment to match the >&2 echo location of the Maven installation. >&2 echo. goto error :valMHome :stripMHome if not "_%M2_HOME:~-1%"=="_\" goto checkMCmd set "M2_HOME=%M2_HOME:~0,-1%" goto stripMHome :checkMCmd @rem if exist "%M2_HOME%\bin\mvn.cmd" goto init echo. echo Error: M2_HOME is set to an invalid directory. >&2 echo M2_HOME = "%M2_HOME%" >&2 echo Please set the M2_HOME variable in your environment to match the >&2 echo location of the Maven installation >&2 echo. goto error @REM ==== END VALIDATION ==== :init set MAVEN_CMD_LINE_ARGS=%* @REM Find the project base dir, ie the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" @rem for %%i in ("%M2_HOME%"\boot\plexus-classworlds-*) do set CLASSWORLDS_JAR="%%i" set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.home=%M2_HOME%" "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE%
      
      







また、Windowsでの全体的なアセンブリはLinuxでのアセンブリよりもはるかに遅いことがわかりました。 なぜこれがまだ明確でないのか。



UPD2



別の微妙な瞬間が浮上した。 SonarQubeのビルドはSmart Builderと競合します。 なぜなら --builderスマートオプションはデフォルトで有効になっています。SonarQubeでのアセンブリでは、実行するだけでは不十分です。



 mvn sonar:sonar
      
      





また、標準のビルド戦略に切り替える必要があります。



 mvn --builder multithreaded sonar:sonar
      
      





または



 mvn --builder singlethreaded sonar:sonar
      
      





状況に応じて。



All Articles