Огромные репозитории: partial clone, FSMonitor, Scalar

Рейтинг: 77.2% · 13 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Огромные репозитории: partial clone, FSMonitor, Scalar

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar (вы здесь)
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Когда git clone идёт полчаса, а git status - три секунды

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

Большой репозиторий git - это не обязательно плохая архитектура. Linux kernel, Chromium, Windows, любой крупный монорепозиторий - там по определению гигабайты и миллионы объектов. Вопрос не в том, как сделать репо маленьким (часто это невозможно или нежелательно), а в том, как git ускорить на таком масштабе, не теряя удобство. К 2026 году в самом git собран полный набор инструментов ровно под эту задачу, и их даже не надо включать по одному - есть команда, которая включает всё разом. Разберём механику каждого слоя, потому что без понимания механики ты не поймёшь, какой инструмент решает какую конкретную боль.

Изображение

Откуда берётся тормоз: три источника медленности

Чтобы лечить, надо понять диагноз. Медленность большого репо складывается из трёх независимых источников, и лечатся они тремя разными инструментами.

Источник 1 - объём данных при клоне и fetch. Полный клон тащит ВСЮ историю всех файлов: каждую версию каждого blob за все годы. Если файл переписывали тысячу раз, ты качаешь тысячу его состояний, хотя тебе нужно одно - текущее. Это лечит partial clone: качаем дерево коммитов и каталогов, а содержимое файлов (blob-объекты) подтягиваем по требованию.

Источник 2 - размер рабочего каталога. Даже если данные скачаны, два миллиона файлов на диске - это два миллиона записей, которые git checkout должен материализовать, а git status - обойти. Лечит sparse-checkout плюс sparse-index: на диск кладём только нужные тебе подкаталоги, а индекс не раздуваем до полного дерева.

Источник 3 - сканирование файловой системы. git status на каждый вызов обходит рабочее дерево и спрашивает у ОС mtime каждого файла, чтобы понять, что изменилось. Миллион stat-вызовов - вот твои три секунды. Лечит FSMonitor: фоновый демон слушает события файловой системы и говорит git, какие файлы вообще трогали с прошлого раза.

Поверх этих трёх - ускорители чтения метаданных: commit-graph (быстрый обход истории) и multi-pack-index (быстрый поиск объекта среди многих пакфайлов). Их обслуживает фоновое git maintenance. Теперь по каждому слою вглубь.

Partial clone: blob по требованию и git backfill

Partial clone стабилен с git 2.27, это давно не экспериментальная новинка. Идея: при клоне применяем фильтр, который говорит серверу не присылать определённый класс объектов. Самый частый фильтр - blobless:

Код: Выделить всё

git clone --filter=blob:none https://git.example.com/monorepo.git
Сервер присылает все коммиты и все tree-объекты (структуру каталогов), но НЕ присылает blob-объекты - содержимое файлов. Клон, который тащил 8 ГБ, скачивает условные 300 МБ за секунды. Когда ты делаешь checkout и git нужно реальное содержимое файла, он на лету делает отдельный запрос к серверу и докачивает недостающие blob. Это называется lazy fetch - ленивая дозагрузка.

Посмотрим, что записалось в конфиг после такого клона:

Код: Выделить всё

$ git config --get-regexp 'remote|partial'
remote.origin.url https://git.example.com/monorepo.git
remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
remote.origin.partialclonefilter blob:none
remote.origin.promisor true
Ключевое слово - promisor. Origin становится promisor-remote: репозиторий, который обещает по запросу выдать любой недостающий объект. Git хранит в себе "обещания" (promisor-объекты) и при обращении к отсутствующему blob знает, у кого его спросить.

Есть тонкость, на которой спотыкаются. Команды, которые трогают много старых версий файлов, спровоцируют шквал мелких lazy fetch и будут тормозить хуже, чем на полном клоне. Классика - git log -p по всей истории файла или git blame на древнем файле: git начнёт по одному докачивать сотни исторических blob. Поэтому если тебе предстоит тяжёлый офлайн-анализ истории, blobless-клон не лучший выбор.

Чтобы не страдать от россыпи мелких докачек, с git 2.49/2.50 есть git backfill - управляемая массовая дозагрузка недостающих blob:

Код: Выделить всё

$ git backfill
Backfilling blobs: 100% (48213/48213), done.
$ git backfill --sparse
Без флагов backfill дотягивает blob для текущего состояния пачкой, в эффективной батч-передаче, а не по одному. С флагом --sparse - дотягивает только в пределах твоего sparse-checkout, что важно для следующего слоя. Логика простая: partial clone даёт быстрый старт, а backfill потом в фоне или по команде заполняет то, что тебе реально нужно, без тысячи round-trip к серверу.

Sparse-checkout и sparse-index: только нужные пути

Допустим, монорепо содержит сто сервисов в каталогах services/, а ты работаешь над тремя. Зачем тебе на диске остальные девяносто семь? Sparse-checkout говорит git материализовать на диск только заданные пути:

Код: Выделить всё

$ git sparse-checkout init --cone
$ git sparse-checkout set services/auth services/billing libs/common
$ git sparse-checkout list
libs/common
services/auth
services/billing
Режим --cone (конусный) - это важно. Он работает по каталогам, а не по произвольным glob-шаблонам, и поэтому быстрый: git проверяет принадлежность пути конусу за константное время, без перебора паттернов. Не-cone режим с произвольными масками работает, но на большом дереве тормозит сам по себе, так что для монорепо всегда --cone.

После set на диске останутся только три указанных подкаталога плюс файлы в корне. Остальные исчезнут из рабочего дерева - но НЕ из истории, они всё так же в репозитории. Вернуть можно в любой момент новым set.

Но тут была вторая половина проблемы. Даже когда на диске три каталога, индекс (.git/index) по умолчанию всё равно содержит записи на ВСЕ два миллиона файлов - просто помеченные как "не в рабочем дереве". И git status снова обходит этот раздутый индекс. Решение - sparse-index:

Код: Выделить всё

$ git sparse-checkout init --cone --sparse-index
Sparse-index хранит в индексе не отдельные файлы вне конуса, а ОДНУ запись на целый каталог - так называемую sparse-directory entry. Вместо миллиона строк "services/foo/.../file" в индексе появляется одна строка "services/foo/" с типом дерева. Индекс схлопывается с миллиона записей до тысяч. Git автоматически разворачивает нужный поддерево, только когда команда реально в него лезет. Это даёт монорепозиторий производительность совсем другого порядка: git status, git add, git commit перестают зависеть от полного размера репо и начинают зависеть только от размера твоего конуса.

Проверить, что индекс действительно разреженный, можно так:

Код: Выделить всё

$ git ls-files --sparse | grep '/$' | head -3
services/api/
services/cache/
services/cdn/
Строки, заканчивающиеся на слеш - это и есть sparse-directory entries, целые каталоги одной записью. Если их видно, sparse-index работает.

FSMonitor: мгновенный git status

Третий источник тормозов - сканирование диска. Даже с sparse-checkout, если в твоём конусе пятьдесят тысяч файлов, git status честно опросит ОС про каждый. FSMonitor (file system monitor) переворачивает логику: вместо "опросить всё" - "узнать у демона, что менялось".

Встроенный fsmonitor стабилен и больше не экспериментальный, поднимается одной настройкой:

Код: Выделить всё

$ git config core.fsmonitor true
$ git config core.untrackedCache true
При первом git status git запускает фоновый демон git-fsmonitor--daemon, который подписывается на события файловой системы ОС (на macOS - FSEvents, на Windows - ReadDirectoryChangesW, на Linux - fanotify). Демон ведёт список путей, изменившихся с момента последнего запроса. Теперь git status не сканирует диск - он спрашивает у демона "что трогали?" и проверяет только эти файлы.

Разница на большом дереве драматическая. Реальный замер на монорепо с парой миллионов файлов:

Код: Выделить всё

# без fsmonitor
$ time git status
real  0m4.812s

# с включённым fsmonitor (демон прогрет)
$ time git status
real  0m0.231s
С четырёх с лишним секунд до доли секунды. core.untrackedCache добавляет к этому кеш списка неотслеживаемых файлов, чтобы git не пересканировал каталоги в поисках новых файлов. Проверить, что демон жив:

Код: Выделить всё

$ git fsmonitor--daemon status
fsmonitor-daemon is watching '/Users/you/monorepo'
Грабли FSMonitor: демон привязан к рабочему дереву. Если ты массово трогаешь файлы вне git (распаковываешь архив, переключаешь ветку сторонним инструментом, чистишь node_modules) - демон всё это исправно отследит, это нормально. А вот сетевые и некоторые виртуальные ФС события могут не отдавать; на экзотических маунтах fsmonitor стоит проверять. На обычных локальных дисках macOS, Windows и Linux всё работает. Исторически был и внешний watchman от Facebook (core.fsmonitor можно указать как путь к хуку) - но встроенный демон в 2026 проще и не требует ставить ничего лишнего.

Scalar: всё это одной командой (и это часть самого git)

Главный миф, который надо убить сразу: scalar - это НЕ внешняя обёртка от Microsoft, которую надо где-то скачивать. Scalar встроен в сам git с версии 2.38. Ты уже его установил вместе с git. Проверь:

Код: Выделить всё

$ git scalar version
$ which scalar
/usr/local/bin/scalar
Microsoft действительно разработал scalar и обкатал на репозитории Windows, но затем код подняли в апстрим (upstream), и теперь это полноправная команда git scalar. Что она делает: берёт все четыре слоя, которые мы настраивали руками, и включает их разом, с проверенными дефолтами.

Код: Выделить всё

$ git scalar clone https://git.example.com/monorepo.git
Один вызов scalar clone автоматически:
  • делает partial clone с --filter=blob:none - быстрый старт без скачивания всех blob;
  • включает sparse-checkout в cone-режиме и sparse-index - на диск только корень, дальше добавляешь пути сам;
  • включает core.fsmonitor - мгновенный status с первого дня;
  • настраивает commit-graph и multi-pack-index для быстрого чтения метаданных;
  • регистрирует репо в фоновом git maintenance, чтобы обслуживание шло само.
После клона ты в каталоге src/ задаёшь свой конус обычным git sparse-checkout set и работаешь. Если у тебя УЖЕ есть склонированный большой репо и ты хочешь натянуть на него весь этот набор, не переклонируя - есть register:

Код: Выделить всё

$ cd existing-monorepo
$ git scalar register
$ git scalar list
/Users/you/existing-monorepo/src
register не трогает данные, он просто включает на существующем репо тот же набор оптимизаций и ставит его на фоновое обслуживание. Это, по сути, способ одной командой применить лучшие практики, не запоминая десяток config-ключей.

Maintenance: фоновое обслуживание вместо ручного gc

Последний кусок мозаики - чтобы быстрые чтения оставались быстрыми, метаданные надо периодически переупаковывать. Раньше это был ручной git gc. В 2026 правильный путь - git maintenance, фоновое инкрементальное обслуживание:

Код: Выделить всё

$ git maintenance start
Эта команда регистрирует репо в системном планировщике (launchd на macOS, cron/systemd на Linux, Task Scheduler на Windows) и запускает периодические задачи: обновление commit-graph, переупаковку в multi-pack-index, дозагрузку prefetch с remote, упаковку cruft packs. С git 2.54 стратегия ручного обслуживания по умолчанию - geometric: пакфайлы держатся в геометрической прогрессии по размеру, что даёт мало операций переупаковки и стабильно быстрый поиск объектов. Старый git gc остаётся как легаси-путь, но для больших репо ориентируйся на maintenance. scalar clone и scalar register, как сказано выше, вызывают maintenance start за тебя - ещё одна причина не настраивать всё руками.

Зачем это в контексте скорости: commit-graph превращает обход истории (git log, git merge-base, проверки достижимости) из чтения тысяч коммит-объектов в чтение одного компактного файла с предпосчитанными generation numbers. Multi-pack-index даёт единый индекс поверх всех пакфайлов, чтобы поиск объекта не перебирал десятки .idx-файлов. Без фонового обслуживания эти структуры устаревают, и скорость деградирует.

Когда это нужно, а когда из пушки по воробьям

Честный разбор - потому что включать sparse-index на репо из пятисот файлов бессмысленно и только добавит сложности.
  • Репо меньше пары сотен мегабайт и десятков тысяч файлов - не трогай ничего, git и так быстрый. FSMonitor и sparse-index тут дадут ноль выигрыша, а граблей добавят.
  • git status стал заметно медленным (секунда и больше), но репо не гигантское - включи только core.fsmonitor. Это самая дешёвая и безопасная оптимизация, эффект мгновенный.
  • Долгий клон и не нужна вся история файлов - --filter=blob:none, при необходимости git backfill для нужных путей.
  • Большой монорепо, работаешь над частью - полный набор через git scalar clone. Не собирай его руками по кусочкам, scalar сделает это правильнее.
  • CI-раннер, которому нужен только текущий снимок - сюда же blobless или treeless partial clone плюс sparse-checkout на нужный сервис; история ему не нужна вообще.
Главный принцип: не лечи то, что не болит. Сначала замерь (time git status, time git clone), пойми, какой из трёх источников тормозит, и включай ровно нужный слой. scalar хорош тем, что для настоящего монстро-репо он закрывает все слои сразу и проверенными дефолтами, но на среднем проекте это избыточно.

Мини-лаба: пощупать каждый слой руками

Возьми любой публичный крупный репозиторий (подойдёт зеркало git, llvm, любой ваш рабочий монорепо).
  • Шаг 1. Замерь полный клон по времени и размеру: git clone URL full && du -sh full/.git.
  • Шаг 2. Сделай blobless-клон: git clone --filter=blob:none URL lazy. Сравни время и du -sh lazy/.git. Загляни в git config remote.origin.partialclonefilter - убедись, что там blob:none.
  • Шаг 3. В каталоге lazy включи sparse-index и оставь один подкаталог: git sparse-checkout init --cone --sparse-index, потом git sparse-checkout set ПУТЬ. Проверь git ls-files --sparse | grep '/$' - увидишь sparse-directory entries.
  • Шаг 4. Включи fsmonitor: git config core.fsmonitor true. Сделай git status (поднимет демон), затем time git status дважды и сравни со временем без демона.
  • Шаг 5. Дотяни blob для конуса: git backfill --sparse. Убедись, что после этого checkout по конусу не делает мелких докачек.
  • Шаг 6. Сравни всё это с git scalar clone URL scalar-clone - и посмотри git config -l в склонированном репо: найди partialclonefilter, core.fsmonitor, sparse-checkout, maintenance. Убедись, что scalar включил то же, что ты собирал руками.
Контрольные вопросы
  • 1. Чем blobless partial clone (--filter=blob:none) отличается от sparse-checkout по тому, ЧТО именно экономится - данные на скачивании или файлы на диске? Могут ли они работать вместе?
  • 2. Почему sparse-checkout без sparse-index не ускоряет git status на монорепо, а с sparse-index ускоряет? Что меняется в .git/index?
  • 3. За счёт чего FSMonitor превращает git status из секунд в доли секунды - что именно перестаёт делать git?
  • 4. Scalar - это внешний инструмент Microsoft, который надо отдельно ставить, или часть git? С какой версии и что конкретно включает scalar clone?
Итог

Боль большого репо складывается из трёх независимых источников: объём при клоне, размер рабочего дерева, сканирование ФС. Им соответствуют partial clone с git backfill, sparse-checkout с sparse-index и FSMonitor; сверху - commit-graph, multi-pack-index и фоновое git maintenance. Каждый слой можно включить отдельно по диагнозу, а можно одной командой git scalar clone или git scalar register - и помни: scalar встроен в git с 2.38, это не внешняя обёртка. Не оптимизируй вслепую: замерь, найди источник, включи нужный слой. На настоящем монстре эффект - с минут до секунд и с секунд до миллисекунд.
👍5 ❤️2 🔥1 😄 🤔4
Аватара пользователя
max2024
Сообщения: 1
Зарегистрирован: 07 июн 2026, 10:37

Re: Огромные репозитории: partial clone, FSMonitor, Scalar

Сообщение max2024 »

Воу, не знал что scalar уже внутри git. Всю жизнь думал что это отдельная утилита от мелкомягких, которую надо ставить руками. Сейчас сделал git scalar version - реально есть из коробки.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
lexdog
Сообщения: 1
Зарегистрирован: 02 июн 2026, 14:37

Re: Огромные репозитории: partial clone, FSMonitor, Scalar

Сообщение lexdog »

Включил один core.fsmonitor=true на нашем фронтовом монорепо и git status с 3 секунд упал до мгновенного. Sparse-index пока не трогал, файлов не настолько много. Спасибо за совет не оптимизировать вслепую, реально сэкономил время.
👍2 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
От SHA-1 к SHA-256: переход на новый хеш
Следующая глава →
Git в CI/CD: shallow, кеш, merge_group и безопасность

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Git LFS: большие файлы в репозиторииgit clone: клонирование репозиторияМонорепозиторий и большие репозитории Git

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость