[Из песочницы] Takari: Maven на стероидах

Предыстория


Уже довольно давно я работаю в проекте, в котором используется система сборки Maven. По началу, когда проект был ещё не таких размеров как сейчас, время его полной компиляции было относительно разумным и не вызывало нареканий. Но со временем код разросся, количество подпроектов резко увеличилось и среднее время полной компиляции выросло до 6 — 10 минут. Что служило постоянным источником упрёков со стороны разработчиков.

Следует так же отметить. что мы не использовали параллельную сборку, т.к. это регулярно вызывало различные проблемы. То артефакты в локальном хранилище побьются, то просто соберёт не в том порядке и в финальный WAR артефакт попадёт старый, неперекомпилированный код. Конечно некоторые разработчики использовали параллельную сборку на свой страх и риск. Но рано или поздно попадали в ситуацию, когда не могли разобраться что происходит. И простая перекомпиляция в один поток сразу помогала.

Так продолжалось довольно долго, пока я не наткнулся на довольно любопытный сайт компании Takari, на котором предлагаются способы по усовершенствованию методов работы с Maven.

Самыми интересными там являются три вещи:


Так же на 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


Всё. Больше никаких дополнительный действий не требуется. Теперь все операции с локальным репозитарием будут безопасными. Само по себе это расширение может использоваться разве что на серверах 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 иллюстрирует диаграмма ниже:

463426a36c9a45be8c6f868eef667a7c.png

Стандартная стратегия распараллеливания Maven проста и наивна. Она базируется на вычислении глубины зависимостей. Maven запускает параллельную сборку всех проектов одного уровня, пока они не закончатся и только потом переходит на следующий уровень.

Takari Smart Builder, в свою очередь, использует более продвинутую стратегию. Он вычисляет цепочки зависимостей, производит топологическую сортировку и только после этого принимает решение о том, в какой последовательности необходимо производить сборку проектов.

Более того. В процессе компиляции, он запоминает время компиляции каждого проекта в файл .mvn/timing.properties и использует его как дополнительную информацию для того, чтобы в следующий раз завершить компиляцию как можно быстрее.

Для того, чтобы воспользоваться этим функционалом, необходимо при запуске Maven указать дополнительный ключ. Например:

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


В версии Maven 3.3.1 было реализовано несколько нововведений. Первое и самое главное — возможность объявлять расширения ядра Maven прямо в проекте. Для этого нужно добавить файл .mvn/extensions.xml. В приложении к описанному раньше, этот файл может иметь следующий вид:



    
        io.takari.maven
        takari-smart-builder
        0.4.1
    
    
        io.takari.aether
        takari-concurrent-localrepo
        0.0.7
    



Теперь нам не нужно докладывать библиотеки непосредственно в дистрибутив Maven. При этом мы получаем тот же результат.

Файл extensions.xml — не единственный возможный в каталоге .mvn. Тут могут располагаться ещё два файла: jvm.config и maven.config.

jvm.config содержит параметры JVM для запуска компиляции текущего проекта. Например этот файл может выглядеть следующим образом:

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


Первая опция задаёт размер heap равный 2 Гб, а следующие две — оптимизируют работу JVM под нужды Maven (подсмотрено тут).

maven.config — ещё один файл с параметрами, но на этот раз для самого Maven. Например:

--builder smart
-T1.0C
-e


Таким образом мы можем задать, что по умолчанию используется smart builder с количеством потоков равному количеству логических ядер. Т.е., если мы просто выполним

mvn clean install


то сборка будет выполняться в несколько потоков и с использование всех расширений и оптимизаций. Причём, если даже мы будем выполнять сборку вложенного модуля, то эти настройки всё-равно будут применены, т.к. Maven производит поиск каталога .mvn не только в текущем каталоге, но и в родительских.

Тут, правда есть один нюанс. Т.к. сборка идёт в несколько потоков, то и лог сборки выводится этими потоками конкурентно. В результате, при возникновении проблем, не всегда бывает понятно что происходит, в следствии того, что строчки оказываются перемешанными. В этом случае, если хочется запустить сборку в один поток и разобраться в причинах неприятностей, то приходится уже вручную переключать сборку в однопоточный режим:

mvn -T1 clean install


The Takari Lifecycle — альтернатива жизненному циклу Maven по умолчанию (сборка JAR файлов). Его отличительная особенность — вместо пяти отдельных плагинов для одного стандартного жизненного цикла используется один универсальный с тем же функционалом, но с гораздо меньшим количеством зависимостей. Как следствие — гораздо более быстрый старт, более оптимальная работа и меньшее потребление ресурсов. Что даёт существенный прирост производительности при компиляции сложных проектов с большим количеством модулей.

Для активации модернизированного жизненного цикла необходимо добавить takari-lifecycle-plugin в качестве расширения сборки:

    
      
        
          io.takari.maven.plugins
          takari-lifecycle-plugin
          true
        
      
    


А так же переопределить сборку JAR модулей как takari-jar:

    
      4.0.0
      io.takari.lifecycle.its.basic
      basic
      1.0
      takari-jar


После этого все проекты типа POM, а так же takari-jar проекты будут собираться с использование нового жизненного цикла.

Так же можно включить данный жизненный цикл для всех JAR модулей (см. документацию), в нашем случае это начало приводить к конфликтам с различными плагинами Maven. В результате было принято решение просто переопределить packaging модулей, где это можно сделать без ущерба для сборки. Как показала практика этого оказалось более чем достаточно.

Следует так же отметить, что при использование расширения takari-lifecycle-plugin, изменяется расположение различных настроек сборки. Они перемещаются в секцию configuration этого плагина. Например:

    
      
        io.takari.maven.plugins
        takari-lifecycle-plugin
        
          1.8
          1.8
        
      
    


Более подробную информацию можно посмотреть в документации…
У Takari есть ещё одна приятная вещь — Maven Wrapper. По аналогии с Gradle Wrapper, он позволяет запустить сборку проекта сразу после клонирования. Без необходимости установки и настройки Maven у себя на компьютере. К тому же, это позволяет закрепить необходимую версию Maven за проектом.

Самый простой способ добавить в свой проект враппер — воспользоваться архитипом. Выполним в корне проекта:

mvn -N io.takari:maven:wrapper


После этого в текущем каталоге у нас появятся два скрипта:

  • mvnw.bat — для Windows
  • mvnw — для *nix систем


А так же в каталоге .mvn/wrapper появится сам враппер и файл его конфигурации.

Всё. После этого можно вызывать:

./mvnw clean install


А если требуется другая версия Maven, то можно установить необходимый URL в конфигурации .mvn/wrapper/maven-wrapper.properties.

И опять-таки тут не обходится без нюансов. Так в организациях с закрытой сетью часто используют проксирующие репозитарии Maven такие как Nexus или Artifactory. В таком случае каждый разработчик вынужден отдельно настраивать у себя зеркало (mirror) Maven на этот репозитарий. Что немного противоречит идеологии враппера — отсутствие необходимости каких либо настроек.

Выйти из положения можно следующим образом: создадим в нашем проекте файл .mvn/settings.xml вида



    
        
            nexus-m2
            *
            http://repo.org.ru/nexus/content/groups/repo-all-m2
            Nexus M2
        
    


и добавим в файл .mvn/maven.config строчку

--global-settings .mvn/settings.xml


В результате чего зеркало начнёт подхватываться автоматически.

Тестирование и результаты


Всё вышеописанное не имело бы смысла, если бы не давало впечатляющих результатов по ускорению сборки проекта. И чтобы не быть голословным — приведу результаты, которые были получены на одном из наших рабочих проектов.

Итак имеем:

  • Рабочий компьютер: Intel® Core i5–3470 CPU @ 3.20GHz
  • Java 8b60
  • Maven 3.3.3
  • Мультипроект в котором насчитывается 234 pom.xml файлов. Большинство из них собирают различные jar артефакты, но есть и некоторое количество ejb, war, ear и т.п.


Т.к. в «ванильном» Maven с многопоточной сборкой наблюдались проблемы, то (почти)всегда использовался только один поток, что в итоге приводило к времени сборки 5:32 (5 минут 32 секунды) и выше. После всех оптимизаций (параллельная сборка + takari lifecycle) время сборки оказалось равным 1:33. Практически в 4 раза!

Все промежуточные результаты сведены в таблицу ниже:

Как собиралось Количество потоков Затраченное время
default 1 5:32
default 4 3:25
smart build 4 3:18
smart build + takari-jar 1 3:23
smart build + takari-jar 4 1:33


Smart Build запускался по два раза, и фиксировался второй результат, т.к. после первого запуска может происходить оптимизация порядка выполнения сборки (см. документацию).

Что любопытно, добавление Takari Lifecycle в однопоточном режиме даёт такой же прирост производительности, как и выполнение сборки в 4-е потока, но на «ванильном» Maven.

В качестве заключения

Я только недавно обнаружил инструменты описанные в данной статье. Так что практика их использования пока ещё очень скромная. Возможно со временем ещё вылезут какие-либо подводные камни. Но в любом случае такого радикального ускорения сборки оказалось достаточным, чтобы рискнуть использовать эти возможности в нашем тех процессе. Время покажет что из этого получится.

Так же хочу обратить внимание, что в github репозитарии компании Takari есть ещё некоторое количество любопытных проектов. Их описание выходит за рамки данной статьи, но, возможно, кого-то что-то ещё и заинтересует.

© Habrahabr.ru