Как ускорить сборку с Maven

    Maven
    Что делать, если сборка (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 и прочие проблемы.

    Надеюсь, что в комментариях вы также поделитесь статистикой по ускорению сборки ваших проектов!
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 40

      0

      Дайте попробую угадать — у вас нет своего репозитория в вашей сети?

        0
        Почти. У нас удалённый офис, репозиторий есть в основном офисе. Но вы утрируете, т.к. оффлайновая сборка решила мою проблему лишь отчасти.
          0

          Ну может и утрирую, но я такого никогда не наблюдал, имея всегда nexus рядом.


          Могу в принципе себе представить проект, собирающийся 25 минут — например весь karaf или camel, может быть. Только смысла собирать его весь не вижу никакого. И делать проект такого размера, не разбитый на модули — тоже.


          takari видели? Пробовали?

            0
            Проект, который я собирал, по размерам должен быть сопоставим с karaf или camel, и в нём как раз много модулей. Он отчасти есть в opensource, но по NDA я не думаю, что могу публично назвать его.
            Насчёт смысла собирать и пересобирать такой большой проект — если вы ещё раз пробежите текст статьи, там как раз и упоминается возможность не собирать постоянно все модули. Надеюсь, кому-то эта возможность пригодится помимо меня, поэтому я и написал об этом.
            Про takari — спасибо за наводку!
              0

              Ну если он попоставимого с karaf размера — тогда да, понять 25 минут можно. Тогда вам скорее всего еще поможет SSD, а вовсе не i7.


              Возможность же не собирать каждый раз все — ну это самый очевидный вариант, на самом деле. Иначе зачем вообще модули?

                0
                Да, это с SSD-шкой, кстати.
        0
        Ну… Тюнить мавен это конечно хорошо, но как на счет понять причины медленной сборки? Помню у нас билд шел 10 мин, при этом поднятие спринговых контекстов в тестах занимало 6 мин.
        + Отрефакторить код и избавиться от лишнего мусора, которого, я уверен, в проекте хватает.
        + Сборку можно вынести на отдельную мощную машину и собирать там.
          0
          Как я уже писал, изначально это огромный opensource проект, в котором основная причина медленной сборки — его размеры.
          Рефакторинг кода — это, конечно, хорошая тема для этого проекта, но такая статья будет совершенно не интересна для аудитории Хабра.
          При этом описанная ситуация не является уникальной, поэтому мой опыт может пригодиться другим.
            –2
            Совсем недавно(месяц назад где то) применил абсолютно все ваши наработки на своем «огромном opensource проекте», а то такая жесть получалась. Кстати, где то писали про локальную репку для mvn. Что то мне локальная репа не помогала, mvn не хотел тянуть быстрее чем в 100 кб\с и такое ощущение что записывал файлы и делал свои махинации во мноооооооого раз дольше нежели закачивал.
          0
          Говорят, мавен тяжело справляется с реально большими проектами.
          Я могу ошибаться, но насколько я понимал год назад (это то время, в течение которого я не пишу на Java), то одна и та же dependency может тянуться множество раз разными библиотеками. Например, какой-нибудь log4j может много раз тянуться разными либами. Мы это проследили, используя dependency:tree и что-то вроде этого. Затем мы довольно кропотливо и муторно добавляли секции exclude для многих зависимостей. В итоге сборка стала значительно легче, но pom.xml стал значительно запутаннее. Я не считаю это хорошим решением, так что в будущем, если бы мне вдруг еще раз пришлось поработать на большом проекте, я бы посмотрел в сторону gradle. Скажу сразу, я последним не пользовался и не хочу сказать, будто он лучше в этом аспекте.
            0
            Как-то очень странно. В том что разные библиотеки используют одну зависимость проблемы нет совсем.
            Если используемые библиотеки — release, то выкачал maven все либы один раз в .m2 (при первом билде проекта) и всё, дальше локально подтягивает. А если хочется ускорить shapshot-ы, то можно updatePolicy в daily выставить.
            Если же проблема в jar hell, когда в зависимостях куча разных версий одной библиотеки, а самостоятельно подбирать неконфликтные версии библиотек не хочется, то можно поглядеть в сторону Spring Boot.
              0
              Обосновать бы еще заказчику, почему он должен заплатить за то, чтобы рабочий и везде используемый модуль переписали на Spring Boot. Особенно, когда на стороне заказчика есть свои разработчики, которые изначально этот модуль писали и тоже не видят смысла в том, чтобы вносить в него кардинальные изменения.
                0
                Артём, добавлю свои 5c. Такие задачи, как использование Spring Boot, обосновывать заказчику нет смысла и необходимости, а при этом при грамотном ведении проекта остаются области, которые можно заполнять такими задачами. Старайтесь логически вырулить в эту сторону в будущем.
                  0
                  Я согласен с этим, просто мне тяжело в двух словах описать, почему наш проект был настолько неповоротливым. Но в этом есть смысл, да.
              0

              А с чего вы взяли, что gradle вдруг будет собирать быстрее?

                0
                А вы с чего взяли, будто я так считаю? =) Я же сказал, что не хочу ничего сказать по этому поводу. Это скорее было отступление, что лично мне хотелось бы посмотреть, как gradle поведет себя в схожей ситуации.
                  0

                  Ну вы как-то неопределенно сформулировали, прямо скажем.


                  Говорят, мавен тяжело справляется с реально большими проектами.

                  Это, скажем прямо, не доказано. Я знаю как достаточно большие проекты, собирающиеся мавеном, так и достаточно большие, собирающиеся gradle. И я бы сказал, что скорость сборки в основном определяется правильной архитектурой разбиения на модули. Чтобы тривиально не собирать то, что не требуется.


                  В качестве примера — я видел десятки проектов, где используется wsdl2java (Axis, или CXF). И к сожалению во многих из них plugin генерации java классов по wsdl вызывается при каждой сборке. И является частью проекта, который содержит как генерируемый код, так и самописный. При том, что wsdl является внешним интерфейсом, и меняется скажем раз в год. Это лечится только применением головы и разбиением на более мелкие части. И кстати, практику один pom-> один артефакт придумали совсем не дураки.

                    0
                    здесь достаточно большой проект, если отключить сборку документации, то занимает 3 минуты, притом на достаточно слабой виртуалке, все дело лишь в правильном приложении мавена
                    github.com/vporoxnenko/mbsa
                  +1
                  Если разные версии одной библиотеки тянутся различными транзитивными зависимостями, то часто удобнее бывает один раз прописать версию этой библиотеки в секции dependencyManagement в корневом pom, чем расставлять везде exclude.
                    0
                    А что делать с ситуацией, когда сторонняя библиотека тянет другую версию? Вопрос без подвоха, просто интересуюсь, прокатит ли это в такой ситуации
                      0
                      Да, я именно про эту ситуацию и говорю. Когда вы прописываете версию библиотеки в dependencyManagement, она перекрывает все остальные определения этой библиотеки из всех зависимостей в дереве, включая сторонние.
                  0

                  Рискуя спровоцировать холивар, всё же спрошу. Как Вы считатете, зачем упоминаемые в статье "многие" могли бы собирать локально проект как mvn clean install? Из Вашего опыта, чем они это мотивируют?

                    +1
                    default command
                    0
                    Спасибо за команду!
                      +1
                      На мой взгляд, в пропуске тестов вот совсем ничего спорного нет. Ведём TDD-разработку – компилируем и запускаем конкретный тест. Запушили ветку – CI-сервер принудительно прогнал новый тест и все старые. А то каждый раз при сборке интеграционные тесты гонять терпения не напасёшься.
                        +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=true
                          0
                          Да, теги из комментария порезались — был XML, стал набор строк. Но минимально мавен координаты расширений остались, а пример extensions.xml каждый может найти сам.
                          +1
                          насколько я понял из текста, у вас не сборка медленно выполнялась, а множество тестов. Пробовали «без оптимизаций» просто без тестов выполнить «clean package» — сколько времени на это уходит?
                            0
                            Спасибо за комментарий, это близко к тому, что было на практике. Только я решил добавить ещё «оптимизаций» после того, как в целом удовлетворительный результат уже был достигнут. Порядка двух минут на билд на неплохом железе меня раздражало.
                            С оптимизациями результат лучше в 5 раз, чем без них, без — порядка двух минут.
                            0
                            На моей практике обычно даже не приходится делать полную сборку на девелоперской машине, т.е. это редкая операция скоростью которой можно пренебречь в пользу полной уверенности что билд проходит от и до.
                            Полный же билд идет на CI серверах и задействовать инкрементальную сборку там сложно, поскольку разработка ведется в нескольких бранчах одновременно, и билд-серверам приходится переключаться между ними по мере коммитов.

                            Вот что давало кратный прирост на интеграционных тестах — так это параллельный их запуск (maven-failsafe-plugin), но пришлось повозиться — настроить конфигурации таким обрзом чтобы не было пересечений ресурсов (разные БД, разные exchnage в RabbitMQ, разные listen ports у компонентов, и т.д.). По идее уже должны существовать альернативные пути, например основанные на контейнеризации (думаю под Gradle точно есть), будет здорово если кто-нибудь просвятит.
                              0
                              Profiler используете?
                              И еще awaitility очень помогает избавляться от разного рода sleep'ов в тестах.
                                0

                                ЭххЪ, а мне на одном из проектов приходится отключать инкрементальную компиляцию (configuration/useIncrementalCompilation в настройках maven-compiler-plugin) иначе javac падает также как в JDK-8037484.

                                  0
                                  Также сталкивался. Можно создать для этого профиль в ~/.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>
                                  
                                    0

                                    Мне было проще в build/pluginManagement добавить настройки, проблема только в рамках тех проектов, которые используют кодогенерацию (например, lombok, immutables).


                                    Кроме того, подход с maven settings, что они автоматически не окажутся на машинах других разработчиков и ci/cd сервере.

                                      +1

                                      в Maven c версии 3.3.1 можно настройки и в проект класть (например в ${maven.projectBasedir}/.mvn/settings.xml) — просто потом в ${maven.projectBasedir}/.mvn/maven.config прописываете путь до вашего settings.xml как --global-settings .mvn/settings.xml

                                        0

                                        Оно при этом будет мержить их с ~/.m2/settings.xml? Если нет, то в моём случае это бесполезно, т. к. коммитить в репозиторий те же пароли от nexus'а нет никакого желания.

                                          +1

                                          будут — ~/.m2/settings.xml это же user-settings.
                                          --global-settings переопределяет ${maven.home}/conf/settings.xml, который редко кто редактирует. При этом даёт пониженный приоритет, т.к. читается она до пользовательского settings.xml.

                                            0

                                            Понятно, спасибо. Буду иметь ввиду этот вариант.

                                  +1
                                  За других говорить не буду, но моё личное решение касательно сборки с Maven — не использовать для сборки Maven, по крайней мере в dev-окружении. Вместо этого предпочитаю собирать средой разработки Idea. Причина, если кратко — у среды разработки есть контроль версий файлов, который она и использует для инкрементальной сборки. Результат — сборка длится считанные секунды независимо от размера проекта, длительность зависит только от количества изменений, внесённых после предыдущей сборки. Да, Maven отлично справляется с конфигурацией проекта, и именно его файлы конфигурации я использую для генерации артефактов в среде разработки. Но как только все нужные зависимости подключены, я про Maven забываю…
                                    +1
                                    У идеи много своих проблем, на которые сильно влияют размеры проекта, но даже когда все работает именно так, как должно, есть проекты, которые используют кодогенерацию и/или другого рода костыли, на которые нет поддержки в IDE. И чем больше проект, тем больше костылей в процессе сборки=)

                                  Only users with full accounts can post comments. Log in, please.