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 — задачи координации и обнаружения кода. Выбирайте те проблемы, которые ваша организация лучше умеет решать.