Monorepo vs Polyrepo
Два подхода к организации кода и их влияние на Developer Experience
Первоисточник
Why Google Stores Billions of Lines of Code in a Single RepositoryRachel Potvin, Josh Levenberg, 2016
Два полюса, между которыми все живут
Вопрос «один репозиторий или много» звучит как архитектурное решение, но на практике он определяет, как ищется код, запускаются тесты, делается рефакторинг и координируется работа между командами. Google хранит два миллиарда строк кода в одном репозитории — 86 терабайт данных, 35 миллионов коммитов, 40 тысяч коммитов в день от более чем десяти тысяч инженеров. Meta пошла тем же путём и в 2022 году выпустила Sapling — собственную систему контроля версий, заточенную под монорепозиторий с десятками миллионов файлов. На другом полюсе Netflix, где каждый микросервис живёт в отдельном репозитории, а команды управляют своими CI/CD-пайплайнами независимо друг от друга.
Оба подхода работают в масштабе. Дальше — про то, в каких ситуациях каждый из них даёт лучший Developer Experience.
Что даёт монорепозиторий
Главное преимущество монорепо — видимость. Код любой команды доступен для чтения, история изменений интерфейса, от которого зависит сервис, видна сразу, а атомарный коммит обновляет API и всех его потребителей одновременно. Rachel Potvin и Josh Levenberg в статье для ACM описывают, как Google проводит масштабные рефакторинги: переименование функции, которую вызывают тысячи файлов, проходит за один коммит, и все зависимые модули обновляются вместе.
Унифицированное версионирование снимает «dependency hell». Вместо указания версии внутренней библиотеки в package.json и слежки за обновлениями код импортируется напрямую из той же кодовой базы. Все видят последнюю версию, и если изменение ломает чужой код — это всплывает сразу, в CI текущего коммита, а не через неделю, когда кто-то обновит зависимость.
Совместное владение кодом тоже упрощается. Границы между «своим» и «чужим» размываются, инженеры из разных команд спокойно отправляют pull request’ы друг другу, потому что весь код перед глазами и в одном рабочем дереве.
Что даёт polyrepo
Polyrepo выигрывает в автономии. Каждая команда управляет своим репозиторием: выбирает стратегию ветвления, настраивает CI/CD, определяет политику релизов и контроля доступа. Сервис, обрабатывающий платёжные данные, можно ограничить тремя инженерами с соответствующим уровнем допуска — в монорепо такая гранулярность требует специальных инструментов.
Сборка в polyrepo быстрее по умолчанию: CI прогоняет тесты одного сервиса, а не пытается понять, какие из миллиона файлов затронуты коммитом. Деплой независимый — сервис выкатывается без оглядки на то, в каком состоянии находится код соседней команды.
Для open source polyrepo — стандарт де-факто. Каждый проект живёт в своём репозитории, у него своя аудитория, свои контрибьюторы, свой цикл релизов. Попытка принять pull request от внешнего разработчика в монорепо с проприетарным кодом быстро объясняет, почему open source так не делает.
Trade-offs: где болит
Монорепо требует инвестиций в инструменты. Git плохо работает с репозиториями на миллионы файлов — поэтому Meta в 2013 году перешла на Mercurial, а потом написала Sapling: базовые git-команды в репозитории такого размера занимали бы по 45 минут. Google использует собственную систему Piper. Для компаний поменьше есть sparse checkout в Git и инструменты вроде Bazel для инкрементальных сборок, но всё это нужно настраивать, поддерживать и обучать людей с ним работать.
Polyrepo страдает от фрагментации знаний. Когда репозиториев 200, поиск нужного кода превращается в археологическое исследование. Обновление общей библиотеки оборачивается марафоном pull request’ов по всем зависимым репозиториям — и если три из них забыли обновиться, в продакшене окажутся несовместимые версии. Координация между командами требует формальных контрактов и API-версионирования, что добавляет когнитивную нагрузку при каждом межкомандном взаимодействии. Независимость CI/CD-пайплайнов — преимущество polyrepo, но за неё приходится платить дублированием конфигураций.
Как выбирать
Организация пишет продукт с тесно связанными компонентами, команды регулярно меняют код друг друга и проводят сквозные рефакторинги — монорепо даст скорость и видимость. Сервисы по-настоящему независимы, команды автономны, технологический стек разнородный (один сервис на Go, другой на Python, третий на Rust) — polyrepo сохранит эту независимость без лишней координации.
Есть и промежуточный путь — несколько монорепо по доменам. Frontend-монорепо, backend-монорепо, infra-монорепо. Spotify пользовалась таким подходом, прежде чем Backstage стал их основным инструментом для навигации по коду. Этот вариант снижает нагрузку на инструментарий по сравнению с единым монорепо, но сохраняет атомарные изменения внутри каждого домена.
Выбор между монорепо и polyrepo — выбор между разными проблемами, а не между проблемами и их отсутствием. В монорепо предстоит решать задачи масштабирования инструментов, и здесь помогут системы сборки вроде Bazel, Nx и Turborepo. В polyrepo — задачи координации и обнаружения кода. Браться стоит за те проблемы, которые организация лучше умеет решать.