CI/CD для монорепозиторію: де ламається збірка
Монорепозиторій виграє на старті й починає гальмувати випуск на обсязі. Перше вузьке місце — збірка всього дерева пакетів на кожну зміну: правка в одному сервісі тягне тести, лінтери й збірку решти, і час збірки зростає від кількості пакетів, а не від розміру правки. Очікування збірки перед випуском стає частиною релізного циклу, а не його підготовкою.
Причина зазвичай не в потужності агентів, а у відсутності графа впливу. Поки задачі не знають, від яких пакетів залежать, планувальник не може пропустити недоторкані гілки й перевикористати результати попередніх прогонів.
Кеш за хешем входів — файлів і версій залежностей — повертає результат за секунди, якщо входи не змінилися.
Далі — конфлікти версій залежностей. Спільна бібліотека піднімає мажорну версію, частина пакетів залишається на попередній, і в одному дереві живуть дві несумісні копії.
Локально збірка проходить, а на спільних кроках розходяться типи й поведінка утиліт: шукати доводиться в пакеті, якого автор правки не торкався.
Окремий біль — падіння перевірок на чужих пакетах. Падає тест, до якого правка не має стосунку, гілка не вливається, стовбур червоний — і з'являються обходи у вигляді вимкнених кроків.
Нижче розбираємо, як це закривається: граф впливу, віддалений кеш і правила щодо версій спільних модулів.
Ефект єдиного конвеєра: швидкість і передбачуваність
Перший відчутний ефект — збірка заторкнутих пакетів замість усього репозиторію. Конвеєр будує граф залежностей і на кожну зміну проганяє лише задачі, чиї входи змінилися: правка в одному пакеті більше не тягне перезбирання всього дерева.
Кеш результатів збірки працює за хешем входів задачі — файли, змінні середовища, версія інструментів, — тому повторний прогін на тій самій ревізії забирає готовий артефакт замість повторного обчислення.
Звідси другий наслідок — відтворюваність випуску. Bazel і системи збірки того самого класу вимагають явно описаних входів кожної цілі, тому результат перестає залежати від того, що вже лежить на конкретній машині збирача.
Той самий коміт дає ідентичний артефакт у розробника, в конвеєрі та на стенді: розбіжності ловляться на перевірці, а не в живому контурі.
Третя метрика, яку зазвичай не рахують, — частка хибних падінь. Поки збірка залежить від порядку задач і прогрітого кешу одного збирача, конвеєр червоніє на рівному місці, і команда звикає перезапускати наосліп. Чистий граф та ізольовані кроки це прибирають: червоний результат знову означає реальну поломку.
Час відновлення після збою скорочується тим самим кешем і відкатом на попередню ревізію — конвеєр повторює лише гілку, що впала, а не весь шлях з нуля.
Підсумок на типовій зміні: час збірки падає на порядок, повторюваність результату перестає бути предметом віри, а невдалий випуск відкочується й переграється передбачувано.
Порядок впровадження: від аудиту до передачі
Порядок робіт не довільний: кожен наступний крок спирається на дані попереднього. Спочатку знімаємо факти — час збірки за задачами, склад повторних прогонів, поведінку кешу, — і лише потім змінюємо конфігурацію. Так правки в конвеєрі спираються на заміри, а не на припущення про те, де «гальмує».
- Аудит кроків збірки. Проганяємо чинний конвеєр, заміряємо час кожної задачі й рахуємо, скільки роботи виконується повторно за незмінених файлів.
- Схема залежностей пакетів. Будуємо граф: що від чого залежить, які пакети збираються паралельно, де циклічні зв'язки заважають поділу на незалежні задачі.
- Проєктування правил запуску. Фіксуємо, які задачі піднімаються на змінений пакет і його залежні, як рахується ключ кешу, що відбувається на гілках і релізних мітках.
- Налаштування середовищ збірки. Зводимо версії інструментів на агентах до одного набору, щоб локальний прогін і конвеєр давали однаковий результат і збої відтворювалися.
- Перевірка на реальних змінах. Проганяємо конвеєр на справжніх комітах: правка в листовому пакеті, оновлення спільної бібліотеки, відкат на попередній реліз.
- Передача конвеєра команді. Віддаємо конфігурації з коментарями, розбираємо типові збої та порядок їх усунення, відкриваємо доступи й доповнюємо документацію.
Що залишається в репозиторії після впровадження
На руки замовник отримує не «налаштований сервер», а комплект артефактів, за яким команда розвиває збірку без нас — для монорепозиторію на Turborepo, Nx або Bazel. Кожен пункт лежить у репозиторії поруч із кодом і проходить рев'ю як звичайна зміна.
- Конфігурація конвеєра — які задачі запускаються на зміну, що кешується між запусками, які кроки йдуть паралельно, а які лише на основній гілці, як додати нову задачу.
- Схема залежностей — граф пакетів і застосунків: що від чого залежить, які задачі зачіпає правка одного файлу, де межа між спільними бібліотеками й продуктами.
- Правила обов'язкових перевірок — що блокує злиття, що працює як попередження, на яких шляхах спрацьовує, хто і за яких умов може обійти блокування.
- Політики доступу до секретів — де зберігаються ключі, які задачі та гілки їх бачать, що закрито для запитів із зовнішніх копій, як проходить ротація.
- Регламент випуску — версіонування пакетів, порядок публікації артефактів, послідовність викочування та відкат на попередній реліз.
- Документація для команди — структура репозиторію, типові операції (додати пакет, оновити спільну бібліотеку, прискорити збірку) і розбір частих помилок.
Комплект передаємо з розбором: показуємо, де що лежить і як це змінювати, щоб конвеєр не перетворився на чорну скриньку, доступну лише авторам налаштування.
Кейс: скорочення часу збірки на великому репозиторії
Монорепозиторій на 42 пакети й чотири продуктові команди: кожна правка в спільному модулі запускала повний прогін CI/CD, який тривав 55 хвилин. Розробники перестали чекати на результат і йшли в інші задачі, а черга прогонів зростала швидше, ніж закривалися зміни.
Що було
Конвеєр збирав і перевіряв усі пакети на кожен коміт, кеш артефактів не перевикористовувався між прогонами, а статичний аналіз, перевірка типів та інтеграційні перевірки йшли однією стадією.
Близько третини падінь виявлялися хибними: нестабільні перевірки та розбіжності середовищ — перезапуск «на щастя» став звичкою, а справжні помилки губилися в шумі.
Що змінили
Побудували граф залежностей і перейшли на Turborepo: збираються й перевіряються лише заторкнуті пакети та те, що від них залежить.
Додали віддалений кеш збірок і результатів перевірок, розвели стадії на швидкі та важкі, закріпили середовища, а нестабільні перевірки винесли в карантин з автоматичною позначкою задачі.
Що стало
Повний прогін скоротився з 55 до 14 хвилин, типовий прогін за однією правкою — до 3–4 хвилин. Частка хибних падінь упала з 30% до 4%: перевірка знову стала сигналом, а не приводом перезапустити.
Відкат невдалого випуску займає близько трьох хвилин замість сорока хвилин ручних дій — повертаємо попередню збірку однією командою.
Чим відрізняється CI/CD для монорепозиторію від збірки частинами?
У наборі окремих репозиторіїв одиниця зміни — репозиторій, тому задача, що зачіпає кілька пакетів, розпадається на кілька незалежних наборів змін. Їх доводиться узгоджувати вручну: порядок злиття, сумісність версій, момент випуску. У монорепозиторії одиниця зміни — весь граф.
Ключова відмінність — як вибирається обсяг збірки. Окремі конвеєри або збирають усе підряд, або не бачать зв'язків між репозиторіями й пропускають потрібне.
Монорепозиторій будує граф залежностей пакетів і обчислює заторкнуту підмножину: кеш спирається на вміст пакета та його залежностей, тому перезбирається лише реально змінене.
Єдина версія спільних бібліотек знімає окремий клас відмов. Коли пакети лежать нарізно, спільна бібліотека розходиться за версіями і в експлуатації одночасно живуть кілька її варіантів — несумісність випливає вже на працюючому контурі. У монорепозиторії бібліотека одна, і зміна проходить перевірки всіх споживачів до випуску.
Атомарний випуск — наслідок єдиного графа. Правка публічного інтерфейсу та всі її споживачі виїжджають одним набором, без вікна розсинхрону між ними. Відкат повертає узгоджений стан цілком, а не половину системи.
Плата — дисципліна конвеєра: розбиття задач за пакетами, ізоляція кешу, права доступу на рівні пакетів, версіонування релізів. Налаштування складніше, ніж набір окремих конвеєрів, але саме воно дає передбачуваний час перевірки та відтворюваність результату.
Практичні сумніви: відкати, кеш, ізоляція середовищ
Сумніви навколо конвеєра в монорепозиторії зазвичай одні й ті самі: один коміт зачіпає відразу кілька пакетів, кеш прискорює збірку, але іноді віддає застарілий результат, а середовища ділять один репозиторій. Нижче — короткі відповіді на те, що найчастіше питають перед стартом.
Як відкотити невдалий випуск?
Кожен реліз — незмінний артефакт з міткою версії. Відкат — перемикання вказівника на попередній артефакт: перезбирання не потрібне, стан повертається за хвилини. Міграції схеми робимо зворотно сумісними, щоб код попередньої версії працював з поточною базою.
Що робити, якщо кеш збірки віддає неправильний результат?
Ключ кешу рахуємо від вмісту вхідних файлів, конфігурації та версій залежностей — за зміни будь-якого з них запис інвалідується сам. За підозри на отруєний кеш скидаємо його за конкретною гілкою, а не глобально: решта збірок не втрачає накопичене прискорення.
Як розводяться середовища?
Той самий артефакт просувається середовищами без перезбирання — змінюються лише значення змінних і секретів. Збірка на тестовому контурі та на продуктивному ідентичні, тому розбіжності не випливають у момент випуску.
Хто має права доступу до конвеєра?
Доступ розводимо за ролями: розробник запускає перевірки у своїй гілці, випуск веде лише захищена гілка з обов'язковим погодженням. Секрети продуктивного контуру недоступні задачам із користувацьких гілок.
Чому перевірки перед вливанням тривають так довго?
Обов'язковий набір збирає й тестує лише заторкнуті пакети за графом залежностей — це хвилини, а не години. Повний прогін іде в нічний розклад і не блокує роботу над наступною задачею.
Обговоримо задачу й повернемося з планом впровадження
Щоб повернутися з планом впровадження, а не із загальними міркуваннями, нам потрібен мінімум вхідних даних з вашого боку. Готувати нічого заздалегідь не потрібно — достатньо того, що вже є в репозиторії та в трекері задач.
- структура репозиторію: де лежать пакети й застосунки, як пов'язані залежності між ними;
- поточний час збірки й тестів — із журналів збірки, навіть якщо вони запускаються вручну;
- вузькі місця випуску: які кроки чекають найдовше і скільки ручних дій на реліз;
- правила гілкування та список середовищ, куди йде зміна після злиття;
- доступ на читання до репозиторію, системи збірки та реєстру образів — для замірів.
За цими даними ми відтворюємо картину збірки й дивимося, які задачі перезбираються марно, де ланцюжок перевірок виконується послідовно, хоча міг би йти паралельно, і на якому кроці випуск втрачає найбільше часу.
- схему графа залежностей із позначкою, які кроки розпаралелюються;
- план впровадження: кеш збірки, інкрементальні перевірки, віддалений запуск задач, випуск;
- оцінку строків за кожним етапом і порядок їх безпечного включення;
- перелік місць, де потрібна правка конфігурацій пакетів, а де — домовленості всередині команди;
- метрики звірки результату: час збірки, частка перевикористаного кешу, час до випуску.
Опишіть задачу — які застосунки в монорепозиторії та що зараз заважає випускати частіше. Повернемося з оцінкою строків і планом, який можна вмикати поетапно.







