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 и прочие проблемы.

Надеюсь, что в комментариях вы также поделитесь статистикой по ускорению сборки ваших проектов!
Поделиться с друзьями
-->

Комментарии (40)


  1. sshikov
    15.07.2016 08:35

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


    1. RedMadCat
      15.07.2016 08:38

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


      1. sshikov
        15.07.2016 08:45

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


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


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


        1. RedMadCat
          15.07.2016 09:00

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


          1. sshikov
            15.07.2016 09:19

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


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


            1. RedMadCat
              15.07.2016 09:23

              Да, это с SSD-шкой, кстати.


  1. doom369
    15.07.2016 09:03

    Ну… Тюнить мавен это конечно хорошо, но как на счет понять причины медленной сборки? Помню у нас билд шел 10 мин, при этом поднятие спринговых контекстов в тестах занимало 6 мин.
    + Отрефакторить код и избавиться от лишнего мусора, которого, я уверен, в проекте хватает.
    + Сборку можно вынести на отдельную мощную машину и собирать там.


    1. RedMadCat
      15.07.2016 09:18

      Как я уже писал, изначально это огромный opensource проект, в котором основная причина медленной сборки — его размеры.
      Рефакторинг кода — это, конечно, хорошая тема для этого проекта, но такая статья будет совершенно не интересна для аудитории Хабра.
      При этом описанная ситуация не является уникальной, поэтому мой опыт может пригодиться другим.


      1. loltrol
        16.07.2016 10:07
        -2

        Совсем недавно(месяц назад где то) применил абсолютно все ваши наработки на своем «огромном opensource проекте», а то такая жесть получалась. Кстати, где то писали про локальную репку для mvn. Что то мне локальная репа не помогала, mvn не хотел тянуть быстрее чем в 100 кб\с и такое ощущение что записывал файлы и делал свои махинации во мноооооооого раз дольше нежели закачивал.


  1. tmn4jq
    15.07.2016 11:13

    Говорят, мавен тяжело справляется с реально большими проектами.
    Я могу ошибаться, но насколько я понимал год назад (это то время, в течение которого я не пишу на Java), то одна и та же dependency может тянуться множество раз разными библиотеками. Например, какой-нибудь log4j может много раз тянуться разными либами. Мы это проследили, используя dependency:tree и что-то вроде этого. Затем мы довольно кропотливо и муторно добавляли секции exclude для многих зависимостей. В итоге сборка стала значительно легче, но pom.xml стал значительно запутаннее. Я не считаю это хорошим решением, так что в будущем, если бы мне вдруг еще раз пришлось поработать на большом проекте, я бы посмотрел в сторону gradle. Скажу сразу, я последним не пользовался и не хочу сказать, будто он лучше в этом аспекте.


    1. TachikomaGT
      15.07.2016 11:32

      Как-то очень странно. В том что разные библиотеки используют одну зависимость проблемы нет совсем.
      Если используемые библиотеки — release, то выкачал maven все либы один раз в .m2 (при первом билде проекта) и всё, дальше локально подтягивает. А если хочется ускорить shapshot-ы, то можно updatePolicy в daily выставить.
      Если же проблема в jar hell, когда в зависимостях куча разных версий одной библиотеки, а самостоятельно подбирать неконфликтные версии библиотек не хочется, то можно поглядеть в сторону Spring Boot.


      1. tmn4jq
        15.07.2016 12:23

        Обосновать бы еще заказчику, почему он должен заплатить за то, чтобы рабочий и везде используемый модуль переписали на Spring Boot. Особенно, когда на стороне заказчика есть свои разработчики, которые изначально этот модуль писали и тоже не видят смысла в том, чтобы вносить в него кардинальные изменения.


        1. RedMadCat
          15.07.2016 12:29

          Артём, добавлю свои 5c. Такие задачи, как использование Spring Boot, обосновывать заказчику нет смысла и необходимости, а при этом при грамотном ведении проекта остаются области, которые можно заполнять такими задачами. Старайтесь логически вырулить в эту сторону в будущем.


          1. tmn4jq
            15.07.2016 12:34

            Я согласен с этим, просто мне тяжело в двух словах описать, почему наш проект был настолько неповоротливым. Но в этом есть смысл, да.


    1. sshikov
      15.07.2016 11:36

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


      1. tmn4jq
        15.07.2016 12:24

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


        1. sshikov
          15.07.2016 12:39

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


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

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


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


        1. axmetishe
          15.07.2016 14:56

          здесь достаточно большой проект, если отключить сборку документации, то занимает 3 минуты, притом на достаточно слабой виртуалке, все дело лишь в правильном приложении мавена
          github.com/vporoxnenko/mbsa


    1. forketyfork
      15.07.2016 12:44
      +1

      Если разные версии одной библиотеки тянутся различными транзитивными зависимостями, то часто удобнее бывает один раз прописать версию этой библиотеки в секции dependencyManagement в корневом pom, чем расставлять везде exclude.


      1. tmn4jq
        15.07.2016 12:52

        А что делать с ситуацией, когда сторонняя библиотека тянет другую версию? Вопрос без подвоха, просто интересуюсь, прокатит ли это в такой ситуации


        1. forketyfork
          15.07.2016 12:54

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


  1. igorp1024
    15.07.2016 11:18

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


    1. RedMadCat
      15.07.2016 11:20
      +1

      default command


  1. spamas
    15.07.2016 12:03

    Спасибо за команду!


  1. TachikomaGT
    15.07.2016 12:06
    +1

    На мой взгляд, в пропуске тестов вот совсем ничего спорного нет. Ведём TDD-разработку – компилируем и запускаем конкретный тест. Запушили ветку – CI-сервер принудительно прогнал новый тест и все старые. А то каждый раз при сборке интеграционные тесты гонять терпения не напасёшься.


  1. 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=true


    1. foal
      15.07.2016 21:28

      Да, теги из комментария порезались — был XML, стал набор строк. Но минимально мавен координаты расширений остались, а пример extensions.xml каждый может найти сам.


  1. Borz
    15.07.2016 16:37
    +1

    насколько я понял из текста, у вас не сборка медленно выполнялась, а множество тестов. Пробовали «без оптимизаций» просто без тестов выполнить «clean package» — сколько времени на это уходит?


    1. RedMadCat
      15.07.2016 18:10

      Спасибо за комментарий, это близко к тому, что было на практике. Только я решил добавить ещё «оптимизаций» после того, как в целом удовлетворительный результат уже был достигнут. Порядка двух минут на билд на неплохом железе меня раздражало.
      С оптимизациями результат лучше в 5 раз, чем без них, без — порядка двух минут.


  1. Siper
    15.07.2016 18:01

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

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


  1. Trans00
    15.07.2016 20:18

    Profiler используете?
    И еще awaitility очень помогает избавляться от разного рода sleep'ов в тестах.


  1. grossws
    15.07.2016 23:20

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


    1. 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>
      


      1. grossws
        19.07.2016 13:24

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


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


        1. 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


          1. grossws
            19.07.2016 21:33

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


            1. Borz
              19.07.2016 21:43
              +1

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


              1. grossws
                19.07.2016 21:45

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


  1. bobzer
    19.07.2016 12:25
    +1

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


    1. Trans00
      23.07.2016 17:38
      +1

      У идеи много своих проблем, на которые сильно влияют размеры проекта, но даже когда все работает именно так, как должно, есть проекты, которые используют кодогенерацию и/или другого рода костыли, на которые нет поддержки в IDE. И чем больше проект, тем больше костылей в процессе сборки=)