Webrium
Подписаться

Сборка идёт долго: что ускорять и в каком порядке

Как определить узкие места сборки и сократить время ожидания без лишних затрат

Три показателя сборки: два в норме, один превышает допустимое время ожиданияСборка тормозит

Сколько стоит ожидание сборки

Сборка в CI — это не абстрактный процесс, а конкретное время, которое кто-то тратит впустую. Каждый запуск сборки — это ожидание для разработчика, тестировщика или релиз-инженера. Если сборка занимает больше времени, чем нужно, это не просто техническая проблема, а экономическая: деньги компании уходят на простой сотрудников.

Чтобы понять, во что обходится медленная сборка, нужно ответить на три вопроса:

  1. Сколько человек в день запускают сборку и зависят от её результата?
  2. Сколько времени в среднем они тратят на ожидание?
  3. Какова стоимость часа работы этих сотрудников (включая налоги и накладные расходы)?

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

Где сборка тормозит на самом деле

Сборка редко тормозит равномерно. Обычно есть несколько шагов, которые съедают большую часть времени. Вот самые распространённые из них:

Установка зависимостей

Если каждый запуск сборки начинается с установки зависимостей (например, npm install, pip install, apt-get update), это может занимать значительное время. Особенно если зависимости объёмные или скачиваются из медленных репозиториев.

Отсутствие кеша слоёв

В Docker-сборках каждый слой пересобирается заново, если нет кеша. Это означает, что даже небольшое изменение в одном слое может привести к пересборке всех последующих. Кеширование слоёв (docker build --cache-from) может сократить время сборки в разы.

Повтор одинаковых шагов

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

Тесты, которые можно не запускать

Не все тесты нужно запускать при каждом коммите. Если тесты не зависят от изменений в коде, их можно пропустить или запускать реже. Например, интеграционные тесты можно запускать только перед релизом, а не при каждом коммите.

Медленные тесты

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

Почему важно ускорять именно ожидание людей

Машинное время дешёвое, а человеческое — нет. Если сборка занимает час, но запускается раз в сутки и никого не блокирует, её ускорение не окупится. Но если каждый разработчик ждёт по несколько минут перед каждым коммитом, это уже заметная потеря.

Ускорять нужно не суммарное время сборки, а время ожидания для людей. Например:

  • Если сборка тормозит на установке зависимостей, но это происходит в фоне, пока разработчик пишет код, — это не критично.
  • Если тормозит финальный шаг, который блокирует релиз, — это приоритет.

Оптимизация должна быть целенаправленной: ускорять нужно то, что действительно мешает работе.

Порядок действий: от простого к сложному

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

1. Измерить по шагам

Разбейте сборку на этапы и замерьте время каждого. Часто оказывается, что большая часть времени съедается одним-двумя шагами. Без замеров оптимизация будет наугад.

Как замерять:

  • Используйте встроенные инструменты CI (например, таймеры в GitHub Actions или GitLab CI).
  • Логируйте время выполнения каждого шага.
  • Сравните время разных запусков, чтобы понять, где есть стабильные задержки.

2. Убрать повторную работу

Повторная работа — это самый очевидный источник потерь времени. Вот что можно сделать:

Кешировать зависимости

  • Используйте npm ci вместо npm install для фиксированных версий зависимостей.
  • Кешируйте загруженные пакеты между запусками сборки.

Кешировать слои Docker

  • Используйте docker build --cache-from, чтобы переиспользовать слои из предыдущих сборок.
  • Если сборка запускается в CI, настройте кеширование на уровне платформы (например, через GitHub Actions Cache или GitLab CI Cache).

Выносить повторяющиеся шаги в общий этап

  • Если несколько задач требуют установки одних и тех же инструментов, вынесите этот шаг в начало сборки.
  • Используйте артефакты CI для передачи результатов между этапами.

3. Распараллелить

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

Запускать независимые шаги параллельно

  • Например, тесты разных модулей можно запускать одновременно.
  • Используйте матричные стратегии в CI (например, тестирование на разных версиях языка).

Оптимизировать порядок выполнения

  • Запускайте самые медленные шаги как можно раньше, чтобы они не блокировали остальные.
  • Если шаги зависят друг от друга, распараллеливание не поможет — нужно оптимизировать сами шаги.

4. Оптимизировать тесты

Тесты часто занимают большую часть времени сборки. Вот как их можно ускорить:

Запускать только релевантные тесты

  • Используйте инструменты, которые определяют, какие тесты зависят от изменений (например, jest --findRelatedTests).
  • Запускайте только те тесты, которые покрывают изменённый код.

Выносить медленные тесты в отдельный этап

  • Интеграционные тесты или тесты, зависящие от внешних сервисов, можно запускать реже (например, только перед релизом).
  • Если тесты требуют долгой инициализации, попробуйте оптимизировать этот процесс.

5. Купить более быстрое железо

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

  • Если сборка тормозит из-за сетевых задержек (например, при скачивании зависимостей), более быстрое железо не поможет.
  • Если сборка упирается в CPU или память, можно попробовать более мощные машины или кэширование на уровне CI-платформы.

Тупик: включить параллельность на всё сразу

Самая частая ошибка — пытаться распараллелить всё подряд. Например:

  • Запускать установку зависимостей и тесты одновременно, хотя тесты зависят от зависимостей.
  • Распараллеливать шаги, которые занимают мало времени, но требуют синхронизации.

Параллельность помогает только тогда, когда шаги действительно независимы. Если они зависят друг от друга, параллельный запуск только усложнит сборку и может даже замедлить её.

Как избежать тупика:

  • Анализируйте зависимости между шагами. Если шаг B зависит от результата шага A, их нельзя запускать параллельно.
  • Запускайте параллельно только те шаги, которые занимают значительное время и не зависят друг от друга.

Когда ускорение не окупается

Есть случаи, когда ускорять сборку нет смысла:

  • Сборка запускается редко и никого не блокирует. Например, если сборка происходит раз в сутки и не мешает работе команды, её ускорение не принесёт пользы.
  • Ускорение требует слишком больших затрат. Например, если для оптимизации нужно переписать половину тестов, это может быть неоправданно дорого.
  • Время ожидания уже минимально. Если сборка занимает несколько десятков секунд и никого не держит, её ускорение не окупится.

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

Позиция редакции

Ускорять сборку стоит только тогда, когда это экономит время людей. Если сборка идёт в фоне и никого не держит, её оптимизация — пустая трата ресурсов.

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

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

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Кеш сборки держится до первого изменившегося шага: всё, что ниже, пересобирается зановокеш держитсяпересобирается
Предыстория

Образ, который собирается минуту вместо десяти

Как устроен кеш сборки и почему он сбрасывается чаще, чем кажется — разбор причин и способов ускорить процесс без лишних усилий.

Два стыка: при честном протоколе замера результат стенда совпадает с поведением продакшена, при дефолтном прогоне — расходится с нимчестный протоколдефолтный прогон
Усиление

Как мерить производительность, чтобы себе не соврать

Как избежать ошибок в бенчмарках и получить данные, которые совпадут с реальной нагрузкой — от модели до протокола замеров.

Что проверяет браузерный тест, а что остаётся человекуавтоматомруками
Практика

Браузерные тесты: что автоматизировать, а что оставить руками

Автоматизируйте рутину, а глазам оставьте только то, что не поймает код — и сэкономите полгода на отладке.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть