Сборка монорепозитория: где теряется время команды
Монорепозиторий начинается как удобное решение: общие библиотеки лежат рядом с сервисами, правила проверки кода едины, сквозная правка уезжает одним набором изменений. Проблема проявляется с ростом — десятки сервисов и пакетов связываются общим графом зависимостей, и изменение в одном модуле формально затрагивает соседей через общие библиотеки, контракты и схемы данных.
Дальше включается эффект «сборки всего». Система непрерывной интеграции видит новое изменение в репозитории и запускает проверку всех модулей: линтеры, тесты, сборку образов. Прогон занимает десятки минут, а параллельные ветки выстраивают очередь сборочных задач — агенты заняты чужими прогонами, свежий коммит ждёт освобождения ресурсов, хотя его правки касаются одного пакета.
Здесь и проявляется узкое место конвейера сборки. Долгая сборка проекта перестаёт быть неудобством и становится тормозом для всей команды: обратная связь по коду приходит с задержкой в часы, разработчик переключается на другую задачу, конфликты слияния копятся, а сломанное изменение обнаруживается тогда, когда контекст уже потерян. Инкрементальные прогоны и кеш промежуточных результатов сглаживают картину, но не устраняют причину: конвейер не различает затронутые модули и незатронутые, поэтому платит за всё.
Разбираем, как это закрывается: построением графа зависимостей между модулями, отбором только затронутых, кешированием по содержимому файлов и разделением очереди по приоритету веток.
Что даёт сборка только затронутых модулей
Сборка затронутых модулей опирается на граф зависимостей репозитория: при каждом изменении система вычисляет, какие проекты затронуты — сам модуль и всё, что зависит от него транзитивно. Инструменты вроде Nx держат такой граф в актуальном состоянии и пересчитывают его по исходникам, а не по ручным спискам. Всё, что осталось за пределами затронутого набора, в проверку не попадает.
Сколько времени экономит проверка по изменениям
Время проверки изменений перестаёт зависеть от размера репозитория и начинает зависеть от размера правки: изменение в листовом модуле тянет 3-5 задач вместо сотен. Кэш результатов сборки усиливает эффект — если входы задачи не менялись (исходники, конфигурация, окружение), результат подставляется из кэша и задача не выполняется вовсе. Ветка с точечной правкой проходит проверку за единицы минут там, где раньше уходили десятки.
Как падает нагрузка на сборочные машины
Параллельные задачи сборки разбираются по графу: агенты получают независимые ветви и не пересобирают общий префикс зависимостей заново. Отказ от полной пересборки убирает пиковую очередь — вместо сотни задач на каждое слияние приходит 5-10, а освободившиеся слоты уходят на одновременную проверку нескольких веток. Кэш общий для всех агентов: повторная задача забирает готовый артефакт, а не запускает компиляцию заново, поэтому сборочные машины перестают быть узким местом при росте числа команд.
Режимы сборки в монорепозитории: сравнение вариантов
Выбор режима сборки определяет, сколько времени ждёт инженер после отправки изменений и сколько ресурсов съедает сборочный конвейер. Ниже — реальные альтернативы, между которыми выбирают команды, и их ограничения.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Полная сборка репозитория | Небольшой монорепозиторий, редкие коммиты | Время растёт линейно с числом модулей |
| Выборочная сборка модулей | Десятки сервисов, частая интеграция | Нужен точный граф зависимостей: ошибка в нём ломает релиз |
| Кэширование артефактов сборки | Стабильный стек, редко меняющиеся зависимости | Промах кэша возвращает полное время сборки |
| Распределённая сборка | Много независимых модулей, длинные тесты | Сложнее отладка, нужны изолированные агенты |
| Разделение на несколько репозиториев | Слабо связанные домены, разные команды | Теряется атомарность изменений, контракты версионируются вручную |
Общий кэш и граф зависимостей
Граф строится по манифестам модулей, и от его точности зависит, какие задачи вообще попадут в сборку. Кэш артефактов живёт на общем хранилище и переиспользуется между ветками: повторная сборка неизменённого модуля занимает секунды вместо минут. Если граф описывает неявные связи неполно, часть зависимых модулей не пересобирается — и дефект всплывает уже в рабочей среде.
Когда выгоднее разделить репозитории
Разделение оправдано, когда домены почти не делят код: тогда выборочная сборка уступает место независимым конвейерам в отдельных репозиториях. Цена — общие библиотеки приходится версионировать и обновлять вручную, а сквозное изменение проходит через несколько согласований. Пока доля общих правок высока, монорепозиторий с affected-сборкой дешевле в эксплуатации.
Как мы внедряем сборку по затронутым модулям
Конвейер в монорепозитории по умолчанию пересобирает всё. Пока модулей десяток, это терпимо, но на сотне пакетов сборка перестаёт успевать за темпом правок: очередь растёт, а упавший тест в чужом модуле блокирует выпуск. Порядок работ ниже выстроен так, чтобы запускались только затронутые модули и их потребители.
От аудита до выборочной сборки
- Аудит конвейера сборки. Снимаем времена этапов, отмечаем, что кэшируется, и фиксируем метрики до изменений — иначе эффект потом не с чем сравнить.
- Карта зависимостей модулей. Разбираем связи между пакетами, общие библиотеки и циклические завязки, из-за которых выборочный запуск даёт пропуски.
- Настройка выборочной сборки. Привязываем запуск к изменённым путям и правилам: правка в общей библиотеке тянет за собой всех её потребителей, готовые артефакты переиспользуются.
- Отладка ложных запусков. Проходим пограничные случаи — изменения конфигурации, схем данных, транзитивных зависимостей, когда собирается лишнее или пропускается нужное.
Пилот и критерии приёмки
- Пилот на одном направлении. Включаем режим для одной команды и сверяем результат с полной сборкой: пропущенные сборки видно до раскатки на весь репозиторий.
- Передача регламентов команде. Описываем, как добавлять модуль и объявлять зависимости; дальше конвейер сопровождает команда без нашего участия.
Критерий приёмки — ни одного пропущенного модуля, который реально затронут, и единичные лишние запуски. Дальше время ожидания в очереди перестаёт зависеть от размера репозитория.
Артефакты и регламенты, которые остаются в репозитории
Граф зависимостей и правила запуска сборок работают, пока зафиксированы в репозитории, а не в переписке и не в голове одного инженера. Поэтому всё, что описывает сборку, лежит рядом с кодом и проходит тот же регламент проверок: правку в правилах видно в истории, её можно обсудить и откатить вместе с релизом.
- Конфигурации сборочных задач — декларативное описание шагов и условий запуска: по какой ветке собираем, какие проверки обязательны, что пропускается для правок в документации.
- Схема зависимостей — граф пакетов и связей между ними; по нему вычисляется, какие сборки и тесты затронуты изменением, а какие запускать не нужно.
- Регламент проверок кода — обязательный набор статического анализа и тестов с порогами покрытия до слияния, порядок исключений и то, кто их утверждает.
- Политики доступа к сборочным агентам — кто вправе запускать сборку, менять правила, читать секреты; где хранятся ключи, как выдаются и как отзываются при уходе человека.
- Документация по сборке — устройство конвейера, где смотреть журналы и метрики, какие отказы типовые и что делать при падении на каждом этапе.
- Инструкция для разработчиков — как воспроизвести сборку локально, как добавить пакет и не сломать граф, какие соглашения об именовании веток и коммитов приняты.
- Регламент дежурства — кто реагирует на упавшую сборку, за какое время, когда конвейер останавливают и как об этом сообщают команде.
Этот набор остаётся у команды после запуска: его не восстанавливают по памяти, а нового инженера вводят в процесс за дни, а не недели.
Кейс: монорепозиторий на десятки модулей и время проверки
Заказчик пришёл с монорепозиторием на несколько десятков модулей, где каждый коммит запускал сборку и проверку всего дерева. Медиана проверки держалась около часа, очередь на сборочные машины росла быстрее, чем команда, и разработчики начали копить правки в крупные пакеты — лишь бы не ждать конвейер лишний раз.
Замеры снимали дважды и независимо. Сначала по журналам GitLab CI — время каждой стадии на одном и том же наборе изменений до и после перестройки. Затем прогнали те же коммиты на копии репозитория и отдельном исполнителе, чтобы исключить влияние кэша и соседних задач. Оба замера сошлись в пределах погрешности — результат воспроизводимый, а не разовый удачный прогон.
Что именно изменилось в конвейере
- Собрали граф зависимостей модулей и держим его в репозитории рядом с кодом.
- Проверка идёт только по затронутым модулям и тем, кто от них зависит; остальное помечается пропуском.
- Кэш артефактов привязали к содержимому модуля, а не к номеру сборки, — он переиспользуется между ветками.
- Быстрые проверки отсекают заведомо непроходящие изменения до тяжёлых задач.
- Параллельность ограничили по числу сборочных машин, чтобы очередь не съедала выигрыш.
После внедрения медиана проверки упала с часа до считанных минут, а сборки, задевающие меньше десятой части модулей, стали преобладающими. Нагрузка на сборочные машины снизилась настолько, что освободившиеся мощности отдали под ночные прогоны. Отдельно ушёл класс отказов, который списывали на «нестабильные тесты»: падения шли из общего состояния и кэша между модулями, теперь у каждой задачи изолированный рабочий каталог.
Что делать, если сборка уже разрослась до десятков модулей
Да, переход на выборочную сборку в монорепозитории делается без остановки разработки — при одном условии: выборочный контур сначала считается параллельно с полным прогоном и не влияет на статус основной ветки. Сравнение двух прогонов на одном и том же изменении показывает, какие модули система считает затронутыми и где граф зависимостей врёт.
Устаревшие модули выносят отдельным шагом: выводят из обязательной сборки, фиксируют последнюю рабочую точку и держат архивной веткой. Сначала проверяют, что на них не ссылается живой код — скрытая зависимость всплывает именно после исключения.
| Шаг | Что затрагивает | Чем рискуем |
|---|---|---|
| Инвентаризация зависимостей | граф модулей, общие библиотеки | неполный граф: правка библиотеки не тянет потребителей, ветка выглядит зелёной |
| Наблюдение | только отчёты, контур не меняется | расхождение в правилах определения затронутых модулей |
| Перевод части модулей | правила по путям, кеш промежуточных артефактов | общая библиотека меняется — пересобирать приходится всех потребителей |
| Заморозка устаревших модулей | исключение из обязательных проверок | скрытая ссылка живого кода на замороженный |
| Полный перевод | основная ветка, обязательные проверки | потеря страховки: нужен быстрый откат к полному прогону |
Каждый шаг откатывается одной правкой правил, а полный прогон снимается последним — когда выборочная сборка сходится с ним по результатам. Итог: типовое изменение пересобирает единицы модулей вместо десятков, основная ветка не простаивает.
Частые технические вопросы перед стартом работ
Перед стартом инженерные команды задают примерно один и тот же набор вопросов: насколько полны проверки при выборочной сборке, что происходит с общими библиотеками и как конвейер живёт после передачи. Отвечаем коротко — по существу.
Как убедиться, что выборочная сборка не пропускает нужные проверки?
Граф зависимостей строится по манифестам пакетов и импортам в коде, а не по структуре каталогов. Изменённые пути сопоставляются с пакетами, и в сборку попадают все, кто зависит от них транзитивно. Поверх этого на защищённых ветках всегда идёт полный прогон — он и служит эталоном, с которым сверяем выборочный результат.
Что будет, если изменилась общая библиотека?
Изменение в общем пакете помечает всех его потребителей, поэтому они пересобираются вместе с ним. Отдельно обрабатываем «глобальные» правки — файлы фиксации версий, конфигурацию сборки и корневые правила: они запускают полный прогон, потому что влияют на весь репозиторий.
А если проверка всё-таки пропустится?
Ложные пропуски ловим сравнением списка затронутых пакетов с полным прогоном на релизных ветках. Каждое расхождение разбираем и закрываем правилом маппинга или явным исключением. Такие расхождения фиксируются в журнале — видно, что причина ушла, а не повторилась через месяц.
Кто поддерживает конвейер после запуска?
Конфигурация лежит в репозитории рядом с кодом и меняется через обычное ревью. Правила сопоставления, схема кэша и порядок добавления нового пакета описаны в документации, поэтому команда заводит сервисы по шаблону без нас. Роли разделены: разработчик запускает сборки и читает логи, правка общих правил и секретов — через отдельный доступ и согласование.
Обсудим задачу: что уточнить до старта
Чтобы оценка работ по сборке опиралась на факты, а не на предположения, нам нужны исходные данные по репозиторию: сколько в нём пакетов и приложений, где лежат общие библиотеки и кто их переиспользует. По этим сведениям мы считаем, какие проверки запускать только для затронутых частей кода, а какие — всегда, и готовим план внедрения с реалистичными сроками.
Ниже — то, что стоит уточнить до старта. Если часть данных пока недоступна, обсудим задачу по тому, что есть: пробный прогон на одном пакете обычно показывает реальную картину быстрее, чем полное описание словами.
- Устройство репозитория: число пакетов, границы между ними, общие модули и связи между сборками.
- Схема веток: какие ветки считаются основными, как оформляются и сливаются изменения.
- Текущее время проверки: сколько идёт полный прогон и сколько — проверка одной правки.
- Самые долгие и самые хрупкие проверки: что чаще всего падает и почему.
- Ограничения по доступам: права к репозиторию, сборочным узлам и хранилищу артефактов.
- Ожидания по срокам внедрения: что критично получить в первую очередь, а что можно отложить.
Опишите задачу в свободной форме — вернёмся с оценкой и планом работ, без общих слов.







