Инженерная практика
Гейт зелёного trunk — это вся сделка
Команда из 40 инженеров переходит на trunk-based и за месяц объявляет это провалом: trunk красный половину дня, а когда он красный, никто не может вытянуть чистую базу или слиться на зелёном, так что вся команда стопорится. Диагноз не «trunk-based не масштабируется» — а в том, что они приняли риск (все на одной ветке) без смягчения (гейт, держащий эту ветку зелёной). Trunk-based без быстрого блокирующего CI-гейта — не trunk-based; это общая ветка без тормозов.
Общий trunk концентрирует риск
Сила trunk-based development — все интегрируются в одну ветку — одновременно его единственная точка отказа. С долгоживущими ветками сломанная ветка одного человека — проблема одного человека. С общим trunk сломанный trunk — проблема всех: никто не может вытянуть чистую базу, чтобы начать работу, никто не может слиться, потому что его PR строится на красном, а «быстрый фикс» приземляется поверх поломки, делая отказ труднее в диагностике. Практика намеренно меняет изолированную отложенную боль на общий немедленный риск — и эта сделка окупается, только если trunk держат зелёным.
Так что гейт — не дополнение; это вторая половина сделки. Правило — нет зелёного, нет слияния: каждое изменение прогоняет полную сборку и набор тестов до приземления на trunk, а красный результат блокирует слияние. Вот почему trunk-based и непрерывная интеграция — одна и та же практика, описанная с двух сторон: «мёржить в trunk ежедневно» безопасно лишь потому, что одновременно держится «автоматический гейт держит trunk всегда релизуемым». Данные DORA показывают, что сила trunk-based идёт через CI; примите модель ветвления без гейта — и вы получите режим отказа красного trunk вместо выгоды.
Гейт должен быть быстрым, или его будут обходить
Эффективность гейта затухает с его задержкой. Если сборка занимает 45 минут, разработчики начинают батчить изменения, чтобы амортизировать ожидание — что удлиняет ветки, что вновь впитывает разрыв из урока 1. Они срезают углы, пропускают локальный прогон тестов или давят слить на красном «только в этот раз». Медленный гейт тихо разрушает то самое поведение, ради обеспечения которого существует. Цель — минуты: сборка достаточно быстрая, чтобы интегрироваться мало и часто было путём наименьшего сопротивления. Скорость идёт от параллелизации тестов, ярусности (быстрые юнит-тесты блокируют слияние; медленные end-to-end наборы бегут post-merge или по ночам), кэширования и беспощадной вырезки самых медленных нарушителей в наборе.
Когда trunk всё же краснеет вопреки гейту — проскочил флейки-тест, отказ только в окружении — сеньорский рефлекс — остановить линию: починка или откат trunk важнее новой работы над фичами, потому что каждая минута красного облагает налогом всю команду. git bisect делает поиск виновного коммита дешёвым через бинарный поиск по истории, а малая, часто интегрируемая история — ровно то, что делает bisect быстрым, а откат чистым.
| Дизайн гейта | Когда бегут тесты | Может ли trunk сломаться? | Предел масштабирования |
|---|---|---|---|
| Только post-merge CI | После приземления на trunk | Да — обнаружено поздно | Ломается постоянно после пары разработчиков |
| Pre-merge (тест PR-ветки) | До слияния, против устаревшей базы | Да — два зелёных PR могут конфликтовать семантически | Гоняется сам с собой при высокой частоте слияний |
| Merge queue (тест против проецируемого trunk) | Против trunk + PR впереди в очереди | Нет — приземляется только зелёное против финального состояния | FIFO-бутылочное горло ~20–30 разработчиков; флейки-тесты её стопорят |
В масштабе гейт гоняется сам с собой — на сцену выходят merge queue
У наивного pre-merge гейта есть тонкая дыра. Вы тестируете PR-A против trunk: зелёный. Я тестирую PR-B против того же trunk: зелёный. Мы оба мёржим. Но A и B никогда не тестировались вместе — они могут конфликтовать семантически и сломать trunk, хотя каждый прошёл. При паре слияний в день это редко; при десятках — постоянно. Лекарство — merge queue: вместо прямого слияния PR входят в очередь, и CI тестирует каждый против проецируемого состояния trunk — trunk плюс все PR впереди него в линии. PR приземляется, только если он зелёный против ровно того состояния, которое создаст. Чтобы держать пропускную способность, очереди батчат: тестируют несколько очередных PR вместе как один комбинированный кандидат и мёржат весь батч, если зелёный, откатываясь к bisect батча, если он падает.
Так большие команды держат единственный trunk зелёным при высоком объёме — собственная merge queue GitHub отмасштабировала монорепозиторий с примерно тысячи слияний в месяц в 2016-м до более чем тридцати тысяч к 2023-му, с сотнями инженеров, мёржащих тысячи PR в месяц, и примерно на треть ниже среднее время деплоя. Но у очереди есть пределы, которые сеньор должен планировать. Единственная FIFO-очередь становится бутылочным горлом после примерно 20–30 активных разработчиков: несвязанные изменения конкурируют за одну полосу, и медленный CI стопорит всё за ним. Хуже того, единственный флейки-тест катастрофичен в очереди — при 50+ PR в день он может застопорить линию на часы, потому что недетерминированный отказ отвергает батчи, которые на самом деле были в порядке. Вот почему карантин флейков и параллелизм очереди (независимые полосы для независимого кода) становятся первоочередными инфраструктурными заботами в масштабе, а не приятными мелочами.
Почему это работает
Заметьте: требование к качеству гейта симметрично дисциплине флагов из прошлого урока. Флейки-тест в merge queue — это версия устаревшего флага у гейта: часть системы, которая должна давать ясный сигнал, но вместо этого излучает шум, и шум нарастает в масштабе, пока не остановит всех. Trunk-based в масштабе — меньше про модель ветвления и больше про то, чтобы держать каждый сигнал в системе — гейт, очередь, флаги — заслуживающим доверия. Сигнал, которому нельзя доверять, хуже отсутствия сигнала, потому что люди его обходят.
При 50 PR/день на одном trunk он ломается пару раз в неделю, хотя каждый PR был зелёным до слияния. Какое правильное лекарство?
Почему trunk-based development неотделим от быстрого блокирующего CI-гейта?
Два PR каждый проходят CI против текущего trunk, затем оба мёржатся — и trunk ломается. Что случилось, и что это предотвращает?
Упорядочьте, как merge queue безопасно приземляет изменение в масштабе:
- 1 PR проходит свой собственный CI и аппрувится
- 2 PR входит в merge queue, а не мёржится напрямую
- 3 CI тестирует его (часто батчем) против trunk + PR впереди в линии
- 4 Зелёный против проецируемого финального состояния → батч мёржится в trunk
- 5 При отказе — bisect батча, чтобы выкинуть виновный PR и перезапустить
- 01Почему команда, которая «перешла на trunk-based», заканчивает с постоянно красным trunk, и какая часть отсутствует?
- 02Объясните, почему наивный pre-merge гейт гоняется сам с собой в масштабе и как merge queue это чинят, включая пределы очередей.
Общий trunk — это сила trunk-based development и его единственная точка отказа: когда все интегрируются в одну ветку, сломанный trunk блокирует всю команду от чистой базы и от слияния, так что практика неотделима от быстрого блокирующего CI-гейта — нет зелёного, нет слияния, — который держит trunk всегда релизуемым. Гейт должен бежать за минуты, потому что медленный гейт толкает разработчиков батчить изменения и вновь впитывать разрыв, ради устранения которого существует trunk-based, а когда trunk всё же краснеет, сеньорский рефлекс — остановить линию и починить или откатить до новой работы, используя git bisect на малой частой истории, чтобы быстро найти виновника. В масштабе наивный pre-merge гейт гоняется сам с собой — два PR, каждый зелёный против одной базы, всё равно могут сломать trunk при объединении — так что merge queue тестируют каждый PR против проецируемого trunk и батчат ради пропускной способности, техника, что позволила GitHub держать один монорепозиторий зелёным с тысячи до тридцати тысяч слияний в месяц. Но очереди не бесплатны: единственная FIFO-очередь становится бутылочным горлом после примерно 20–30 разработчиков, а один флейки-тест может застопорить её на часы, делая контроль флейков и параллельные полосы ключевой инфраструктурой. Сквозная мысль: trunk-based в масштабе — это дисциплина держать каждый сигнал — гейт, очередь, флаги — заслуживающим доверия, потому что сигнал, которому люди не могут доверять, обходят.