Что делать, если сборка (build) с Maven проходит слишком медленно? Ведь когда сборка длится слишком долго, любой, даже самый терпеливый разработчик, может заскучать и отвлечься.
Для быстрого поиска в Google или для закладок, сразу предлагаю итоговое решение:
mvn package -am -o -Dmaven.test.skip -T 1C
— для сборки проекта без тестов.
Эта история началась с того, что я выкачал довольно большой проект на Java из нашего корпоративного репозитория. Думаю, что многие, как и я, сразу собрали бы его командой:
mvn clean package
, ну а многие командой:
mvn clean install
Мой проект собрался за 25 минут, и каждый раз собирался так же долго, как и в первый раз.
package вместо install
Согласно жизненному циклу сборки проекта после фазы package, на которой мы получаем полноценный jar-файл, есть фаза verify, а затем install. Часто необходимо получить именно jar-файл, который нет необходимости помещать в локальный репозиторий, поэтому в данной ситуации я остановился на package и сэкономил немного времени.
Параллельная сборка
При обычном запуске Maven не использует все возможности современных процессоров по распараллеливанию вычислений на разных ядрах, собирая модули один за другим. К счастью, можно воспользоваться параметром -T, указав Maven, что необходимо построить граф зависимостей и собирать модули параллельно.
Параметром можно воспользоваться разными способами:
mvn -T 4 package
или
mvn -T 1C package
В первом случае вы указываете число потоков для использования Maven, а во втором — что необходимо использовать один поток на каждое ядро CPU.
Второй способ задания параметра, привязанный к количеству ядер, я и собираюсь использовать в своей итоговой команде.
Поэкспериментировав с количеством потоков на ядро, попробовав следующие варианты:
mvn -T 1C package
mvn -T 1.5C package
mvn -T 2C package
— я не получил какого-то заметного прироста в случае с более чем 1 потоком на ядро на собираемом проекте на процессоре Core i7.
Замечу ещё, что параллельная сборка — это экспериментальная функция Maven 3. При сборке могут возникнуть проблемы при использовании плагинов, не являющихся @threadSafe. В частности, это плагины:
- Surefire с параметром forkMode=never, surefire [2.6,) предупреждает (assert) об этом.
- maven-modello-plugin, исправлено с версии 1.4,
- Все клиенты maven-archiver (EAR, EJB, JAR, WAR etc), исправлено в последних версиях
и библиотеки:
- plexus-utils 2.0.5
- maven-archiver 2.4.1
- plexus-archiver 1.0
- plexus-io 1.0
Инкрементальная сборка
Как мы обсуждали выше, проект чаще всего собирается командой:
mvn clean package
Команда clean используется для очистки проекта, но так ли это нужно каждый раз при сборке? Нет, обычно мы хотим инкрементального обновления проекта, и Maven на это способен при помощи команды:
mvn package -am
Maven собирает модуль и обновляет те модули, от которых зависит данный модуль, в случае если в них что-то поменялось.
Эта опция в разы ускоряет сборку проектов из многих модулей, в которых, как правило, вносятся точечные изменения в 1-2 модуля.
Оффлайновая сборка
Часто бывает так, что артефакты на внешних репозиториях обновляются не так уж и часто, особенно, когда это артефакты определённой версии, или эти артефакты должны поддерживаться вашими усилиями (- Добро пожаловать в новый проект!). Довольно логично в такой ситуации сообщить Maven, что не нужно каждый раз заново скачивать их из репозиториев:
mvn package -o
или
mvn package --offline
Мы для лаконичности остановимся на первом варианте.
Пропуск тестов
Пожалуй, самое спорное предложение по оптимизации скорости сборки я оставил напоследок. Тем, кто полностью разделяет ценности TDD, я просто предлагаю пролистнуть этот абзац и убрать из нашей итоговой команды параметр -Dmaven.test.skip.
Не буду отрицать, тесты, безусловно, нужны, и программист должен понимать ту часть ответственности, которую он берёт на себя, отключая их. Но если вы вдруг столкнулись с новым гигантским проектом, в котором кто-то когда-то написал тесты, и они не работают, то вам и так придётся их для начала отключить.
Что касается опции запуска Maven, я хочу отметить только то, что, как правило, для того, чтобы пропустить тесты пользуются командой:
mvn package -DskipTests
Но при этом можно также пропустить компиляцию тестов, если выполнить команду в следующем виде:
mvn package -Dmaven.test.skip
Подведём итоги
Итак, ещё раз команда, которая получилась у нас для ускорения сборки с Maven:
mvn package -am -o -Dmaven.test.skip -T 1C
Результат, показанный на собираемом мной проекте, радует: сборка вместо 25 минут стала проходить за 30 секунд.
Авторы некоторых других статей по оптимизации скорости сборки с Maven также рекомендуют использовать параметры оптимизации запуска компилятора:
MAVEN_OPTS= -XX:+TieredCompilation -XX:TieredStopAtLevel=1
mvn package
— но, во-первых, я не почувствовал какого-то реального ускорения сборки, по всей видимости, из-за того, что я и так пользовался инкрементальной сборкой и процесс компиляции отнимал в этом случае не так много времени, а во-вторых, встречается достаточное количество упоминаний того, что эти параметры могут вызвать ошибки OutOfMemory и прочие проблемы.
Надеюсь, что в комментариях вы также поделитесь статистикой по ускорению сборки ваших проектов!
Комментарии (40)
doom369
15.07.2016 09:03Ну… Тюнить мавен это конечно хорошо, но как на счет понять причины медленной сборки? Помню у нас билд шел 10 мин, при этом поднятие спринговых контекстов в тестах занимало 6 мин.
+ Отрефакторить код и избавиться от лишнего мусора, которого, я уверен, в проекте хватает.
+ Сборку можно вынести на отдельную мощную машину и собирать там.RedMadCat
15.07.2016 09:18Как я уже писал, изначально это огромный opensource проект, в котором основная причина медленной сборки — его размеры.
Рефакторинг кода — это, конечно, хорошая тема для этого проекта, но такая статья будет совершенно не интересна для аудитории Хабра.
При этом описанная ситуация не является уникальной, поэтому мой опыт может пригодиться другим.loltrol
16.07.2016 10:07-2Совсем недавно(месяц назад где то) применил абсолютно все ваши наработки на своем «огромном opensource проекте», а то такая жесть получалась. Кстати, где то писали про локальную репку для mvn. Что то мне локальная репа не помогала, mvn не хотел тянуть быстрее чем в 100 кб\с и такое ощущение что записывал файлы и делал свои махинации во мноооооооого раз дольше нежели закачивал.
tmn4jq
15.07.2016 11:13Говорят, мавен тяжело справляется с реально большими проектами.
Я могу ошибаться, но насколько я понимал год назад (это то время, в течение которого я не пишу на Java), то одна и та же dependency может тянуться множество раз разными библиотеками. Например, какой-нибудь log4j может много раз тянуться разными либами. Мы это проследили, используя dependency:tree и что-то вроде этого. Затем мы довольно кропотливо и муторно добавляли секции exclude для многих зависимостей. В итоге сборка стала значительно легче, но pom.xml стал значительно запутаннее. Я не считаю это хорошим решением, так что в будущем, если бы мне вдруг еще раз пришлось поработать на большом проекте, я бы посмотрел в сторону gradle. Скажу сразу, я последним не пользовался и не хочу сказать, будто он лучше в этом аспекте.TachikomaGT
15.07.2016 11:32Как-то очень странно. В том что разные библиотеки используют одну зависимость проблемы нет совсем.
Если используемые библиотеки — release, то выкачал maven все либы один раз в .m2 (при первом билде проекта) и всё, дальше локально подтягивает. А если хочется ускорить shapshot-ы, то можно updatePolicy в daily выставить.
Если же проблема в jar hell, когда в зависимостях куча разных версий одной библиотеки, а самостоятельно подбирать неконфликтные версии библиотек не хочется, то можно поглядеть в сторону Spring Boot.tmn4jq
15.07.2016 12:23Обосновать бы еще заказчику, почему он должен заплатить за то, чтобы рабочий и везде используемый модуль переписали на Spring Boot. Особенно, когда на стороне заказчика есть свои разработчики, которые изначально этот модуль писали и тоже не видят смысла в том, чтобы вносить в него кардинальные изменения.
RedMadCat
15.07.2016 12:29Артём, добавлю свои 5c. Такие задачи, как использование Spring Boot, обосновывать заказчику нет смысла и необходимости, а при этом при грамотном ведении проекта остаются области, которые можно заполнять такими задачами. Старайтесь логически вырулить в эту сторону в будущем.
tmn4jq
15.07.2016 12:34Я согласен с этим, просто мне тяжело в двух словах описать, почему наш проект был настолько неповоротливым. Но в этом есть смысл, да.
sshikov
15.07.2016 11:36А с чего вы взяли, что gradle вдруг будет собирать быстрее?
tmn4jq
15.07.2016 12:24А вы с чего взяли, будто я так считаю? =) Я же сказал, что не хочу ничего сказать по этому поводу. Это скорее было отступление, что лично мне хотелось бы посмотреть, как gradle поведет себя в схожей ситуации.
sshikov
15.07.2016 12:39Ну вы как-то неопределенно сформулировали, прямо скажем.
Говорят, мавен тяжело справляется с реально большими проектами.
Это, скажем прямо, не доказано. Я знаю как достаточно большие проекты, собирающиеся мавеном, так и достаточно большие, собирающиеся gradle. И я бы сказал, что скорость сборки в основном определяется правильной архитектурой разбиения на модули. Чтобы тривиально не собирать то, что не требуется.
В качестве примера — я видел десятки проектов, где используется wsdl2java (Axis, или CXF). И к сожалению во многих из них plugin генерации java классов по wsdl вызывается при каждой сборке. И является частью проекта, который содержит как генерируемый код, так и самописный. При том, что wsdl является внешним интерфейсом, и меняется скажем раз в год. Это лечится только применением головы и разбиением на более мелкие части. И кстати, практику один pom-> один артефакт придумали совсем не дураки.
axmetishe
15.07.2016 14:56здесь достаточно большой проект, если отключить сборку документации, то занимает 3 минуты, притом на достаточно слабой виртуалке, все дело лишь в правильном приложении мавена
github.com/vporoxnenko/mbsa
forketyfork
15.07.2016 12:44+1Если разные версии одной библиотеки тянутся различными транзитивными зависимостями, то часто удобнее бывает один раз прописать версию этой библиотеки в секции dependencyManagement в корневом pom, чем расставлять везде exclude.
tmn4jq
15.07.2016 12:52А что делать с ситуацией, когда сторонняя библиотека тянет другую версию? Вопрос без подвоха, просто интересуюсь, прокатит ли это в такой ситуации
forketyfork
15.07.2016 12:54Да, я именно про эту ситуацию и говорю. Когда вы прописываете версию библиотеки в dependencyManagement, она перекрывает все остальные определения этой библиотеки из всех зависимостей в дереве, включая сторонние.
TachikomaGT
15.07.2016 12:06+1На мой взгляд, в пропуске тестов вот совсем ничего спорного нет. Ведём TDD-разработку – компилируем и запускаем конкретный тест. Запушили ветку – CI-сервер принудительно прогнал новый тест и все старые. А то каждый раз при сборке интеграционные тесты гонять терпения не напасёшься.
foal
15.07.2016 14:16+2Тут уже упомянули takari. Для maven 3.3.9 можно в корне проекта разместить каталог .mvn со следующими файлами
extensions.xml
maven.config
timing.properties
jvm.config
extensions.xml — служит для подключения различных расширений, например takari. В моём случае это:
io.takari.maven
takari-smart-builder
0.4.1
io.takari.aether
takari-local-repository
0.11.2
maven.config — параметры мавена для данного проекта:
-T8
--builder
smart
timing.properties — пустой файл, takari использует его для построения оптимального прожода по модулям проекта.
jvm.config — параметры jvm специфичные для данного проекта:
-Xmx4g
-Djava.awt.headless=truefoal
15.07.2016 21:28Да, теги из комментария порезались — был XML, стал набор строк. Но минимально мавен координаты расширений остались, а пример extensions.xml каждый может найти сам.
Borz
15.07.2016 16:37+1насколько я понял из текста, у вас не сборка медленно выполнялась, а множество тестов. Пробовали «без оптимизаций» просто без тестов выполнить «clean package» — сколько времени на это уходит?
RedMadCat
15.07.2016 18:10Спасибо за комментарий, это близко к тому, что было на практике. Только я решил добавить ещё «оптимизаций» после того, как в целом удовлетворительный результат уже был достигнут. Порядка двух минут на билд на неплохом железе меня раздражало.
С оптимизациями результат лучше в 5 раз, чем без них, без — порядка двух минут.
Siper
15.07.2016 18:01На моей практике обычно даже не приходится делать полную сборку на девелоперской машине, т.е. это редкая операция скоростью которой можно пренебречь в пользу полной уверенности что билд проходит от и до.
Полный же билд идет на CI серверах и задействовать инкрементальную сборку там сложно, поскольку разработка ведется в нескольких бранчах одновременно, и билд-серверам приходится переключаться между ними по мере коммитов.
Вот что давало кратный прирост на интеграционных тестах — так это параллельный их запуск (maven-failsafe-plugin), но пришлось повозиться — настроить конфигурации таким обрзом чтобы не было пересечений ресурсов (разные БД, разные exchnage в RabbitMQ, разные listen ports у компонентов, и т.д.). По идее уже должны существовать альернативные пути, например основанные на контейнеризации (думаю под Gradle точно есть), будет здорово если кто-нибудь просвятит.
Trans00
15.07.2016 20:18Profiler используете?
И еще awaitility очень помогает избавляться от разного рода sleep'ов в тестах.
grossws
15.07.2016 23:20ЭххЪ, а мне на одном из проектов приходится отключать инкрементальную компиляцию (
configuration/useIncrementalCompilation
в настройкахmaven-compiler-plugin
) иначеjavac
падает также как в JDK-8037484.Beholder
19.07.2016 12:38Также сталкивался. Можно создать для этого профиль в
~/.m2/settings.xml
<profiles> ... <profile> <!-- http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8067747 --> <id>NoIncrementalCompilation</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <maven.compiler.useIncrementalCompilation>false</maven.compiler.useIncrementalCompilation> </properties> </profile> </profiles>
grossws
19.07.2016 13:24Мне было проще в
build/pluginManagement
добавить настройки, проблема только в рамках тех проектов, которые используют кодогенерацию (например, lombok, immutables).
Кроме того, подход с maven settings, что они автоматически не окажутся на машинах других разработчиков и ci/cd сервере.
Borz
19.07.2016 15:23+1в Maven c версии 3.3.1 можно настройки и в проект класть (например в
${maven.projectBasedir}/.mvn/settings.xml
) — просто потом в${maven.projectBasedir}/.mvn/maven.config
прописываете путь до вашего settings.xml как--global-settings .mvn/settings.xml
grossws
19.07.2016 21:33Оно при этом будет мержить их с
~/.m2/settings.xml
? Если нет, то в моём случае это бесполезно, т. к. коммитить в репозиторий те же пароли от nexus'а нет никакого желания.
bobzer
19.07.2016 12:25+1За других говорить не буду, но моё личное решение касательно сборки с Maven — не использовать для сборки Maven, по крайней мере в dev-окружении. Вместо этого предпочитаю собирать средой разработки Idea. Причина, если кратко — у среды разработки есть контроль версий файлов, который она и использует для инкрементальной сборки. Результат — сборка длится считанные секунды независимо от размера проекта, длительность зависит только от количества изменений, внесённых после предыдущей сборки. Да, Maven отлично справляется с конфигурацией проекта, и именно его файлы конфигурации я использую для генерации артефактов в среде разработки. Но как только все нужные зависимости подключены, я про Maven забываю…
Trans00
23.07.2016 17:38+1У идеи много своих проблем, на которые сильно влияют размеры проекта, но даже когда все работает именно так, как должно, есть проекты, которые используют кодогенерацию и/или другого рода костыли, на которые нет поддержки в IDE. И чем больше проект, тем больше костылей в процессе сборки=)
sshikov
Дайте попробую угадать — у вас нет своего репозитория в вашей сети?
RedMadCat
Почти. У нас удалённый офис, репозиторий есть в основном офисе. Но вы утрируете, т.к. оффлайновая сборка решила мою проблему лишь отчасти.
sshikov
Ну может и утрирую, но я такого никогда не наблюдал, имея всегда nexus рядом.
Могу в принципе себе представить проект, собирающийся 25 минут — например весь karaf или camel, может быть. Только смысла собирать его весь не вижу никакого. И делать проект такого размера, не разбитый на модули — тоже.
takari видели? Пробовали?
RedMadCat
Проект, который я собирал, по размерам должен быть сопоставим с karaf или camel, и в нём как раз много модулей. Он отчасти есть в opensource, но по NDA я не думаю, что могу публично назвать его.
Насчёт смысла собирать и пересобирать такой большой проект — если вы ещё раз пробежите текст статьи, там как раз и упоминается возможность не собирать постоянно все модули. Надеюсь, кому-то эта возможность пригодится помимо меня, поэтому я и написал об этом.
Про takari — спасибо за наводку!
sshikov
Ну если он попоставимого с karaf размера — тогда да, понять 25 минут можно. Тогда вам скорее всего еще поможет SSD, а вовсе не i7.
Возможность же не собирать каждый раз все — ну это самый очевидный вариант, на самом деле. Иначе зачем вообще модули?
RedMadCat
Да, это с SSD-шкой, кстати.