Архитектура · Git · AI-агенты
Как масштабировать Git-бэкенд для AI-разработки

AI-агенты создают больше короткоживущих веток, коммитов, деревьев и проверок, чем обычная команда разработчиков. Поэтому Git-бэкенд нужно оценивать не только по размеру репозитория, но и по форме нагрузки: всплескам параллельных операций, повторному чтению одних и тех же объектов и частым служебным запросам.
Почему AI-нагрузка отличается от обычной
Традиционный Git-сервер чаще обслуживает небольшое число людей, которые выполняют крупные, относительно редкие операции: clone, fetch, push и создание pull request. Агентная система может одновременно вести десятки рабочих контекстов. Каждый контекст способен создавать временную ветку, сохранять промежуточные изменения, запускать проверки и удалять ветку через несколько минут.
В результате меняется профиль нагрузки:
- растёт число мелких операций записи и чтения;
- возникают синхронные пики после генерации изменений;
- несколько агентов обращаются к одним репозиториям одновременно;
- увеличивается доля временных объектов и недостижимого мусора;
- один медленный запрос может удерживать ресурсы, нужные другим операциям.
Первым специальным термином здесь будет Git-объект: неизменяемая единица, из которой Git собирает содержимое файлов, деревья каталогов и коммиты. Именно работа с объектами обычно становится фундаментом эксперимента.
Гипотеза и границы эксперимента
Сформулируйте гипотезу до запуска измерений. Например: «узкое место возникает не в сетевом протоколе Git, а в случайных операциях хранения объектов и блокировках при обслуживании нескольких параллельных запросов». Это пример рабочей гипотезы, а не утверждение о конкретной реализации.
Зафиксируйте границы:
- какие операции считаются критичными: clone, fetch, push, создание ветки, merge;
- какой размер репозитория и сколько объектов используются в сценарии;
- сколько параллельных агентов моделируется;
- какие задержки и ошибки считаются неприемлемыми;
- какая совместимость обязательна: обычный Git CLI, hooks, partial clone или только внутренний API.
Не смешивайте в одном первом прогоне производительность и функциональные изменения. Если одновременно поменять формат хранения, протокол и модель блокировок, результат будет трудно интерпретировать.
Минимальная архитектура для сравнения
Для эксперимента удобно сравнить базовую и изменённую конфигурации. Базовая конфигурация — обычный Git-сервис с локальным или сетевым хранилищем bare-репозиториев. Изменённая конфигурация может менять только один слой: например, размещение объектов, кэширование чтений или планирование фоновой очистки.
Логически разделите систему на следующие компоненты:
- Git-шлюз. Принимает SSH или HTTP-запросы и ограничивает число одновременно выполняемых операций.
- Слой репозиториев. Отвечает за refs, блокировки и целостность конкретного репозитория.
- Хранилище объектов. Сохраняет содержимое объектов и обслуживает чтение pack-файлов.
- Кэш чтения. Ускоряет повторные обращения, но не должен становиться единственным источником данных.
- Фоновые задачи. Выполняют repack, maintenance и очистку только по согласованной политике.
- Наблюдаемость. Собирает задержки, ошибки, размеры очередей и состояние диска.
Важно измерять эти компоненты раздельно. Среднее время push само по себе не показывает, ожидал ли запрос блокировку refs, запись на диск или освобождение рабочего процесса.
Шаг 1. Подготовьте безопасный стенд
Используйте отдельную временную директорию и тестовые данные. Не запускайте эксперименты в рабочем репозитории и не подставляйте в скрипты реальные токены, URL или секреты.
set -eu
WORKDIR="${TMPDIR:-/tmp}/git-backend-experiment"
rm -rf -- "$WORKDIR"
mkdir -p -- "$WORKDIR"
cd -- "$WORKDIR"
git init --bare --quiet server.git
git clone --quiet "file://$WORKDIR/server.git" client.git
cd client.git
git config user.name "Experiment User"
git config user.email "experiment@example.invalid"
printf '%s\n' "baseline" > README.md
git add README.md
git commit --quiet -m "baseline"
git push --quiet origin HEAD
Команда выше создаёт локальный стенд. Домен example.invalid
намеренно не предназначен для доставки почты. В реальном окружении
замените transport на тот, который нужно проверить, но сохраните отдельные
учётные данные и тестовый репозиторий.
Шаг 2. Проверьте стоимость хранения объектов
Сначала создайте несколько типов истории: много маленьких коммитов, повторяющееся содержимое, крупные бинарные файлы и ветки, которые быстро удаляются. Это позволит увидеть, как система ведёт себя при разных коэффициентах дедупликации и объёме pack-файлов.
cd -- "$WORKDIR/client.git"
i=1
while [ "$i" -le 200 ]; do
printf 'generated line %s\n' "$i" >> generated.txt
git add generated.txt
git commit --quiet -m "generated $i"
i=$((i + 1))
done
git count-objects -vH
git rev-list --objects --all | wc -l
git fsck --full --no-progress
Снимите до и после каждого этапа: размер каталога репозитория, число loose objects, размер pack-файлов, время выполнения команды и ошибки. После упаковки сравните результат с исходным состоянием:
git gc --auto
git count-objects -vH
git verify-pack -v objects/pack/*.idx 2>/dev/null | tail -n 5 || true
git fsck --full --no-progress
Не трактуйте уменьшение размера как безусловный успех: упаковка может временно увеличить нагрузку на CPU, память и диск. Зафиксируйте также длительность фоновой операции и влияние на параллельные чтения.
Шаг 3. Измерьте параллелизм и блокировки
Для первого прогона используйте фиксированное число рабочих копий и одинаковые изменения. Важно измерять не только максимальное количество процессов, но и поведение при отказе: конфликте refs, занятом lock-файле или превышении лимита очереди.
cd -- "$WORKDIR"
n=1
while [ "$n" -le 8 ]; do
git clone --quiet "file://$WORKDIR/server.git" "worker-$n"
(
cd "worker-$n"
git config user.name "Worker $n"
git config user.email "worker-$n@example.invalid"
printf 'worker=%s\n' "$n" > "worker-$n.txt"
git add .
git commit --quiet -m "worker $n"
/usr/bin/time -f "worker=%s elapsed=%e status=%x" \
git push origin HEAD:refs/heads/experiment/"$n"
) &
n=$((n + 1))
done
wait
Этот сценарий проверяет независимые refs. Для проверки конфликтов намеренно направьте несколько workers в одну ветку, но держите такой прогон отдельным: конфликт refs — это другой класс поведения, а не просто более высокая параллельность.
На серверной стороне соберите p50, p95 и p99 задержки push, число успешных и отклонённых операций, время ожидания блокировок, загрузку CPU, I/O wait, свободное место и количество активных процессов.
Шаг 4. Проверьте пропускную способность
Пропускная способность должна быть выражена в операциях в секунду и в полезном объёме данных в секунду. Эти величины нельзя заменять одной средней скоростью: множество маленьких push и один большой push создают разные профили нагрузки.
Постройте матрицу сценариев:
| Сценарий | Что меняется | Что измерять |
|---|---|---|
| Мелкие коммиты | Число операций записи | p95 push, IOPS, refs lock |
| Большой fetch | Объём последовательного чтения | время, сеть, CPU распаковки |
| Повторный fetch | Доля данных из кэша | hit rate, задержка, нагрузка на origin |
| Фоновая упаковка | Конкуренция maintenance и клиентов | tail latency, память, ошибки |
Каждый сценарий повторите с одинаковым seed данных и одинаковым числом клиентов. Если генератор нагрузки меняется между прогонами, его параметры должны попасть в журнал эксперимента.
Шаг 5. Проверьте Git-совместимость
Производительное хранилище бесполезно, если существующие клиенты не могут безопасно с ним работать. Проверяйте совместимость отдельным набором приёмочных операций:
- clone нового репозитория;
- fetch существующих refs;
- push новой ветки;
- повторный push без изменений;
- отклонение не-fast-forward push;
- удаление временной ветки;
- чтение tag и commit с большим числом родителей;
- прерывание клиента во время передачи;
- повтор запроса после временной ошибки.
Результат фиксируйте как таблицу «операция — ожидаемый результат — фактический результат — причина отклонения». Ошибка совместимости не должна маскироваться под ошибку производительности.
Как интерпретировать результаты
Если p99 резко растёт при неизменном объёме данных, ищите блокировки, исчерпание процессов или очередь на диске. Если задержка растёт вместе с размером pack-файлов, проверяйте чтение, распаковку и кэш. Если ошибки появляются только во время maintenance, разделите расписание фоновых задач и клиентские операции либо введите ограничение на конкуренцию.
Для каждого кандидата на узкое место сформулируйте проверяемое изменение: например, ограничить число одновременных repack, вынести горячие чтения в кэш или разделить refs и объекты по разным ресурсам. Меняйте за прогон только один существенный фактор. После изменения повторяйте базовый сценарий, иначе сравнение будет недействительным.
Типовые ошибки
- Измерять только среднее. AI-агенты чувствительны к хвосту распределения, поэтому нужны p95 и p99.
- Считать clone единственной нагрузкой. Короткие push и операции с refs могут перегружать систему раньше больших чтений.
- Запускать gc без журнала. Фоновая упаковка меняет состояние хранилища и должна быть частью сценария.
- Тестировать только один репозиторий. В многопользовательской системе важны конкуренция между репозиториями и общий лимит ресурсов.
- Игнорировать отказоустойчивость. Повтор запроса после обрыва должен быть безопасным и предсказуемым.
- Смешивать несовместимость и медленную работу. Сначала подтвердите корректность результата, затем сравнивайте время.
Ограничения плана
Локальный стенд не показывает реальную сетевую задержку, распределённые блокировки, особенности файловой системы и поведение балансировщика. Синтетические коммиты также не воспроизводят все паттерны монорепозитория или бинарных артефактов. Поэтому результаты эксперимента следует считать сигналом для следующего этапа, а не доказательством готовности к промышленной нагрузке.
После локального прогона повторите только самые информативные сценарии в изолированном окружении, максимально близком к целевой инфраструктуре. Сохраните версии Git, конфигурацию, параметры генератора, сырые логи и контрольные суммы тестовых данных.
Итоговый чек-лист
- Определить профиль AI-нагрузки и критичные Git-операции.
- Зафиксировать базовую конфигурацию и безопасный набор тестовых данных.
- Раздельно измерить объекты, refs, дисковый I/O, CPU и сеть.
- Провести сценарии с независимыми refs и конфликтующими refs.
- Сравнить p50, p95, p99, throughput и частоту ошибок.
- Проверить clone, fetch, push, удаление refs и восстановление после сбоя.
- Изменять по одному архитектурному фактору за прогон.
- Сформулировать узкое место и следующий эксперимент в явном виде.
Дополнительные практические материалы собраны в разделе guides, а определения терминов — в глоссарии.