× Install ThecoreGrid App
Tap below and select "Add to Home Screen" for full-screen experience.
B2B Engineering Insights & Architectural Teardowns

Git fetch в CI: как Datadog снял CPU с узкого горлышка

Git fetch в CI оказался не фоновым шагом, а главным источником нагрузки. Datadog перестроил путь выдачи кода через gitretriever и тем самым снял давление с монорепозиториев и старого Git backend.

Если CI долго висит на «Fetching repository», проблема часто не в сборке. Проблема в том, что git fetch сам по себе становится дорогой операцией, особенно на больших монорепозиториях. В Datadog это проявилось на масштабе: миллионы fetch-запросов в неделю, тысячи репозиториев и всплески нагрузки от CI, deployment, audit и security-сервисов. На таком фоне деградация быстро превращалась в многочасовые CI outages.

Сначала команда пробовала стандартные меры. Они увеличивали capacity, поднимали размер инстансов, разводили репозитории по выделенным backend’ам и оптимизировали pipelines. Это давало краткий эффект, но не меняло механику нагрузки. Причина была в архитектуре: в replicated setup каждый write нужно было размножать на все реплики, а чтение росло вместе с числом CI jobs. В итоге система масштабировалась не по той оси.

Главный вывод оказался инженерно простым. CDN и caching proxy не решали проблему, потому что дорогая часть fetch — не статический байтовый диапазон, а вычисление, зависящее от конкретного запроса клиента. Вынос clone напрямую в GitHub тоже не помогал, потому что создавал thundering herd уже на стороне upstream и упирался в rate limits. Поэтому Datadog пошёл не путём добавления ещё одного слоя, а путём разделения ролей в самом Git-потоке.

Решением стал gitretriever — Git mirror, построенный как отдельный слой выдачи кода для CI. Архитектура опирается на независимые pods, которые держат свежую локальную копию репозиториев и не ждут консенсуса между узлами. В этой модели mirrors синхронизируются с GitHub, а relays раздают reads CI-задачам. Такое разделение — прагматичный компромисс: mirrors остаются маленькими и хорошо себя ведут по отношению к GitHub, а relay-флот может расти по CPU и network load без пропорционального роста нагрузки на upstream.

Сама реализация строится вокруг того, как Git хранит и отдает данные. В статье описаны blobs, trees, commits, packfiles, delta compression, reachability bitmaps, multi-pack indexes и pack reuse. Это важно, потому что основная стоимость fetch возникает на сервере при сборке response packfile. Именно там Git может тратить CPU и I/O на декомпрессию, повторную компрессию и индексирование. Gitretriever не отменяет эту стоимость. Он меняет место, где она возникает, и ограничивает число повторных вычислений.

Команда добавила несколько механизмов, которые уменьшают повторную работу. Mirrors постоянно опрашивают upstream, выполняют parallel fetch для изменённых refs и не концентрируют дорогое delta compression в одном запросе. Relays получают packfile по сигнализирующему gRPC stream и передают bytes через plain HTTP endpoint. При этом relay не пересобирает packfile заново, а просто устанавливает полученный артефакт по content-addressed hash. Это ключевой момент: работа по pulling и indexing выполняется один раз на mirror, а relays переиспользуют результат.

Отдельно Datadog добавил pack cache. Он позволяет обслуживать одинаковые запросы без повторной сборки packfile. По данным из источника, около половины pack-building fetches обслуживаются из cache. Это снижает дельта-компрессию на mirrors и relays. Дополнительно система использует readiness check, который понимает состояние Git, и background repacking, чтобы держать число packfiles под контролем, пока pod продолжает обслуживать запросы.

Интересно, что после миграции картина нагрузки подсветила ещё один слой проблемы. Оказалось, что многие non-CI workloads не нуждаются в полном clone. Им нужен один файл на commit, SHA ветки, список изменённых файлов или merge base. Для таких запросов команда добавила read-only HTTP API. Это важное эволюционное улучшение: API отвечает за single-digit to tens of milliseconds, тогда как shallow clone большого monorepo занимает около 75 секунд и удерживает CPU core почти всё это время. Здесь trade-off очевиден: вместо универсального, но тяжёлого интерфейса появился более узкий, но намного дешевле обслуживаемый путь.

Внедрение тоже было сделано осторожно. Команда использовала feature flags и fallback на старый backend в CI jobs. Миграция шла по группам репозиториев, начиная с самого большого monorepo. Это снизило операционный риск. После первого cutover на старом backend сразу увидели падение CPU. Позже цифры подтвердили эффект: синхронизация сократилась с нескольких секунд до нескольких сотен миллисекунд, median serve latency держится около 40 ms, а fetch-serving CPU на предыдущем Git backend снизился в 3–4 раза. При этом система выдержала рост трафика примерно в 20 раз за 4 месяца и прошла путь к более чем 100 млн requests в неделю.

Итог здесь не в том, что Git стал «быстрее» в абстрактном смысле. Итог в том, что Datadog убрал концентрацию CPU в одном месте и перестал заставлять каждую новую нагрузку размножать старую проблему. Для CI, SRE и platform teams это, пожалуй, главный урок: иногда масштабирование заканчивается не на добавлении ресурсов, а на пересборке самого пути данных.

Ознакомиться с источником

×

🚀 Deploy the Blocks

Controls: ← → to move, ↑ to rotate, ↓ to drop.
Mobile: use buttons below.