Главная » Хабрахабр » Монорепозитории: пожалуйста, не надо (часть 2)

Монорепозитории: пожалуйста, не надо (часть 2)

Всем привет!

В первой части мы обсуждали перевод статьи уважаемого инженера из Lyft (и ранее Twitter) о том, какие есть недостатки у монорепозиториев и почему они нивелируют почти все достоинства этого подхода. Итак, новая порция обещанного холивара про монорепозитории. Но, как и обещал, чтобы поставить точку в этом обсуждении, я бы хотел озвучить еще несколько моментов, на мой взгляд даже более важных и более практических.
Расскажу чуть-чуть о себе — я работал и в маленьких проектах, и в относительно больших, использовал полирепозитории в проекте с более 100 микросервисов (и SLA 99,999%). Лично я во многом согласен с доводами, приведенными в оригинальной статье. Не работал в Google, Facebook, Twitter, т.е. В данный момент занимаюсь переводом небольшого монорепозитория (на самом деле нет, всего лишь фронт js + бэкенд java) с maven на bazel. не имел удовольствия использовать правильно настроенный и обвешанный тулингом монорепозиторий.

Комментарии к переводу оригинальной статьи показали, что многие считают, что монорепозиторий, это когда все 5 разработчиков компании работают над одним репозиторием и хранят в нем фронтэнд и бэкенд вместе. Итак, для начала, что же такое монорепозиторий? Монорепозиторий, это способ хранения всех проектов компании, библиотек, инструментов для сборки, плагинов для IDE, скриптов деплоя и всего прочего в одном большом репозитории. Конечно же, это не так. Подробности здесь trunkbaseddevelopment.com.

Это тоже монорепозиторий, только маленький.
Естественно, в оригинальной статье говорится о том, что все описанные проблемы начинают проявляться на определенном масштабе. Как же тогда называется подход, когда компания небольшая, и у нее просто нет такого количества проектов, модулей, компонентов? Поэтому те, кто пишет, что их монорепозиторий на 1,5 землекопа отлично работает, конечно же абсолютно правы.

Складывая весь код в одну кучку, на первых порах вы получите только одни преимущества, т.к. Итак, первый факт, который хотелось бы зафиксировать: монорепозиторий — это отличное начало для вашего нового проекта. поддержка нескольких репозиториев безусловно добавит немного накладных расходов.

А проблема, как было отмечено в оригинальной статье, начинается на определенном масштабе. В чем же тогда проблема? И главное, не упустить момент, когда такой масштаб уже наступил.

Т.е. Поэтому я склонен утверждать, что по сути проблемы, которые возникают, это не проблемы самого подхода «сложи весь свой код в одну кучку», а это проблемы просто больших репозиториев исходных кодов. если предположить, что вы использовали полирепозитории для разных сервисов/компонентов, и один из этих сервисов стал таким большим (насколько большим, обсудим чуть ниже), то вы скорее всего получите ровно те же самые проблемы, только еще и без преимуществ монорепозиториев (если они, конечно, есть).

Если ваш проект имеет терабайты кода, но при этом с ним работает 1-2 человека, то скорее всего, они почти не заметят проблем (ну или по крайней мере, будет проще ничего не предпринимать, даже если заметят 🙂 Итак, насколько же большим должен быть репозиторий, чтобы начать считаться проблемным?
Определенно существует 2 показателя, от которых это зависит — количество кода и количество разработчиков, работающих с этим кодом.

Конечно, это субъективный показатель, скорее всего ваши разработчики начнут жаловаться, что их что-то не устраивает. Как же определить, что уже пора задумываться над тем, как оздоровить свой репозиторий? Приведу некоторые цифры от себя лично: если клонирование вашего репозитория занимает больше 10 минут, если сборка проекта занимает более 20-30 минут, если количество разработчиков превышает 50 и так далее. Но проблема в том, что может быть уже поздно что-то менять.

Интересный факт из личной практики:

Разработка велась в фича-бранчах, а мердж происходил перед самым фича-фризом. я работал над довольно большим монолитом в команде из примерно 50 разработчиков, разделенных на несколько небольших команд. Однажды я потратил на мердж нашей командной ветки 3 дня, после того как передо мной замерджились 6 других команд.

Теперь давайте пройдемся по списку тех проблем, которые возникают в больших репозиториях (часть из них была затронута в оригинальной статье, часть нет).

1) Время скачивания репозитория

С одной стороны, можно сказать, что это разовая операция, которую разработчик выполняет при начальной настройке своей рабочей станции. Лично у меня часто бывают ситуации, когда хочется склонировать проект в соседнюю папку, поковыряться в нем, а затем удалить. Однако, если клонирование занимает больше 10-20 минут, это сделать будет уже не так комфортно.

И вот тут вы начинаете придумывать, как же сэкономить это время, ведь если каждая сборка будет занимать на 10-20 минут дольше, а результат сборки появляться на 10-20 минут позже, это не устроит никого. Но кроме того, не стоит забывать, что перед сборкой проекта на CI-сервере нужно клонировать репозиторий на каждого билд-агента. Так репозиторий начинает появляться в образах виртуальных машин, из которых разворачиваются агенты, появляется дополнительная сложность и дополнительные расходы на поддержку этого решения.

2) Время сборки

Это довольно очевидный пункт, который обсуждался многократно. По сути, если у вас много исходных кодов, то сборка в любом случае будет занимать немалое время. Знакомая ситуация, когда после изменения одной строчки кода приходится ждать по полчаса, пока изменения пересобираются и тестируются. По сути выход здесь только один — использовать систему сборки, построенную вокруг кэширования результатов и инкрементальной сборки.

Т.е. Вариантов здесь не так уж и много — несмотря на то, что в тот же самый gradle были добавлены возможности кэширования (к сожалению, не использовал на практике), практической пользы они не приносят из-за того, что традиционные системы сборки не обладают повторяемостью результатов (reproducible builds). Поэтому остается только вариант использовать Bazel/Buck/Pants и иже с ними. из-за сайд-эффектов предыдущей сборки все равно в какой-то момент необходимо будет вызвать очистку кэшей (стандартный подход maven clean build). Почему это не очень хорошо, обсудим чуть ниже.

3) Индексирование IDE

Мой текущий проект индексируется в Intellij IDEA от 30 до 40 минут. А ваш? Конечно можно открыть только часть проекта или исключить из индексирования все ненужные модули, но… Проблема в том, что переиндексирование происходит при каждом переключении с одной ветки на другую. Вот почему я люблю клонировать проект в соседнюю директорию. Некоторые приходят к тому, что начинают кэшировать кэш IDE 🙂

4) Логи сборки

Какой CI-сервер вы используете? Предоставляет ли он удобный интерфейс для просмотра и навигации в логах сборки размером в несколько Гигабайт? К сожалению, мой нет 🙁

5) Сломанные тесты

Что происходит, если кто-то смог запушить сломанные тесты/некомпилящийся код в мастер? Вы конечно скажете, что ваш CI не позволяет делать такого. А что насчет нестабильных тестов, которые проходят у автора, и ни у кого кроме? А теперь представьте, что этот код растекся на машины 300 разработчиков, и ни один из них не может собрать проект? Что делать в такой ситуации? Ждать, когда автор заметит и исправит? Исправлять за него? Откатывать изменения? Конечно в идеале, стоит коммитить только хороший код, и писать сразу без багов. Тогда такой проблемы возникать не будет.
(для тех, кто в танке и не понял намеков, речь о том, что негативный эффект, если это происходит в репозитории с 10 разработчиками и в репозитории с 300 будет немного разный)

6) Merge bot

Слышали когда-нибудь о такой штуке? Знаете, зачем она нужна? Будете смеяться, но это еще один инструмент, который не должен был бы существовать 🙂 Вот представьте, что время сборки вашего проекта составляет 30 минут. И у вас над проектом работает 100 разработчиков. Предположим, что каждый из них пушит по 1 коммиту в день. Теперь представьте честный CI, который позволяет мерджить изменения в мастер только после того, как они были применены к самому свежему коммиту из мастера (rebase).

Правильный ответ, 50. Внимание, вопрос: сколько времени должно быть в сутках, чтобы такой честный CI-сервер смог смерджить изменения от всех разработчиков? Ну или представьте, как вы только что отребейзили свой коммит на самый последний коммит в мастер, запустили сборку, а когда она завершилась, мастер уже уехал на 20 коммитов вперед. Кто ответил верно, может взять с полки пряник. Все сначала?

Очень удобная штука. Так вот merge bot или merge queue — это такой сервис, который автоматизирует процесс ребейза всех мердж реквестов на свежий мастер, прогон тестов и непосредственно сам мердж, а также может еще и объединять коммиты в батчи и тестировать их вместе. (обещаю написать про это подробнее в будущем) Смотрите mergify.io, k8s test-infra Prow от Google, bors-ng и др.

Теперь к менее техническим проблемам:

7) Использование единого build tool

Честно говоря, для меня до сих пор загадка, зачем собирать весь монорепозиторий с помощью одной общей системы сборки. Почему бы не собирать javascript Yarn'ом, java — gradle'ом, Scala — sbt и т.д.? Если кто-то знает ответ на этот вопрос (не догадывается или предполагает, а именно знает) напишите в комментариях.

Но все же понимают, что любая универсальная вещь заведомо хуже, чем специализированная, т.к. Конечно, это кажется очевидным, что использование одной системы сборки лучше, чем нескольких разных. Но что еще хуже, разные языки программирования могут иметь разные парадигмы в плане сборки, управления зависимостями и т.д., которые будет очень трудно завернуть в одну общую обертку. она скорее всего имеет только подмножество функций всех специализированных. Стоит задуматься. Не хочу вдаваться в детали, приведу один пример про bazel (подробностей ждите в отдельной статье) — мы нашли 5 независимых имплементаций правил сборки javascript для bazel от 5 разных компаний на GitHub, наряду с официальной от Google.

8) Общие подходы

В ответ на оригинальную статью CTO из Chef написал свой ответ Monorepo: please do!. В своем ответе он утверждает, что «главное в монорепо, это то, что он заставляет разговаривать и делает недостатки видимыми». Он имеет в виду, что когда вы захотите изменить свой API, то вы вынуждены будете найти все его использования и обсудить свои изменения с мейнтейнерами этих кусков кода.

Понятно, что это очень сильно зависит от инженерной культуры в команде, но я вижу в этом подходе сплошные минусы. Так вот мой опыт ровно противоположный. И вот вы решили по какой-то причине, решая схожую проблему, использовать немного другой способ, возможно более современный. Представьте, что у вас используется некий подход, который верой и правдой служил вам на протяжении какого-то времени. Какова вероятность того, что добавление нового подхода пройдет ревью?

Обычно на этом энтузиазм «инноватора» заканчивается. В своем недавнем прошлом я несколько раз получал комментарии типа «у нас уже есть проверенный путь, используй его» и «если хочешь внедрить новый подход, обнови код во всех 120 местах, где используется старый подход и получи аппрув от всех команд, которые ответственны за эти куски кода».

В полирепозитории — нисколько. А сколько, по-вашему, будет стоить написание нового сервиса на новом языке программирования? А теперь то же самое в монорепозитории? Создаешь новый репозиторий и пишешь, да еще и берешь самую подходящую системы сборки.

По моему субъективному мнению, монорепозиторий скорее препятствует этому. Я прекрасно понимаю, что «стандартизация, переиспользование, совместное владение кодом», но проект должен развиваться.

9) Open source

Недавно меня спросили: "а есть ли open source инструменты для монорепозиториев?" Я ответил: «Проблема в том, что инструменты для монорепозиториев, как ни странно, разрабатываются внутри самого монорепозитория. Поэтому выложить их в open source оказывается довольно затруднительно!»

Google разрабатывает его в своем внутреннем репозитории, а затем «выплескивает» части от него в Github с потерей истории коммитов, без возможности отправить pull request и так далее. Для примера посмотрите на проект на Github с плагином для bazel для Intellij IDEA. Кстати этот факт упомянут в оригинальной статье, что монорепозитории мешают выкладыванию в open-source и созданию коммьюнити вокруг проекта. Я не считаю это open source (вот пример моего небольшого PR, который был закрыт, вместо мерджа, а затем изменения появились в следующей версии). Думаю, многие не придали особого значения этому аргументу.

Альтернативы

Что ж, если говорить о том, что же делать, чтобы избежать всех этих проблем? Совет ровно один — стремитесь иметь репозиторий как можно меньшего размера.
А при чем же здесь монорепозиторий? А ровно при том, что этот подход лишает вас возможности иметь маленькие, легкие и независимые репозитории.

Я вижу ровно 1: невозможность отслеживать, кто является потребителем твоего API. Какие недостатки есть у подхода полирепозиториев? (Кстати, как думаете, этот подход кто-нибудь использует в монорепозиториях?) Эту проблему, к сожалению, необходимо решать либо организационными средствами, либо пытаться использовать инструменты для браузинга кода, которые поддерживают независимые репозитории (например, https://sourcegraph.com/). Особенно это касается подхода в микросервисах «share nothing», при котором код не шарится между микросервисами.

Ответ на это очень простой: «не надо путать проблемы подхода с неправильной декомпозицией». А что насчет комментариев типа «мы пробовали полирепозитории, но потом нам пришлось постоянно имплементить фичи сразу в нескольких репозиториях, что было утомительно, и мы слили все в один котел»? В мою бытность использования полирепозиториев мы прекрасно складывали семейство тесно связанных микросервисов в один репозиторий. Никто не утверждает, что в репозитории должен лежать ровно один микросервис и все. Самое главное, о чем нужно думать в плане декомпозиции, это то, как эти сервисы будут деплоиться. Тем не менее, с учетом того, что сервисов было более 100, таких репозиториев было больше 20.

Ведь монорепозитории позволяют не иметь версий и деплойть все из одного коммита! А как же аргумент про версии? Даже в такой старой штуке как maven есть maven-version-plugin, который позволяет зарелизить версию всего в один клик. Во-первых, версионирование — это самая простая из всех озвученных здесь проблем. Если да, то у вас уже есть версии, и вы никуда от этого не денетесь! А во-вторых, и в-главных, есть ли у вашей компании мобильные приложения?

На самом деле, нет. Ну есть же еще самый главный аргумент в поддержку монорепозиториев — он позволяет сделать рефакторинг по всей кодовой базе в один коммит! Вы должны всегда иметь в виду, что какое-то продолжительное время (продолжительность зависит от того, как у вас выстроен процесс) вы будете иметь 2 версии одного и того же сервиса параллельно. Как было упомянуто в оригинальной статье, из-за ограничений, которые накладывает деплой. Это приводит к тому, что нельзя проводить глобальные рефакторинги, затрагивающие интерфейсы взаимодействия, в один коммит даже в монорепозитории. Например, на моем прошлом проекте у нас система находилась в таком состоянии несколько часов при каждом деплое.

Вместо заключения:

Итак, тем уважаемым и немногочисленным коллегам, которые работают в Google, Facebook и т.д. и придут сюда защищать свои монорепозитории, хочется сказать: «Не волнуйтесь, вы все делаете правильно, наслаждайтесь своим тулингом, на который было потрачено сотни тысяч или миллионов человекочасов. Они уже потрачены, так что если вы не будете использовать, то и никто не будет».

А всем остальным: «Вы не Google, не используйте монорепозитории!»

Остальным даже не стоит пытаться». П.С. как заметил многоуважаемый Bobuk в подкасте радио-Т при обсуждении оригинальной статьи: «в мире есть ~20 компаний, которые умеют в монорепозиторий.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Как изучение критической уязвимости DHCP в Windows 10 привело к обнаружению еще двух ошибок безопасности

Изображение: Unsplash А в некоторых случаях таких новых уязвимостей оказывается больше одной. Как было описано в предыдущей статье про CVE-2019-0726, иногда поиск деталей об уже известной уязвимости приводит к обнаружению новой уязвимости. Как всегда происходит при поиске уязвимостей, даже если ...

Быстрорастворимое проектирование

Люди учатся архитектуре по старым книжкам, которые писались для Java. Книжки хорошие, но дают решение задач того времени инструментами того времени. Время поменялось, C# уже больше похож на лайтовую Scala, чем Java, а новых хороших книжек мало. Увидим обзор типовых ...