- PVSM.RU - https://www.pvsm.ru -
Многие выбрали Git за его гибкость: в частности, модель веток и слияний позволяют эффективно децентрализовать разработку. В большинстве случаев эта гибкость является плюсом, однако некоторые сценарии поддержаны не так элегантно. Один из них — это использование Git для больших монолитных репозиториев — монорепозиториев. Эта статья исследует проблемы монорепозиториев в Git и предлагает способы их смягчения.
Скала Улуру [1] в Австралии как пример монолита — КДПВ, не более
Определения разнятся, но мы будем считать репозиторий монолитным при выполнении следующих условий:
.git
)Мне видится пара возможных сценариев:
foo.example.com
.
При таких условиях предпочтение может быть отдано единому репозиторию, поскольку он позволяет гораздо проще делать большие изменения и рефакторинги (к примеру, обновить все микросервисы до конкретной версии библиотеки).
У Facebook есть пример такого монорепозитория [2]:
С тысячами коммитов в неделю и сотнями тысяч файлов, главный репозиторий исходного когда Facebook громаден — во много раз больше,
чем даже ядро Linux, в котором, по состоянию на 2013 год, находилось 17 миллионов строк кода в 44 тысячах файлов.
При проведении тестов производительности в Facebook использовали тестовый репозиторий [3] со следующими параметрами:
.git
около 15 ГбС хранением несвязанных проектов в монорепозитории Git возникает много концептуальных проблем.
Во-первых, Git учитывает состояние всего дерева в каждом сделанном коммите. Это нормально для одного или нескольких связанных проектов, но становится неуклюжим для репозитория со многими несвязанными проектами. Проще говоря, на поддерево, существенное для разработчика, влияют коммиты в несвязанных частях дерева. Эта проблема ярко проявляется с большим числом коммитов в истории дерева. Поскольку верхушка ветки всё время меняется, для отправки изменений требуется частый merge
или rebase
.
Тег в Git — это именованный указатель на определённый коммит, который, в свою очередь, ссылается на целое дерево. Однако польза тегов уменьшается в контексте монорепозитория. Посудите сами: если вы работаете над веб-приложением, которое постоянно развёртывается из монорепозитория (Continuous Deployment), какое отношение релизный тег будет иметь к версионированному клиенту под iOS?
Наряду с этими концептуальными проблемами существует целый ряд аспектов производительности, влияющих на монорепозиторий.
Хранение несвязанных проектов в едином большом репозитории может оказаться хлопотным на уровне коммитов. С течением времени такая стратегия может привести к большому числу коммитов и значительному темпу роста (из описания Facebook — "тысячи коммитов в неделю"). Это становится особенно накладно, поскольку Git использует направленный ациклический граф (directed acyclic grap — DAG) для хранения истории проекта. При большом числе коммитов любая команда, обходящая граф, становится медленнее с ростом истории.
Примерами таких команд являются git log
(изучение истории репозитория) и git blame
(аннотирование изменений файла). При выполнении последней команды Git придётся обойти кучу коммитов, не имеющих отношение к исследуемому файлу, чтобы вычислить информацию о его изменениях. Кроме того, усложняется разрешение любых вопросов достижимости: например, достижим ли коммит A
из коммита B
. Добавьте сюда множество несвязанных модулей, находящихся в репозитории, и проблемы производительности усугубятся.
Большое число указателей — веток и тегов — в вашем монорепозитории влияют на производительность несколькми путями.
Анонсирование указателей содержит каждый указатель вашего монорепозитория. Поскольку анонсирование указателей — это первая фаза любой удалённой Git операции, под удар попадают такие команды как git clone
, git fetch
или git push
. При большом количестве указателей их производительность будет проседать. Увидеть анонсирование указателей можно с помощью команды git ls-remote
, передав ей в качестве аргумента URL репозитория. Например, такая команда выведет список всех указателей в репозитории ядра Linux:
git ls-remote git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Если указатели хранятся не в сжатом виде, перечисление веток будет работать медленно. После выполнения команды git gc
указатели будут упакованы в единый файл, и тогда перечисление даже 20.000 указателей станет быстрым (около 0.06 секунды).
Любая операция, которая требует обхода истории коммитов репозитория и учитывает каждый указатель (например, git branch --contains SHA1
) в монорепозитории будет работать медленно. К примеру, при 21.708 указателях поиск указателя, содержащего старый коммит (который достижим из почти всех указателей), занял на моём компьютере 146.44 секунды (время может отличаться в зависимости от настроек кеширования и параметров носителя информации, на котором хранится репозиторий).
Индекс (.git/index
) учитывает каждый файл в вашем репозитории. Git использует индекс для определения, изменился ли файл, выполняя stat(1)
для каждого файла и сравнивая информацию об изменении файла с информацией, содержащейся в индексе.
Поэтому количество файлов в репозитории оказывает влияние на производительность многих операций:
git status
может работать медленно, т.к. эта команда проверяет каджый файл, а индекс-файл будет большимgit commit
также может работать медленно, поскольку проверяет каждый файлЭти эффекты могут варьироваться в зависимости от настроек кешей и характеристик диска, а заметными становятся только при действительно большом количестве файлов, исчисляемом в десятках и сотнях тысяч штук.
Большие файлы в одном поддереве/проекте влияют на производительность всего репозитория. Например, большие медиа-файлы, добавленные в проект iOS-клиента в монорепозитории, будут клонироваться даже разработчикам, работающим над совершенно другими проектами.
Количество и размер файлов в сочетании с частотой их изменений наносят ещё больший удар по производительности:
./templates
так, чтобы она соответстовала указанной ветке, но при этом не изменит HEAD
, что приведёт к побочному эффекту: обновлённые файлы будут отмечены в индексе как изменённые:
`git checkout ref-28642-31335 -- templates`
git push
(сама сборка при этом происходит только в том случае, если она необходима).git upload-pack
, git gc
, требует значительных ресурсов.Как следствие описанных эффектов, монолитные репозитории — это испытание для любой системы управления Git-репозиториями, и Bitbucket не является ислючением. Ещё важнее то, что порождаемые монорепозиториями проблемы требуют решения как на стороне сервера, так и клиента.
Параметр | Влияние на сервер | Влияние на пользователя |
---|---|---|
Большие репозитории (много файлов, большие файлы или и то, и другое) | Память, CPU, IO, git clone нагружает на сеть, git gc медленный и ресурсоёмкий |
Клонирование занимает значительное время, — как у разработчиков, так и на CI |
Большое количество коммитов | — | git log и git blame работают медленно |
Большое количество указателей | Просмотр списка веток, анонсирование указателей занимают значительное время (git fetch , git clone , git push работают медленно) |
Страдает доступность |
Большое количество файлов | Коммиты на стороне сервера становятся долгими | git status и git commit работают медленно |
Большие файлы | См. "Большие репозитории" | git add для больших файлов, git push и git gc работают медленно |
Конечно, было бы здорово, если бы Git специально поддержал вариант использования с монолитными репозиториями. Хорошая новость для подавляющего большинства пользователей заключается в том, что на самом деле, действительно большие монолитные репозитории — это скорее исключение, чем правило, поэтому даже если эта статья оказалась интересной (на что хочется надеяться), она вряд ли относится к тем ситуациям, с которыми вы сталкивались.
Есть целый ряд методов снижения вышеописанных негативных эффектов, которые могут помочь в работе с большими репозиториями. Для репозиториев с большой историей или большими бинарными файлами мой коллега Никола Паолуччи [4] описал несколько обходных путей.
Если количество указателей в вашем репозитории исчисляется десятками тысяч, вам стоит попробовать удалить те указатели, которые стали ненужными. Граф коммитов сохраняет историю эволюции изменений, и поскольку коммиты слияния содержат ссылки на всех своих родителей, работу, которая велась в ветках, можно отследить даже если сами эти ветки уже не существуют. К тому же, коммит слияния зачастую содержит название ветки, что позволит восстановить эту информацию, если понадобится.
В процессе разработки, основанном на ветках [5], количество долгоживущих веток, которые следует сохранять, должно быть небольшим. Не бойтесь удалять кратковременные feature-ветки после того, как слили их в основную ветку. Рассмотрите возможность удаления всех веток, которые уже слиты в основную ветку (например, в master
или production
).
Если в вашем репозитории много файлов (их число достигает десятков и сотен тысяч штук), поможет быстрый локальный диск и достаточный объём памяти, которая может быть использована для кеширования. Эта область потребует более значительных изменений на клиентской стороне, подобных тем, которые Facebook реализовал для Mercurial [2].
Их подход заключается в использовании событий файловой системы для отслеживания изменённых файлов вместо итерирования по всем файлам в поисках таковых. Подобное решение, также с использованием демона, мониторящего файловую систему, обсуждалось и для Git [6], однако на данный момент так и не привело к результату.
Для проектов, которые содержат большие файлы, например, видео или графику, Git LFS [7] является одним из способов уменьшения их влияния на размер и общую производительность репозитория. Вместо того, чтобы хранить большие объекты в самом репозитории, Git LFS под тем же имененм хранит маленький файл-указатель на этот объект. Сам объект хранится в специальном хранилище больших файлов. Git LFS встраивается в операции push
, pull
, checkout
и fetch
, чтобы прозрачно обеспечить передачу и подстановку этих объектов в рабочую копию. Это означает, что вы можете работать с большими файлами так же, как обычно, при этом не раздувая ваш репозиторий.
Bitbucket Server 4.3 полностью поддерживает Git LFS v1.0+ [8], а кроме того, позволяет просматривать и сравнивать большие графические файлы, хранящиеся в LFS.
Мой коллега Стив Стритинг [9] активно участвует в разработке проекта LFS и не так давно написал о нём статью [10].
Наиболее радикальное решение — это разделение монорепозитория на меньшие, более сфокусированные репозитории. Попробуйте не отслеживать каждое изменения в едином репозитории, а идентифицировать границы компонентов, например, выделяя модули или компоненты, имеющие общий цикл выпуска версий. Хорошим признаком компонентов может быть использование тегов в репозитории и то, насколько они имеют смысл для других частей дерева исходного кода.
Хоть концепт монорепозитория и расходится с решениями, сделавшими Git чрезвычайно успешным и популярным, это не означает, что стóит отказываться от возможностей Git только потому, что ваш репозиторий монолитный: в большинстве случаев, для возникающих проблем есть работающие решения.
Автор: detouched
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/git/116349
Ссылки в тексте:
[1] Скала Улуру: https://ru.wikipedia.org/wiki/%D0%A3%D0%BB%D1%83%D1%80%D1%83
[2] пример такого монорепозитория: https://code.facebook.com/posts/218678814984400/scaling-mercurial-at-facebook/
[3] Facebook использовали тестовый репозиторий: http://permalink.gmane.org/gmane.comp.version-control.git/189776
[4] Никола Паолуччи: https://developer.atlassian.com/blog/authors/npaolucci/
[5] процессе разработки, основанном на ветках: http://blogs.atlassian.com/2013/11/the-essence-of-branch-based-workflows/
[6] обсуждалось и для Git: http://git.661346.n2.nabble.com/RFC-On-watchman-support-td7620988.html
[7] Git LFS: https://git-lfs.github.com/
[8] полностью поддерживает Git LFS v1.0+: https://confluence.atlassian.com/bitbucketserver/git-large-file-storage-794364846.html
[9] Стив Стритинг: https://developer.atlassian.com/blog/authors/sstreeting/
[10] написал о нём статью: https://developer.atlassian.com/blog/2015/10/contributing-to-git-lfs/
[11] @stefansaasen: http://twitter.com/stefansaasen
[12] Источник: https://habrahabr.ru/post/280358/
Нажмите здесь для печати.