vyvchy
    Теми розділу

    09 · Git і CI/CD

    Паралелізація і швидкість пайплайна

    Зміст

    Уся цінність CI тримається на одному: швидкому зворотному звʼязку. (pipeline), який їде 40 хвилин, перестає бути й стає податком. Розробник не чекає результату — він перемикається на іншу задачу, накопичує зміни, зливає рідше й більшими партіями. Це рівно та «інтеграційна» біль, яку CI мав прибрати (див. CI/CD: інтеграція, доставка, розгортання). Для AQA це особистий біль: гарна e2e-, результату якої ніхто не дочікується, — це сюїта, якій ніхто не довіряє.

    Ця глава має бейдж «поглиблення»: при першому проході її можна пропустити й повернутися, коли ваш власний пайплайн почне гальмувати (умовно — за 15–20 хвилин на PR). Тут — єдиний повний виклад економіки швидкості пайплайна на сайті; інші глави посилаються сюди. Суміжні теми лишаються деінде: сам концепт паралельного прогону і його (ізоляція тестів і даних) — у розділі про стратегію автоматизації, а конкретні прапорці (наприклад, workers і --shard у Playwright) — у розділі про інструменти автоматизації. Ми ж дивимося на пайплайн з боку економіки: що паралелити, що кешувати, що взагалі не запускати — і скільки це коштує.

    Що саме ми оптимізуємо

    Головна метрика — не сумарний час роботи процесорів, а wall-clock: скільки хвилин минає від push до вердикту (зелено/червоно). Саме цей час чекає людина, і саме він визначає, чи працює зворотний звʼязок. Класичний орієнтир — «десятихвилинний білд» (ten-minute build): гайдлайн Extreme Programming, який закріпив Martin Fowler у принципі «Keep the Build Fast» (Humble і Farley у «Continuous Delivery» так само радять тримати commit-стадію в межах приблизно десяти хвилин). Логіка проста: якщо фідбек триває понад приблизно десять хвилин, увага розробника встигає піти на інше, і CI втрачає ефект.

    Важелів рівно два, і все в цій главі — один з них:

    1. Робити менше роботи й вибірковість.
    2. Робити роботу паралельно, matrix, .

    Між ними є прихована напруга. Паралелізація ріже wall-clock, але зазвичай підвищує сумарні хвилини (за які ви платите), бо кожна паралельна одиниця несе власний накладний коефіцієнт. Тому починають завжди з кешу — він майже безкоштовний, — і лише потім розпаралелюють.

    Паралельні джоби і matrix

    Пайплайн має ієрархію pipeline → stage → job → step (див. Будова пайплайна). Стадії йдуть послідовно за залежностями, а от джоби (job) в одній стадії за замовчуванням стартують паралельно — стільки, скільки дозволяють доступні агенти.

    Matrix- (matrix build) — це один опис джоби, розгорнутий на набір комбінацій параметрів: браузер × ОС × версія Node. Це декартів добуток: конфігурація «3 браузери × 3 ОС» — це 9 незалежних джобів. Matrix потрібен саме для різних конфігурацій (кросбраузерність, різні рантайми), а не для прискорення. Його ціна — множення хвилин, тому матрицю проріджують: свідомо виключають безглузді комбінації (наприклад, WebKit під Windows), лишаючи тільки ті, що реально несуть .

    Не плутайте два різні наміри: matrix відповідає на питання «чи працює той самий тест у різних середовищах», а шардінг (нижче) — на питання «як прогнати ту саму конфігурацію швидше».

    Шардінг: розрізати сюїту між агентами

    Шардінг (sharding) — це поділ одного й того самого набору тестів на M частин (шардів), які виконуються паралельно на M агентах. Ідеально wall-clock падає приблизно як загальний_час / M. На практиці — мінус накладні витрати.

    Ці накладні витрати (overhead) — ключ до розуміння меж. Кожен шард платить фіксовану ціну незалежно від кількості тестів у ньому: репозиторію, встановлення залежностей, завантаження браузерів, підняття . Ці секунди-хвилини — підлога, нижче якої шардінг не опускається. Тому виграш нелінійний: перші шарди дають багато, а після певної межі кожен наступний майже не зменшує wall-clock, зате додає ще одну порцію overhead. Це той самий ефект, що й у : фіксована частина роботи обмежує прискорення.

    Друга — жорсткіша — умова: тести мають бути незалежними. Жодного покладання на порядок, жодних спільних мутабельних даних чи облікових записів. Якщо два шарди водночас логіняться під одного тестового юзера, паралельність не прискорить, а породить чергу й гонки. Ця передумова (ізоляція на тест і на ) — канон розділу про стратегію автоматизації; шардінг лише оголює її відсутність.

    Механічно шардінг — це патерн / fan-in: сюїту розрізають, шарди біжать паралельно, кожен віддає частковий звіт (формат обміну з CI — JUnit XML, див. Автотести в CI: стабільність і дебаг), а окрема фінальна джоба зливає їх у єдиний вердикт. Без кроку злиття ви отримаєте M розрізнених звітів і жодного цілісного «пройшло / впало».

    Push або PR

    Білд один раз
    артефакт

    Шард 1/4

    Шард 2/4

    Шард 3/4

    Шард 4/4

    Злити часткові звіти
    JUnit XML

    Єдиний вердикт

    Push або PR

    Білд один раз
    артефакт

    Шард 1/4

    Шард 2/4

    Шард 3/4

    Шард 4/4

    Злити часткові звіти
    JUnit XML

    Єдиний вердикт

    Балансування шардів

    Наївний поділ — за кількістю файлів чи тестів порівну. Проблема в тому, що тести дуже різні за тривалістю: один e2e-флоу з логіном і оплатою може їхати хвилину, а сотня швидких перевірок — кілька секунд. Розбивши «по рівну кількість», ви легко отримаєте шард, що фінішує за 2 хвилини, і сусідній, що висить 12. А wall-clock всього прогону дорівнює найповільнішому шарду — його називають straggler (відстаючий). Решта агентів простоюють, а ви платите за час, який визначив один невдало навантажений шард.

    Правильний орієнтир — балансувати за історичним часом, а не за кількістю: розкласти тести так, щоб очікувана сумарна тривалість кожного шарда була приблизно однакова (жадібне «пакування» за минулими замірами). Де інфраструктура це підтримує — ще краще працює динамічний розподіл: черга видає наступний тест тому агенту, який щойно звільнився, тож straggler не виникає за побудовою — ціною окремого шару координації.

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

    Кешування: не робити ту саму роботу двічі

    Кеш (cache) у CI — це роботи між прогонами пайплайна. Його треба відрізняти від артефакту: артефакт передає результат між джобами одного прогону, кеш зберігає результат між різними прогонами. Це головний канон кешування у розділі — інші глави посилаються сюди.

    Три речі, які кешують найчастіше:

    • Залежності. node_modules або кеш пакетного менеджера. Ключ кешу мусить містити хеш (package-lock.json): попадання в кеш дозволяє пропустити npm install — часто найбільший одиничний пожирач часу. Це найтонше місце: якщо ключ не залежить від lock-файлу, кеш ніколи не інвалідовується — ви тягнете старі залежності й ловите загадкові фейли на, здавалося б, свіжому коді.
    • Браузери. Playwright та інші інструменти щоразу качають сотні мегабайтів бінарників браузерів. Їх кешують за версією інструмента — класичний AQA-специфічний виграш. Альтернатива — готовий Docker- із уже вшитими браузерами (див. Docker і тестові середовища).
    • Білд. Зібрати застосунок один раз, опублікувати як артефакт, а всі тестові джоби споживають готове замість перезбирання. Плюс шар-кеш самого Docker для образів.

    Золоте правило дзеркалить HTTP-кеш у браузері: кеш вартий рівно стільки, скільки варта його інвалідація. Ключуйте за вмістом — хеш lock-файлу, версія браузера, — ніколи за статичним рядком, що не змінюється. Інакше ви ризикуєте регулярно бачити зелений пайплайн, який тестує застарілий код (той самий клас проблеми, що застарілий кеш у браузері).

    Вибірковість: найшвидша джоба — та, яку ти не запустив

    Найдешевша робота — та, якої не було. Вибірковість — важіль, який недооцінюють, бо він не такий ефектний, як «додати паралельності».

    Базовий прийом — розкладати прогони за тригером. Smoke (швидкий набір критичного шляху) — на кожен PR, блокує merge, їде кілька хвилин. Повна — уночі за розкладом (cron), поза критичним шляхом, десятки хвилин. Логіка проста: фідбек на PR мусить вимірюватися хвилинами, тож 90-хвилинну регресію не можна вішати на кожен push — але можна ганяти раз на ніч і ловити те, що smoke пропустив.

    PR / push

    cron уночі

    Тригер

    Критичний шлях:
    lint + smoke
    кілька хвилин, блокує merge

    Повна регресія
    десятки хвилин, поза критичним шляхом

    Швидкий фідбек розробнику

    Ловить те, що smoke пропустив

    PR / push

    cron уночі

    Тригер

    Критичний шлях:
    lint + smoke
    кілька хвилин, блокує merge

    Повна регресія
    десятки хвилин, поза критичним шляхом

    Швидкий фідбек розробнику

    Ловить те, що smoke пропустив

    Глибший рівень — вибір за змінами (affected/changed-based): запускати тільки тести, що стосуються зміненого коду чи області. Потужно, але ризиковано: якщо мапінг «код → тести» неповний, ви пропустите тест, який саме й зловив би регресію. Тому такий вибір застосовують як прискорювач на PR, а повну нічну регресію лишають страхувальною сіткою. Ще один прийом — fail-fast: скасувати решту джобів після першого падіння, щоб не палити хвилини; ціна — втрачаєте повну картину (виправив одне, наступного прогону червоне вже інше), а на -сюїтах fail-fast буває шумним.

    Межа зон відповідальності: що саме блокує merge, як розкладати перевірки по стадіях і як coverage працює як гейт — це канон Стратегії якості в пайплайні. Тут ми дивимося на ту саму розкладку під кутом швидкості: вибірковість тримає критичний шлях коротким.

    Ціна: хвилини CI, ліміти конкурентності, self-hosted

    Hosted-ранери тарифікуються похвилинно. Паралелізація міняє гроші на wall-clock: 10 шардів — це приблизно десята частина очікування, але приблизно вдесятеро більше хвилин (плюс overhead на кожен шард, тож трохи більше). Звідси спадна віддача: після певної межі ви доплачуєте помітні хвилини за секунди зекономленого очікування.

    Друге обмеження — ліміт конкурентності (concurrency limit). Конкурентність (concurrency) тут — це скільки одиниць роботи система дозволяє тримати в польоті водночас; не плутай її з паралельністю, тобто фактичним одночасним виконанням на різних ранерах. План або організація визначають, скільки джобів біжить одночасно. Можна оголосити 20 шардів, але зі стелею у 5 паралельних джобів вони підуть чотирма хвилями — і wall-clock визначить стеля, а не число шардів. Шардінг понад ваш ліміт конкурентності не дає нічого. Сюди ж — час у черзі: на завантаженому спільному пулі ранерів джоба може хвилинами чекати старту, і це теж частина фідбеку, хоч її й не видно у власне тривалості тестів.

    Self-hosted ранери — це агенти на власному залізі. Замість похвилинної оплати — фіксована вартість; контроль над CPU/RAM (важливо для важких браузерних тестів, див. Docker); за масштабу — часто швидше й дешевше. Ціна цього — ви їх самі підтримуєте, патчите й захищаєте. Окремий ризик: self-hosted ранер, що збирає недовірені PR із форків, — це пряма діра в безпеці (доступ до секретів, виконання чужого коду). Це канон глави про секрети та конфігурацію в CI.

    Практичний висновок: спершу міряй, де час, а тоді оптимізуй критичний шлях, а не сумарні хвилини наосліп. Порядок дій майже завжди той самий — кеш (майже безкоштовний wall-clock) → вибірковість → паралельність до стелі конкурентності. Кидати шарди в сюїту, яка не ізольована, — значить лише помножити .

    Загальний контекст CI/CD у силабусі ISTQB CTFL 4.0 подано в темі про тестування у DevOps і місце автоматизованих тестів у пайплайні; сама економіка паралелізації окремою темою силабусу не є — основи CI/CD див. у гл.9.

    Типові помилки

    • Додали 20 шардів, а пайплайн майже не прискорився. Виглядає як брак паралельності, а насправді впираєтесь у ліміт конкурентності (умовно 5 ранерів) або в overhead-підлогу: кожен шард по 3 хвилини качає браузери, і цей setup зʼїдає весь виграш.
    • Один шард стабільно висить 12 хвилин, решта — по 3. Виглядає як повільний агент, а насправді нерівне балансування: розбили за кількістю файлів, а не за часом; straggler тримає весь wall-clock.
    • Кеш є, а залежності старі — і тести зелені на застарілому коді. Виглядає як баг тесту, а насправді ключ кешу не містить хеша lock-файлу: кеш ніколи не інвалідовується (той самий клас проблеми, що застарілий HTTP-кеш).
    • Прибрали нічну регресію, лишили лише smoke на PR — стало швидко. Виглядає як оптимізація, а насправді дірка в покритті: smoke не ловить регресій поза критичним шляхом, і вони течуть у прод між релізами.
    • Тести падають лише коли шардів багато. Виглядає як проблема інфраструктури, а насправді тести не ізольовані: два шарди ділять одного тестового юзера чи запис, і паралельність оголила приховану залежність (діагностика — гл.15).

    Підсумок

    1. Метрика, що має значення, — wall-clock від push до вердикту, а не сумарні CPU-хвилини. Оптимізуй критичний шлях.
    2. Важелів два: робити менше (кеш + вибірковість) і робити паралельно (джоби, matrix, шардінг). Кеш — перший, бо майже безкоштовний.
    3. Шардінг ріже час майже лінійно лише до межі, яку ставлять overhead на шард і ліміт конкурентності; за нею додавати шарди марно.
    4. Паралельність вимагає ізольованих тестів і даних; без цього вона не прискорює, а оголює флак.
    5. Кеш вартий рівно стільки, скільки варта його інвалідація: ключуй за вмістом (хеш lock-файлу, версія браузера), ніколи за статичним рядком.

    Можливі питання

    • «Ваш пайплайн їде 40 хвилин. Що робитимете?» Інтервʼюер хоче структурний підхід, а не «додам шардів»: спершу поміряти, де час (профіль джобів), потім кеш → вибірковість → паралельність до ліміту. Наосліп кидати паралелізм — червоний .
    • «Чим шардінг відрізняється від matrix?» Перевіряють чистоту понять: matrix — різні конфігурації тих самих тестів (покриття), шардінг — ті самі тести, розрізані між агентами (швидкість).
    • «Розбили сюїту на 8 шардів, а швидше стало вдвічі, не у 8 разів. Чому?» Очікувані відповіді: overhead на кожен шард, нерівне балансування (straggler), стеля конкурентності.
    • «Що ганяти на PR, а що вночі?» Smoke критичного шляху на PR (швидкий блокувальний фідбек), повна регресія nightly. Дивляться, чи розумієте, що короткий фідбек-луп важливіший за повноту на кожному пуші (канон розкладки — гл.19).
    • «Коли self-hosted ранери, а коли hosted?» Тредоф ціни, контролю над залізом і вартості підтримки — плюс безпековий нюанс з PR із форків.

    За всім цим інтервʼюер дивиться на одне: чи мислите ви економічно (wall-clock проти хвилин), чи не лікуєте симптом «більше паралелізму» без діагнозу і чи памʼятаєте передумову ізоляції.

    Джерела

    Що саме ми оптимізуємо

    • Martin Fowler — Continuous Integration — «The whole point of Continuous Integration is to provide rapid feedback»; число атрибутовано не автору, а XP: «the XP guideline of a ten minute build is perfectly within reason», і вузьке місце назване прямо — «the usual bottleneck is testing».

    Паралельні джоби і matrix

    • GitHub Docs — Understanding GitHub Actions — джоби за замовчуванням залежностей не мають і йдуть паралельно; матриця запускає ту саму джобу багато разів із різними комбінаціями змінних (ОС, версії) — тобто вона про покриття конфігурацій, а не про швидкість.

    Шардінг: розрізати сюїту між агентами

    • Playwright — Sharding — воркери масштабують у межах однієї машини, шардінг — між машинами, і шард у CI дорівнює окремій джобі; кожен шард дає власний звіт, а єдиний отримують злиттям blob-звітів, які містять і , і діфи .
    • GitHub Docs — Understanding GitHub Actions — звідки береться накладна ціна кожного шарда: кожен прогін іде на свіжій віртуалці, і один ранер виконує одну джобу за раз.

    Балансування шардів

    • Playwright — Sharding — межа наївного поділу задана самим механізмом: шардяться лише тести, які можна виконувати паралельно, і за замовчуванням одиницею поділу є файл, а не час виконання.

    Кешування: не робити ту саму роботу двічі

    • Playwright — Continuous Integration — кешувати бінарники браузерів is not recommended: відновлення кешу займає стільки ж, скільки завантаження, а залежності ОС не кешуються; якщо кешувати — за хешем версії.
    • GitHub Docs — Understanding GitHub Actions — межа між кешем і артефактом: свіжа віртуалка на кожен прогін означає, що результат для наступної джоби передають саме артефактом.

    Вибірковість: найшвидша джоба — та, яку ти не запустив

    • Martin Fowler — Continuous Integration — перший крок оптимізації названий прямо: Deployment Pipeline як «multiple builds done in sequence», де commit build «has to be done quickly», а довші перевірки їдуть далі по пайплайну.
    • GitHub Docs — Understanding GitHub Actions — інструментальна опора розкладки: крім подій, запускається за розкладом, вручну або через REST API.

    Ціна: хвилини CI, ліміти конкурентності, self-hosted

    • GitHub Docs — Understanding GitHub Actionsодин ранер виконує одну джобу за раз; своя ОС чи специфічне залізо — це self-hosted у власному датацентрі чи хмарі.
    • Playwright — Continuous Integration — self-hosted названий саме тією умовою, за якої паралельність у CI виправдана; для ширшої паралелізації дока радить шардінг між джобами.
    • Secure use reference (GitHub Actions) — зворотний бік self-hosted: такі ранери should almost never використовуватися для публічних репозиторіїв, бо будь-хто може відкрити PR і скомпрометувати середовище.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.