Настройка монорепозиторного CI/CD пайплайна с affected builds

Когда репозиторий разрастается до десятков модулей, проверка каждой правки занимает часы и тормозит выпуск. Мы настраиваем сборку монорепозитория под ключ: собираем и тестируем только затронутые модули, а не весь код. Время проверки сокращается в разы, и команда перестаёт ждать конвейер.
Часто задаваемые вопросы

Сборка монорепозитория: где теряется время команды

Монорепозиторий начинается как удобное решение: общие библиотеки лежат рядом с сервисами, правила проверки кода едины, сквозная правка уезжает одним набором изменений. Проблема проявляется с ростом — десятки сервисов и пакетов связываются общим графом зависимостей, и изменение в одном модуле формально затрагивает соседей через общие библиотеки, контракты и схемы данных.

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

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

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

Что даёт сборка только затронутых модулей

Сборка затронутых модулей опирается на граф зависимостей репозитория: при каждом изменении система вычисляет, какие проекты затронуты — сам модуль и всё, что зависит от него транзитивно. Инструменты вроде Nx держат такой граф в актуальном состоянии и пересчитывают его по исходникам, а не по ручным спискам. Всё, что осталось за пределами затронутого набора, в проверку не попадает.

Сколько времени экономит проверка по изменениям

Время проверки изменений перестаёт зависеть от размера репозитория и начинает зависеть от размера правки: изменение в листовом модуле тянет 3-5 задач вместо сотен. Кэш результатов сборки усиливает эффект — если входы задачи не менялись (исходники, конфигурация, окружение), результат подставляется из кэша и задача не выполняется вовсе. Ветка с точечной правкой проходит проверку за единицы минут там, где раньше уходили десятки.

Как падает нагрузка на сборочные машины

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

Режимы сборки в монорепозитории: сравнение вариантов

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

Вариант Когда подходит Ограничения
Полная сборка репозитория Небольшой монорепозиторий, редкие коммиты Время растёт линейно с числом модулей
Выборочная сборка модулей Десятки сервисов, частая интеграция Нужен точный граф зависимостей: ошибка в нём ломает релиз
Кэширование артефактов сборки Стабильный стек, редко меняющиеся зависимости Промах кэша возвращает полное время сборки
Распределённая сборка Много независимых модулей, длинные тесты Сложнее отладка, нужны изолированные агенты
Разделение на несколько репозиториев Слабо связанные домены, разные команды Теряется атомарность изменений, контракты версионируются вручную

Общий кэш и граф зависимостей

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

Когда выгоднее разделить репозитории

Разделение оправдано, когда домены почти не делят код: тогда выборочная сборка уступает место независимым конвейерам в отдельных репозиториях. Цена — общие библиотеки приходится версионировать и обновлять вручную, а сквозное изменение проходит через несколько согласований. Пока доля общих правок высока, монорепозиторий с affected-сборкой дешевле в эксплуатации.

Как мы внедряем сборку по затронутым модулям

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

От аудита до выборочной сборки

  1. Аудит конвейера сборки. Снимаем времена этапов, отмечаем, что кэшируется, и фиксируем метрики до изменений — иначе эффект потом не с чем сравнить.
  2. Карта зависимостей модулей. Разбираем связи между пакетами, общие библиотеки и циклические завязки, из-за которых выборочный запуск даёт пропуски.
  3. Настройка выборочной сборки. Привязываем запуск к изменённым путям и правилам: правка в общей библиотеке тянет за собой всех её потребителей, готовые артефакты переиспользуются.
  4. Отладка ложных запусков. Проходим пограничные случаи — изменения конфигурации, схем данных, транзитивных зависимостей, когда собирается лишнее или пропускается нужное.

Пилот и критерии приёмки

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

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

Артефакты и регламенты, которые остаются в репозитории

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

  • Конфигурации сборочных задач — декларативное описание шагов и условий запуска: по какой ветке собираем, какие проверки обязательны, что пропускается для правок в документации.
  • Схема зависимостей — граф пакетов и связей между ними; по нему вычисляется, какие сборки и тесты затронуты изменением, а какие запускать не нужно.
  • Регламент проверок кода — обязательный набор статического анализа и тестов с порогами покрытия до слияния, порядок исключений и то, кто их утверждает.
  • Политики доступа к сборочным агентам — кто вправе запускать сборку, менять правила, читать секреты; где хранятся ключи, как выдаются и как отзываются при уходе человека.
  • Документация по сборке — устройство конвейера, где смотреть журналы и метрики, какие отказы типовые и что делать при падении на каждом этапе.
  • Инструкция для разработчиков — как воспроизвести сборку локально, как добавить пакет и не сломать граф, какие соглашения об именовании веток и коммитов приняты.
  • Регламент дежурства — кто реагирует на упавшую сборку, за какое время, когда конвейер останавливают и как об этом сообщают команде.

Этот набор остаётся у команды после запуска: его не восстанавливают по памяти, а нового инженера вводят в процесс за дни, а не недели.

Кейс: монорепозиторий на десятки модулей и время проверки

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

Замеры снимали дважды и независимо. Сначала по журналам GitLab CI — время каждой стадии на одном и том же наборе изменений до и после перестройки. Затем прогнали те же коммиты на копии репозитория и отдельном исполнителе, чтобы исключить влияние кэша и соседних задач. Оба замера сошлись в пределах погрешности — результат воспроизводимый, а не разовый удачный прогон.

Что именно изменилось в конвейере

  • Собрали граф зависимостей модулей и держим его в репозитории рядом с кодом.
  • Проверка идёт только по затронутым модулям и тем, кто от них зависит; остальное помечается пропуском.
  • Кэш артефактов привязали к содержимому модуля, а не к номеру сборки, — он переиспользуется между ветками.
  • Быстрые проверки отсекают заведомо непроходящие изменения до тяжёлых задач.
  • Параллельность ограничили по числу сборочных машин, чтобы очередь не съедала выигрыш.

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

Что делать, если сборка уже разрослась до десятков модулей

Да, переход на выборочную сборку в монорепозитории делается без остановки разработки — при одном условии: выборочный контур сначала считается параллельно с полным прогоном и не влияет на статус основной ветки. Сравнение двух прогонов на одном и том же изменении показывает, какие модули система считает затронутыми и где граф зависимостей врёт.

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

Шаг Что затрагивает Чем рискуем
Инвентаризация зависимостей граф модулей, общие библиотеки неполный граф: правка библиотеки не тянет потребителей, ветка выглядит зелёной
Наблюдение только отчёты, контур не меняется расхождение в правилах определения затронутых модулей
Перевод части модулей правила по путям, кеш промежуточных артефактов общая библиотека меняется — пересобирать приходится всех потребителей
Заморозка устаревших модулей исключение из обязательных проверок скрытая ссылка живого кода на замороженный
Полный перевод основная ветка, обязательные проверки потеря страховки: нужен быстрый откат к полному прогону

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

Частые технические вопросы перед стартом работ

Перед стартом инженерные команды задают примерно один и тот же набор вопросов: насколько полны проверки при выборочной сборке, что происходит с общими библиотеками и как конвейер живёт после передачи. Отвечаем коротко — по существу.

Как убедиться, что выборочная сборка не пропускает нужные проверки?

Граф зависимостей строится по манифестам пакетов и импортам в коде, а не по структуре каталогов. Изменённые пути сопоставляются с пакетами, и в сборку попадают все, кто зависит от них транзитивно. Поверх этого на защищённых ветках всегда идёт полный прогон — он и служит эталоном, с которым сверяем выборочный результат.

Что будет, если изменилась общая библиотека?

Изменение в общем пакете помечает всех его потребителей, поэтому они пересобираются вместе с ним. Отдельно обрабатываем «глобальные» правки — файлы фиксации версий, конфигурацию сборки и корневые правила: они запускают полный прогон, потому что влияют на весь репозиторий.

А если проверка всё-таки пропустится?

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

Кто поддерживает конвейер после запуска?

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

Обсудим задачу: что уточнить до старта

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

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

  • Устройство репозитория: число пакетов, границы между ними, общие модули и связи между сборками.
  • Схема веток: какие ветки считаются основными, как оформляются и сливаются изменения.
  • Текущее время проверки: сколько идёт полный прогон и сколько — проверка одной правки.
  • Самые долгие и самые хрупкие проверки: что чаще всего падает и почему.
  • Ограничения по доступам: права к репозиторию, сборочным узлам и хранилищу артефактов.
  • Ожидания по срокам внедрения: что критично получить в первую очередь, а что можно отложить.

Опишите задачу в свободной форме — вернёмся с оценкой и планом работ, без общих слов.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1543
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    816
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142