Подробные статьи о технологиях, определяющих будущее.

Когда Git ломается: монорепо при 30 000 слияний в день

Масштабирование git-монорепозитория предсказуемо ломается при высокой частоте слияний. Уроки от клиентов, которые всегда онлайн, и серверных коммитов.

Иллюстрация: роботы-курьеры толпятся у стойки выдачи в огромном архиве коробок с файлами.
Git предполагает, что каждый клиент хранит весь архив. Это работает, пока тысячи клиентов не начинают обращаться к нему каждую секунду.

Если вы ведёте git-монорепозиторий, рано или поздно наступает момент, когда всё начинает разваливаться. У Anthropic этот порог был примерно таким: около 30 000 слияний в день, история основной ветки в 100 ГБ и порядка 25 миллионов CI-checkout в день. Если загружать всё наивным способом, это больше 600 петабайт трафика git. Каждый день. Их ответом стало написание собственной системы контроля версий за пять месяцев. Вам такой ответ не нужен. Но ошибки, на которые они наткнулись, те же, что я вижу у команд в десять раз меньшего масштаба, так что давайте пройдёмся по ним.

Ошибка первая: обращение с централизованным процессом как с распределённым

Главная ставка дизайна Git — децентрализация. Каждый клон — полноценный репозиторий: полная история, полное хранилище объектов, полная работоспособность офлайн. Эта ставка сделала Git тем, чем он стал, и для большинства репозиториев она до сих пор верна. Но есть одна вещь, которую почти никто не признаёт: почти никто не использует git так. У вашей организации есть один авторитетный удалённый репозиторий на GitHub или GitLab. Разработчики постоянно пушат и тянут изменения. CI-раннеры по определению всегда онлайн. Вы работаете с централизованной системой в распределённом костюме и платите распределёнными издержками: каждое CI-задание тянет историю, которую никогда не прочитает, а каждая песочница агента разворачивает packfile-ы, которые ей не нужны.

Это первый реальный урок из переработки Anthropic. Пересмотрев всё, они заметили, что их клиенты — это «агенты и задачи, которые по определению онлайн», и спроектировали систему вокруг сервера, который всегда доступен. Клиент намеренно сделали простым: ограниченный локальный кэш, рабочая копия на диске, наблюдатель файловой системы. Сервер отвечает на blame, вычисляет diff и выполняет слияния. К такому же выводу пришли Google с Piper и Meta с Sapling. Это не позиция против git, а признание того, что архитектура должна соответствовать реальной нагрузке, а не той, для которой инструмент проектировали двадцать лет назад. Я видел этот паттерн и в инструментах безопасности: команды разворачивают архитектуру агентов с поддержкой офлайн-работы, а потом запускают её в среде, где офлайн не бывает никогда, и удивляются, почему слой синхронизации превращается в кошмар.

Вывод для вашей команды не в том, что нужно «написать свою VCS». Нужно пересмотреть допущения. Запишите, от каких свойств git вы на самом деле зависите. Работа офлайн? Настоящие форки? Криптографическая подпись коммитов? Если честный ответ — «один remote, всегда онлайн, всё делает CI», перестаньте оптимизировать распределённый процесс, которого у вас нет, и начните оптимизировать стоимость checkout и запросов.

Ошибка вторая: маскировка масштаба зеркалами и seed-ами

Каждая команда с большим репозиторием проходит одни и те же стадии отчаяния. Сначала shallow clone. Потом sparse checkout и promisor remote. Потом внутренние зеркала git, затем снимки-«seed» в blob storage, чтобы CI-задания могли стартовать с чего-то близкого к вершине main. Anthropic использовали всё это, на пике несколько сотен внутренних зеркал. Сбой всегда одинаковый: обходные пути не падают громко, они гниют. Seed-ы между обновлениями всё дальше уходят от вершины. Частичные клоны ломают git blame и git log --follow самым запутанным образом. Чей-то bisect молча возвращает неверные результаты, потому что история обрезана. Никто этого не замечает, пока не случится разбор инцидента.

Именно этот последний пункт не даёт мне спать. Ошибки корректности частичной истории — ровно тот тонкий вид неправильности, который живёт в разрыве между тем, что, как вам кажется, гарантирует система, и тем, что она делает на самом деле. Shallow clone не выдаёт ошибку, когда вы запрашиваете blame глубже его границы. Он просто отдаёт частичный ответ. Если ваш автоматический конвейер отката доверяет этому ответу, вы построили машину, которая уверенно откатывает не то изменение.

  • Shallow clone ломает инструменты, зависящие от истории (blame, bisect, merge-base), и делает это тихо, а не с громким сбоем.
  • Seed-снимки отходят от вершины main. Чем выше частота слияний, тем быстрее расхождение, так что обходной путь деградирует именно тогда, когда нагрузка растёт.
  • Парк зеркал умножает операционную поверхность: сотни stateful-копий, которые могут отставать, портиться или независимо заканчивать место на диске.
  • Каждый обходной путь, затрагивающий семантику истории, нужно покрывать регрессионным тестом, иначе он ударит вас во время инцидента.

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

Ошибка третья: заставлять каждое CI-задание поддерживать собственную копию репозитория

Вот вопрос, который стоит задать о вашем CI-парке: скольким заданиям вообще нужна рабочая копия? Ответ Anthropic после построения системы: «гораздо меньше, чем мы предполагали». Задание, которому нужен diff между коммитом и его merge base, blame для файла или список коммитов, затрагивающих путь, может спросить сервер напрямую через API. Ни клона, ни fetch, ни диска. Их автоматические конвейеры bisect и отката «были бы непрактичны в масштабе» без такого дешёвого программного доступа. Я в это верю, потому что видел, как команды пытались запускать те же конвейеры поверх локальных клонов и тонули во времени на fetch.

Этот принцип выходит далеко за рамки контроля версий. Относитесь к checkout как к дорогому исключению, а не к умолчанию. Когда заданию действительно нужны файлы, sparse и виртуализированные checkout (материализация только запрошенных каталогов или даже только реально читаемых файлов) снижают стоимость на порядки. Протокол загрузки Anthropic полностью отказался от согласования git: клиент запрашивает конкретные неизменяемые деревья и blob-ы, любой кэш может ответить, и всё работает как многоуровневая CDN, а не как умный сервер, который для каждого клиента вычисляет packfile. Если вам когда-либо приходилось профилировать git-сервер, который плавится под нагрузкой CI, вы узнаете, почему это важно: генерация packfile требует много CPU именно потому, что протокол заставляет сервер вычислять дельту относительно того, что у каждого клиента уже есть.

Если ваши клиенты всегда онлайн и есть один авторитетный экземпляр, каждый локальный клон, который вы поддерживаете, — это кэш, за который вы платите полную цену, а кэш-семантику всё равно получаете.

Ошибка четвёртая: позволить стоимости коммита расти вместе с размером репозитория

Самое интересное инженерное решение в их дизайне — changeset. Коммит в git указывает на дерево, представляющее всё состояние репозитория. Их changeset записывает только список файлов, изменившихся относительно первого родителя, плюс адресуемые по содержимому указатели в blob storage, и добавляет всё это в write-ahead log. Никаких packfile для приёма, никаких блокировок, никакой координации между серверами. Refs (ветки и теги) намеренно вынесены на второй план, потому что «глобальное состояние, с которым должны соглашаться все серверы, — это надвигающаяся проблема масштабирования». Перемещение ref — это compare-and-swap по одной строке в Spanner, отделённый от создания коммита.

Стилизованная иллюстрация: посылки на конвейерной ленте попадают в светящийся журнал и производные структуры данных.
Записывайте только то, что изменилось, в write-ahead log, а всё остальное (blame, историю путей, индексы графа) вычисляйте из него лениво.

Сопутствующая идея — «производные данные»: индексы blame, история путей, skip-списки графа коммитов, всё вычисляется рекурсивно из родителей каждого changeset и проходит через один общий механизм. Вычисляйте заранее или лениво, для каждого типа отдельно. Добавляйте новые типы индексов, не трогая клиент. Это по-настоящему хороший паттерн для любой event-sourced системы. Я рекомендовал ту же схему для конвейеров журналов аудита, где append-only журнал — это истина, а каждая поисковая структура — перестраиваемая проекция. Как только кто-то начинает вручную поддерживать проекцию вне этого механизма, появляется расхождение.

Одно замечание с позиции безопасности: комментаторы, опасавшиеся централизации, правы, и к этому стоит отнестись всерьёз. Единая серверная система, где живут коммиты, слияния и запросы истории, — высокоценная цель. AI-агенты с правом записи в «единственный репозиторий, чтобы править всеми» концентрируют риск самым плохим образом. Anthropic построили рядом «богатую систему IAM для контроля версий», и детали там важнее структур данных. Если вы централизуете, математика радиуса поражения меняется: закладывайте изоляцию, учётные данные для каждого агента и аудит с первого дня, а не с третьего квартала. Команды, которые запускают stateful-агентов на общей инфраструктуре, уже знают эту боль.

Ошибка пятая: считать, что 30 000 слияний в день — это 30 000 хороших решений в день

Инженерия впечатляет. Открытый вопрос в том, что через неё проходит. Один практик сказал прямо: каждый раз, когда он перестаёт ревьюить код, сгенерированный ИИ, он «потом находит какую-то халтуру. Не каждое ревью находит халтуру, но достаточно часто, чтобы я не мог перестать ревьюить всё». Умножьте этот уровень попаданий на 30 000 слияний в день, и станет понятно, почему Anthropic пришлось также создать «новые способы управления и приземления большого объёма проверенного кода»: поверхность ревью, очередь приземления, автоматические конвейеры отката. Продуктом был не VCS. Продуктом был конвейер приземления. Система контроля версий стала узким местом только после того, как перед ней узким местом стала ёмкость ревью.

В их историях из практики есть и откровенная деталь: инженеры проекта описывали значительную часть своей роли как говорение кодинг-агенту «нет». Написанный вручную прототип конвейера приёма был нормальным, пока более новая модель не переписала его в 20 раз экономнее по памяти и в 4 раза быстрее. Но для этого понадобились люди со вкусом, которые решали, что принять. Это совпадает со всем, что я видел: ИИ смещает работу инженера от написания к ревью, и пропускная способность ревью теперь стала дефицитным ресурсом. Мы уже писали о том, что каждый уровень ревью замедляет команду, но сложность в том, что с кодом, сгенерированным агентами, убирать уровни ревью тоже небезопасно. Нужен лучший инструментарий ревью, а не меньше ревью.

Что стоит сделать на самом деле

Вы не Anthropic. У вас нет их бюджета на токены (они сами об этом говорят), и у вас нет 25 миллионов checkout в день. Но AI-инструменты для программирования поднимают частоту слияний у всех, и точки давления появляются в предсказуемом порядке. Вот порядок действий, которому я бы следовал, в соответствии с законом наименьшего сожаления Прии:

  1. Сначала измерьте. Посчитайте ежедневные checkout, байты, загружаемые каждым CI-заданием, и сколько машин хранят полные копии репозитория. Скорее всего, проблема расточительства у вас возникнет раньше, чем проблема масштаба.
  2. Уберите ненужные checkout. Переведите вопросы о метаданных (diff, blame, предки) в API, например GitHub/GitLab API или тонкий внутренний сервис, прежде чем покупать какую-либо инфраструктуру.
  3. Сделайте sparse и частичные клоны безопасными. Явно протестируйте ваши инструменты bisect, blame и revert на shallow-клонах или запретите их в автоматизации.
  4. По умолчанию переводите CI на серверные загрузки неизменяемых объектов, где это возможно, и относитесь к паркам зеркал как к техническому долгу со сложным процентом.
  5. Инвестируйте в конвейер приземления, а не в формат репозитория. Ёмкость ревью, очереди слияния и автоматический откат — именно там масштабная разработка с агентами ломается в первую очередь.
  6. Только потом рассматривайте альтернативные архитектуры VCS. Sapling открыт и даёт большую часть преимуществ клиент-серверной модели без написания собственной системы.

Самый глубокий урок здесь вообще не про git. Каждый инструмент закладывает допущения о своей нагрузке: офлайн-люди, один коммит за раз, код, который пишется медленнее, чем ревьюится. Эти допущения прямо сейчас нарушают агенты: в инфраструктуре, в ревью, на границах безопасности. Выиграют не те команды, которые перестроят всё с нуля. Выиграют те, кто заметил, какие допущения сломались первыми, и починил именно их.