Это не кликбейт. Мы и правда сделали это! В Microsoft мы работаем с очень большим монорепозиторием, который между собой называем 1JS. Недавно мы достигли 1000 активных пользователей в месяц, около 2500 пакетов и ~20 млн строк кода! Последнее клонирование репозитория вернуло невероятные 178 ГБ.
По множеству причин, это попросту слишком большой размер, некоторые ребята из Европы попросту не могут успешно клонировать репозиторий из‑за его размера.
Вопрос в том, как это вообще произошло?!
Урок #1
Когда я впервые присоединился к репозиторию несколько лет назад, я заметил, что он растет. Когда я впервые его склонировал, его размер составлял около одного‑двух гигабайт, но через несколько месяцев он уже достигал около 4 гигабайт. Трудно было понять, почему именно это происходит.
Тогда я использовал инструмент git-sizer
, который поведал мне несколько деталей о некоторых больших блобах. Они возникают, когда кто‑то случайно добавляет бинарные файлы и мало, что можно сделать в таком случае, кроме ограничения размеров файлов — фичи Azure DevOps. В целом, после того, как файл попал в репозиторий, он в каком‑то смысле «застревает» в истории.
Также было показано предупреждение о неудаленных файлах изменений Beachball. Мы используем их как Changesets, достигая того же результата, как и при использовании semantic‑release, где мы говорим пакетам, как автоматически увеличивать их диапазоны версий в соответствии с semver.
В какие‑то моменты у нас было больше 40 тысяч таких файлов в одной папке, что приводило к созданию огромных tree‑объектов при каждом добавлении новых файлов в этой папке.
Итак, урок номер один, который мы извлекли, был...
Не храни тысячи файлов в одной папке
Чтобы облегчить ситуацию, мы сделали две вещи. Первая — pull request в beachball для вноса нескольких изменений в один файл вместо создания отдельного файла на каждый пакет.
Вторая — мы написали пайплайн, который периодически выполняется и при запуске автоматически чистит папку изменений для избежания ее разрастания.
Ура! Мы пофиксили раздувание git!
Урок #2
Наш флоу версионирования поддерживает зеркало main
под названием versioned
, хранящее актуальные версии пакетов, чтобы избежать конфликтов git в main
и иметь возможность точно видеть, какие коммиты относятся к какой semver версии, выпускаемой посредством пакетов NPM. (Это потребует отдельного поста, ну да ладно…)
Я заметил, что версионированная ветка становилась все сложнее и сложнее для клонирования из‑за ее размера. Но мы уже разобрались с проблемой файлов изменений и все, что происходило в ветке versioned
с точки зрения коммитов это добавление к файлам CHANGELOG.md
и CHANGELOG.json
.
Время шло, а репозиторий, хоть и понемногу, но разрастался. Но было сложно определить, был ли связан рост просто с масштабом или дело в чем‑то совершенно другом. Мы добавляли сотни тысяч строк кода и сотни разработчиков с 2021 года, так что можно было привести аргумент, что это просто естественный рост. Однако, когда мы поняли, что превзошли темпы роста одного из крупнейших монорепозиториев Microsoft — Office, мы осознали, что что‑то здесь явно не так!
Пришло время звать на подмогу…
Автор таких фич git как git shallow checkout, git sparse index и всяких других только вернулся в нашу организацию, поработав в Github и подарив эти фичи всему миру.
Он глянул и сразу понял, что что‑то не так с подобным темпом роста. Когда мы запуллили версионированные ветки, те самые, в которых меняется только CHANGELOG.md и CHANGELOG.json, мы получили 125ГБ дополнительных данных git?! НО КАК??
Итак, после очень глубокого погружения в git, оказалось, что некий старый код упаковки, добавленный Linux Torvalds (может, слышали про такого ?♂️) на самом деле проверял только последние 16 символов названия файла при подготовке к сжатию перед отправкой diff'ов. Для контекста, обычно git отправляет только diff'ы измененных файлов, но из‑за проблемы с упаковкой, git сравнивал файлы CHANGELOG.md из двух разных пакетов! Stolee хорошо объяснил это тут.
К примеру, если вы изменили repo/packages/foo/CHANGELOG.json
, когда git готовился пушить изменения, он генерировал diff относительно repo/packages/bar/CHANGELOG.json
! Это значит, что во многих случаях раз за разом отправлялся весь файл целиком, что могло иногда составлять десятки мегабайт, и вы догадываетесь, как в репозитории нашего размера это может стать проблемой.
Затем мы попробовали перепаковать наш репозиторий с увеличенным окном командой git repack -adf --window=250
, чтобы Git смог лучше сжать пак‑файлы и уменьшить размер репозитория. Это действительно значительно снизило размер репозитория, но мы можем лучше!
Этот PR https://github.com/git‑for‑windows/git/pull/5171?ref=jonathancreamer.com добавил новый способ упаковки репозитория на основе обхода путей git вместо стандартного обхода коммитов.
Результаты впечатляют…
Вчера я запустил новый git clone на своей машине, чтобы проверить новую версию git в форке git Microsoft (версия git version 2.47.0.vfs.0.2)…
И после выполнения новой команды git repack -adf --path-walk
…
Невероятно. С 178ГБ до 5. ?
Ещё одна новая опция конфигурации, которая будет добавлена, обеспечит генерацию нужных типов дельт во время выполнения команды git push
...
git config --global pack.usePathWalk true
Это позволит убедиться, что команда git push
использует правильное сжатие.
Любой разработчик на версии git 2.47.0.vfs.0.2 теперь может переупаковать склонированный локально репозиторий, а также использовать новый алгоритм обхода путей при git push
, чтобы остановить рост репозитория.
На Github переупаковка и сборка мусора git происходит периодически, но, опять же, используемый Github тип упаковки не будет корректно рассчитывать дельты файлов CHANGELOG.md и CHANGELOG.json или потенциально любых часто изменяющихся файлов с одинаковыми 16-ю последними символами в названии. К примеру, большие файлы со строками i18n и им подобные.
В Azure DevOps, который мы используем, пока вообще нет такой переупаковки. Поэтому мы также работаем над этим, чтобы уменьшить размер репозитория на серверной стороне.
Эти изменения также будут добавлены в апстрим git! Ура OSS.
Итоги
Если вы работаете в околобольшом монорепозитории и в нем есть файлы CHANGELOG.md или, на самом деле, любой файл с относительно длинным названием (>16 символов), который часто обновляется, вам может быть полезно последить за этой штукой с обходом путей.
Также можете попробовать новую команду git survey
, чтобы просмотреть разную эвристику, к примеру Top Files By Disk Size, Top Directories By Inflated Size, или Top Files By Inflated Size.
Это поможет вам понять, повлияет ли обход путей на размер вашего репозитория или нет.
В целом я очень впечатлен и воодушевлен нашей приверженностью созданию решений, которые помогают нам масштабировать репозитории в Microsoft, а также возможностью передавать эти решения всему миру.
Комментарии (9)
MyraJKee
01.11.2024 17:59Я тупой наверное, нифига не понял :(
Кроме того что удалось сильно уменьшить размеры репы
Moon_darker
01.11.2024 17:59Чтобы не сохранять каждую новую версию файла полностью, сохраняются только изменения относительно предыдущей версии - если это возможно и оправдано с точки зрения экономии пространства.
Но код, который должен был сравнивать изменения, вместо полного пути файла использовал только последние 16 символов. Пример, 3 файла:
addons/source/libraries/CHANGELOG.md
main/source/libraries/CHANGELOG.md
frontend/source/libraries/CHANGELOG.md
Вместо сравнения содержимого каждого отдельного файла со своей предыдущей версией, для сжатия каждый из них будет сравниваться только с каким-то одним из этих трёх (хз по какому принципу оно там сортируется, не суть)
Затем git будет видеть что чёт содержимое frontend/source/libraries/CHANGELOG.md уж очень отличается от того, с чем он сравнивал (например, addons/source/libraries/CHANGELOG.md) и каждый раз будет сохранять полную версию этого файла
petropavel
01.11.2024 17:59и их
CHANGELOG.json
был десятки мегабайт, а менялись всего несколько строк каждый раз. Ну, скажем, килобайт. То есть гит запоминал в десятки тысяч раз больше, чем надо.rukhi7
01.11.2024 17:59То есть гит запоминал в десятки тысяч раз больше, чем надо.
так наверно не запоминал, а запоминает? никто же не пофиксил эту проблему? И я так понимаю не собирается фиксить судя по вялой реакции на пост?
Получается что ГИТ который фактически стал отраслевым стандартом, является продуктом среднего качества, мягко говоря! Самое интересное что проблемы с таким высоко-востребованным продуктом никого особо не беспокоят и не трогают в общем то!
osmanpasha
01.11.2024 17:59Эмм, в статье же все написано
Этот PR https://github.com/git‑for‑windows/git/pull/5171?ref=jonathancreamer.com добавил новый способ упаковки репозитория на основе обхода путей git вместо стандартного обхода коммитов.
Эти изменения также будут добавлены в апстрим git! Ура OSS.
POPSuL
01.11.2024 17:59Странно что никто не пошутил про node_modules :)
А за раскрытие такой особенности гита -- спасибо, интересно!
ajijiadduh
что?
MrPizzly Автор
fixed. Потерялось слово "увеличивать"