joelparkerhenderson/monorepo_vs_polyrepoの粗訳です。
モノリポは多数のプロジェクトを1つのリポジトリに入れる方針。
ポリリポ(マルチリポ、メニーリポ)は1つのプロジェクトを1つのリポジトリに入れる方針。
このページでは、それぞれの類似点と相違点を議論します。
このページの内容:
- Introduction
- Comparisons
- Tooling
- Monorepo scaling
- Proponents of monorepo
- Proponents of polyrepo
- Opinions about splitting
- Opinions about balances
- Opinions about alternatives
参考情報:
- SCM at Facebook
- SCM at Google
- Why Google stores billions of lines of code in a single repository (2016) (acm.org)
- Scaling Mercurial at Facebook
- Cthulhu: Organizing Go Code in a Scalable Repo
主流の見解:
ハッカーニュースの議論:
貢献者たち:
-
このページで紹介している意見やコメントは、さまざまなWebサイトで交わされた議論を整形したものです
-
意見を書いた本人や、意見を洗練するのに貢献した人は、このリポジトリにコミット権限を渡すので教えてください
モノリポとは、「ソースコードのバージョン管理システムで1つのリポジトリを使う」方針のニックネームです。
- アーキテクチャ的に言うと、モノリポとは、複数のリポジトリではなく1つのリポジトリを使う方針です
- 具体的には、Webアプリ、モバイルアプリ、サーバーアプリのディレクトリ(プロジェクト)が1つのリポジトリに入っているならそれがモノリポです
- ワンリポ、ユニリポと呼ばれることもあります
ポリリポとは、「ソースコードのバージョン管理システムで複数のリポジトリを使う」方針のニックネームです。
- アーキテクチャ的に言うと、ポリリポとは、1つのリポジトリではなく複数のリポジトリを使う方針です
- 具体的には、Webアプリ、モバイルアプリ、サーバーアプリのディレクトリ(プロジェクト)それぞれにリポジトリを用意しているならそれがポリリポです
- メニーリポ、マルチリポと呼ばれることもあります
モノリポとポリリポの主要な類似点は次のとおりです。
- ソースコードを管理するという意味では同じです
- さまざまなプロジェクトの規模で成功事例があります
- ソースコード管理システムの上限を超えなければ、どちらも素直に実現できます
モノリポとポリリポの相違点をいろいろなプロポネントについてまとめています。 また、典型的な相違点を強調しています。
モノリポ(Monorepo) | ポリリポ(Polyrepo) | |
---|---|---|
コンテンツ(Contents) | プログラミング言語やパッケージ方法に関わらず、複数のプロジェクトを持っています | 1つのプロジェクトだけを持っています |
プロジェクト(Projects) | 1つのリポジトリで一緒に、統一的に管理します | 複数のリポジトリで、別々に管理します |
ワークフロー(Workflows) | ワークフローは、同じモノリポに登録された全てのプロジェクトを同時に扱います | ワークフローは、別々のリポジトリになっているプロジェクトをそれぞれで扱います |
変更(Changes) | 全てのプロジェクトに影響する変更は一緒に追跡、テスト、リリースします | それぞれのプロジェクトに対する影響は別々に追跡、テスト、リリースします |
コラボレーション(Collaboration) | 1つの組織内でコードを通じたコラボレーションをします | 組織を跨いでコードを通じたコラボレーションをします |
テスト(Testing) | すべてのプロジェクトを一緒にテストできるのでホワイボトックステストがしやすい | それぞれのプロジェクトは別々にテストできるので、ブラックボックステストがしやすい |
リリース(Releases) | 本質的にリリースは一緒になるし、さまざまなツールを駆使しないといけない | リリースはプロジェクトごとに別々だけど、基本的なツールを利用できる |
状態(State) | あらゆる状態は1つのコミットに相当します | それぞれのリポジトリにそれぞれの状態があります |
結合(Coupling) | プロジェクトは密結合しがちです | プロジェクト同士はあまり結合しません |
考え方(Thinking) | 全てのプロジェクトを同化して考えがちです | プロジェクト間の契約を考えがちです |
アクセス制御(Access) | 全てのプロジェクトに同じアクセス制御を適用します。チームによってはきめ細かいアクセス制御ができるツールを使うこともあります。例えば、Gitlabはマージリクエストの受付をディレクトリベースで制御できます。Google Piperにもアクセス制御ツールがあります。Phabricatorは指定したサブディレクトリの変更に対するマージを停止するルールを適用できます。サービスオーナーという考え方で、複数のサービスに影響するサービスの変更に対する必須レビューアを自動的に構成することもできます。 | アクセス制御はプロジェクトごとに適用します。適用範囲の広いアクセス制御ツールを利用するチームもあります。GitHubではチームが複数のプロジェクトを所有できるし、他のリポジトリ(プロジェクト)へ影響する変更を承認制にできます。 |
規模(Scaling) | 規模を拡大するには専用のツールが必要です。とても大きなリポジトリやとても大きなファイルを、素のGitで運用するのは現実的ではありません。モノリポの成長には、独自ツールの開発やツールの使い方を訓練に時間をかけないといけません。 | 規模を拡大するには専用の調停機構が必要です。多数のリポジトリ(プロジェクト)を素のGitで運用するのは現実的ではありません。ポリリポの成長には、リポジトリ間を調停するスクリプトの開発や、それぞれのリポジトリの互換性を維持することに時間をかけないといけません。 |
ツール開発(Tooling) | Googleはビルドツール「bazel」を開発しました。有効非巡回グラフ(DAG)で内部の依存関係を追跡します。 | Lyftは管理ツール「refactorator」を開発しました。複数のリポジトリに対する変更、PRの作成、承認ステータスの追跡などいろんなことをします。 |
ツール(Tooling)](#tooling)
Bazelは高速で、スケーラブルで、多くのプログラミング言語に対応し、拡張性もあるビルドシステムです。 必要なものだけを再ビルドするようになっています。 ローカルキャッシュと分散キャッシュを活用して、依存関係の解析、並列実行を高速化しています。
「ビルド対象」ごとに依存関係を定義しなければなりません。 依存先のファイルは、同じBazelワークスペースに保存するか、ビルド時にgitコマンドで取り込むことができます(同じリポジトリに存在しないファイルにも依存できる)。
ビルドの再現性を保つため、依存対象はコミットIDやファイルハッシュで定義できます。
LernaはGitリポジトリとnpmレジストリに配置した複数のパッケージ管理を最適化するツールです。
OctolinkerはGitHubのポリリポをとても簡単に閲覧できるツールです。 プロジェクト名を指定するだけで簡単にリポジトリを移動できます。
モノリポを拡大するには、素のGitでどうにもならない問題があります。
-
空間効率がとても悪くなります。リポジトリが、開発者のラップトップPCに入りきらないほど大きくなってしまうのです。対策として大容量ストレージを用意するのは現実的ではありません。
-
時間効率がとても悪くなります。リモートリポジトリを取得する時間だけでなく、pruneとかrepackとか、あらゆる操作に時間がかかるようになります。
-
プロジェクトが増えてくると、プロジェクトを跨いで検索、編集、変更部分を特定するのが辛くなってしまいます。
モノリポの拡大に伴う問題は、次のような方法でましになります。
-
作業PCで一部のファイルだけを実体として扱うような仮想ファイルシステム(VFS)を使いましょう。有償VCSのPerforceにはそういう機能があります。Googleは社内ツール「G3」を使っていますし、MicrosoftはGVFSというツールを公開しています。
-
SaaSのソースコード検索ツールを使いましょう。普通の開発者は素のGitで検索できるようリポジトリ全体を取得しないからです
次のようなメトリクスで、モノリポの拡大に伴う問題を近似的に観測できます。
-
10人以上100人未満のフルタイム開発者がいる
-
10以上100未満のプロジェクトが進行している
-
10以上100未満のパッケージングタスクを、日次等、同じタイミングで実行してる
-
ソースコードの合計行数が1,000以上10,000未満
-
Nodeモジュール、Pythonパッケージ、Ruby gems等、バージョンを指定した依存関係が1,000以上10,000未満
同時にリリースしないといけない複数のコンポーネントは、モノリポで管理したほうがいい(If components need to release together, then use a monorepo)
同時にリリースしないといけない複数のコンポーネントがあるなら、モノリポで管理したほうがいいでしょう。 そのほうが、リリーススケジュールを管理しやすいからです。
別々のリポジトリになっているコンポーネントを同時にリリースするのは本当に辛い。
一緒にリリースしなくていいからリポジトリを別にしよう、という判断をするかもしれません。 ですが、それぞれのコンポーネントが一部のソースコードを共有しているなら、確実なバージョン管理が必要ですし、そういう管理は辛い。 管理の負担より価値がある場合にだけそうしましょう。 スタートアップ企業なら、多分そうはならないと思います。
未成熟で捨てられがちなコードだらけなら、モノリポで管理したほうがいい(I’ve found monorepos to be extremely valuable in an less-mature, high-churn codebase)
関数のシグネチャやインターフェイスを変更したいですって?全体を検索して置換すればいいよね。
モノリポの有用性は、ソースコードの合計行数が10,000行を超えたくらいで臨界点に達します(個人的にはそんなにたいした量じゃないと思うけど)。 パッケージを分割するべきでしょう。
それでも、VSCodeの自動補完とか自動インポートとか、そういう便利機能に気を取られて間違えてしまうことばかりです。
会社のことを、共通するミッションを遂行する人々の集まりとして考えるとよさそうです。 会社はミッションを実現するため、複数のプロジェクトを進行します。 それぞれのプロジェクトにおける判断や、あらゆるソースコードの変更は、主目標に向けた足場になるのです。 コードベースは会社が目標に到達するためのナレッジであり、目標そのものでもあるのです。
そういうふうに考えると、モノリポは全てのチームメンバーが一同に介する様子そのものであることが分かります。 複数に分かれているのはエンタープライジーです。
有名で巨大なテクノロジー企業がモノリポを使っているからといって、うちが従う道理はないよね(If tech's biggest names use a monorepo, should we do the same?)
Google、Facebook、Twitter等、有名で巨大なテクノロジー企業はモノリポを使っているようです。 みんなモノリポを使っているのなら、すごいメリットがあるはずだから、自分たちも使うべきだ、という考えは本当に正しいのでしょうか。
そもそも規模が違いすぎます。 ポリリポで解決できる問題は、モノリポでも解決できますが、密結合というデメリットをもたらします。 また、VCSのスケーラビリティ問題というとても大変な苦労をしょい込むことになります。
中長期的には、モノリポの導入に組織的なメリットはありません。 それどころか、エース級エンジニアが心理的なダメージ(PTSD)を被る可能性があります(うつろな表情でGitの内部性能を愚痴りだしたらそうなっています)。
モノリポで気になるのは不必要なプロジェクトとの結合です。 たしかに、自由主義者としての考え方に影響を受けているところもあります。 しかし、あるプロダクトのために必要なサーバー更新スケジュールにより、他のプロジェクトを数か月ほど中断しなければならなかった、という経験には、共通する何かがあると思います。 たった1つの依存関係に新しいフィーチャや大規模な改修が入るからといって、会社全体がアップグレードに対応できるまで待たされてしまうなんて想像できるでしょうか。
GoogleがJUnitでそういう状況になった話を聞いたことがあります。 2007年に、JUnitを3.8.xから4.xに更新するとき、ごく少数のバックエンドプロジェクトが非互換性を解消できずに困ってしまったそうなのです。 変更セットは極めて巨大になり、開発者がテストを作成するペースを保つのに苦労するようになったとか。
見通しのよい組織はコードベースを単一のリポジトリに格納するので、コードの再利用、インターフェイスと実装の分離を促進すると言われています。 個人的にはそうは思いません。
自分のツールボックスで、「直接的なつながりはないけど、同じバージョンにしてる」、「バージョンに応じて適切に分離されている」、という状態を表現するのはとても重要です。
全てのメンバーが同時にスタンドアップミーティングへ参加できないほど、あなたの所属するチームが大きくなったなら、たぶん複数のリポジトリで作業するべきです。 semVerでリリースできるようにしないといけません。 そうしないと、あなたの担当しているコードの変更を、他の人たちに伝えられないからです。 タグはリポジトリ全体に付くので、バージョンとリリースの単位はリポジトリごとにならないといけないのです。
Gitの作法に従っていれば、大きなリポジトリを小さなリポジトリへ分割するのは簡単です。 subtreeを使えば履歴を残したまま分割できます。 リポジトリを複製して不要なツリーを削除してもいいでしょう。
しかし、小さなリポジトリを統合するのはとてもとても大変です。
だから、モノリポから始めるのが一番です。
本当に分割しないといけないことが分かったら、分割しましょう。
ポリリポで困るのが(困っているのが)、リポジトリを分割しすぎることです。 1つのフィーチャが1つのコミットでは完結せず、複数のリポジトリへアクセスしなければなりません。
いくつものブラウザタブを行ったり来たりしないといけないので、レビューする人も大変です。
全てのプロジェクトから利用する基本ライブラリ(内部ライブラリ)を変更するときは特に大変です。 全ての利用箇所を見て回るのはとても大変だったけど、それぞれの利用箇所へ修正を反映しました。 つまり、自分以外の人だったら違う修正をするかもしれないのです。 同じコードベースで新しく働き始めた人には、とても分かりにくい状態だと思います。
有名なモノリポにはどれも境界が残っています。 誰も追跡していないせいで分かりにくなっています。 おそらく、あなたが「触った」ファイルに関連する隠れた依存関係を、CIプロセスが発見します。 そういう隠れて結びついている番人に気付くのは、コードレビューの終盤になってからでしょう。
ポリリポではそういう依存関係は明示的になっています。 依存される側のリポジトリの持ち主にとってそれは明らかなことなのです。 あなたは、そういう依存先のリポジトリの持ち主と喧嘩したくないと思っているでしょうけど、もしそうなったとしても驚くようなことではありません。
私は前職と、前々職で、100人以上開発者のいる組織で作業効率の改善に取り組んでいました。 片方はモノリポを、もう片方はポリリポを実線していました。
実際のところ、どちらの組織も効率や開発者体験はいまいちでした。 それでも、どちらのリポジトリ種類を選択したらどんな課題に取り組まないといけないのか理解できました。
モノリポ:組織の拡大に伴うリポジトリサイズの拡大が主な課題です。
ポリリポ:基本的に調停が主な課題です。
オンプレアプリやデスクトップアプリを開発している、つまり、長期間リリースブランチを維持するような場合、ここまでの議論とはまったく異なる状況になります。
個人的な経験では、モノリポのリリースブランチのほうが、出荷済みのソフトウェアをちゃんと運用できていました。 ブランチには全てが含まれているからです。 複数のリポジトリをパッケージマネージャーで管理する方法も悪くありません。 モジュールのバージョンを指定するだけで、複数のブランチをマージする手間を最小化できたからです。 その代わり、バージョン番号を管理する苦労は発生するのですが。
私が参加していたプロジェクトでは、6つか7つのブランチで、同時に開発を進めつつ、masterブランチから分岐した小規模なブランチがいくつかある状態でした。 そこでは、あらゆるブランチをいい感じにマージしないといけなかったのです。 これは、Googleのモノリポ運用における「master only」と全く異なるやり方でした。 問題をややこしくしている主な理由の1つとして挙げられるのは、Golangに適切なバージョン管理戦略が存在していなかったことがあります。
コメント:利用者が自分で管理する環境で実行するソフトウェアには、メジャーリリースやマイナーリリースという名の更新パッケージを提供しなければなりません。 よくあるのは、リリースバージョンと対応するブランチを作成する方法です。 問題が報告されたら、対応するブランチをチェックアウトして改修し、改修の内容を本流のブランチへ反映するのです。
モノリポとsubmoduleで両方のいいとこどりをしませんか(Could you get the best of both worlds by having a monorepo of submodules?)
ソースコードは別々のリポジトリになっているとしても、モノリポにそれらのリポジトリへの参照を格納できます。 モノリポに対するコミットやロールバックはCIを起動します。
回答:モノリポだけ、ポリリポだけでやっている場合ほど便利ではないと思います。 どんな規模であろうと、sabumoduleに追加したリポジトリを上手く扱うには、ツールの使い方を習熟するのにすごいコストがかかります。 大規模の場合、元の使い方とそんなに変わらないと思います。 自分用のリポジトリは「小さい」ままでしょう(ただし、他の人に引き継ぐのは大変です)。 ですが、そのまま過ごしていると、性能的な理由で、中小規模では使わなかった機能が必要になってきます。 組織の問題として扱うのを避けていると、技術的な問題として顕在化します。
回答:Uber ではそれをやっています。
ある開発チームがuber_monorepo
というリポジトリを作成し、他のさまざまなリポジトリを登録していったのです。
それぞれのリポジトリに置いた.buckconfig
というファイルには、モバイルアプリ開発者が参照しなければならないリポジトリが記述されていて、//uber_monorepo/
というプリフィックスでアクセスできるようになっていました。
なお、疎結合な依存関係に関する問題に取り組んでいたこともあります。
サブモジュールの更新を1か月2か月さぼると、最新化するのに1日2日かかるくらいの規模でした。
自分たちの組織には1,000くらいのリポジトリがあったので「メニーリポ」にしたほうがいいのかなと考えていました。 それぞれのリポジトリには5から20以上のパッケージ、プロジェクト、サービスが格納されていたんですけどね。 面白いのは、それぞれのリポジトリの所有者は「モノリポ」でやっていると思っていたので、話がかみ合っていなかったのです。
1,000くらいのライブラリが相互に依存しているやっかいな状況でした。 必要最低限のreadmeが用意されている場合もあるし、「本」になるほどしっかりしたドキュメントがある場合もあるし、ソースコードコメントで十分だと言わんばかりの場合もありました。
同じ開発プロセスを会社全体に強制していませんでした。 そのせいで、それぞれのチームが自分たちのプロセスに必要なやり方をしていたのです。
それぞれのリポジトリが独自の自動化やツール活用をしていたのは言うまでもありません。
本当に必要なのは、リポジトリの統合と分離が簡単にできる、あるいは、モノリポの欠点を克服できるVCSです。
予想:GitのようなDVCSが、SVNのような旧世代VCSを駆逐したように、リポジトリを簡単に統合・分離できる新しいVCSが登場すれば、今のVCSもすぐに駆逐されるでしょう。 ユーザーをリポジトリ単位で割り当てたり、サブリポジトリを登録したり、すべての状態を単一のコミットハッシュで追跡できるような。 そして、VCSのクライアントは、毎回紐づけされたすべてのリポジトリをダウンロードしなくても済むような。
今の時点でも、VCSとしての機能を使わず、フォルダーがリモートリポジトリを参照するような仕組みを活用すれば、そういったことは実現できています。