モノリシックなバージョン管理の利点

以下は、私がよく交わす会話の一例です。

人物A:FacebookやGoogleは、巨大なモノリシックリポジトリ(モノレポ)を使っているんだってよ。

私:みたいだね。あれは本当に便利だと思う。

人物A:僕に言わせれば最悪の愚行さ。全てのコードを単一のリポジトリに入れるのがヒドイ考えだと、FacebookやGoogleはなぜ思わないんだろうか。

私:FacebookやGoogleのエンジニアたちも小さなリポジトリには精通しているだろうけど(濱野純(Junio Hamano)氏はGoogle勤務だし)、単一の大きなリポジトリの方が、きっと”ある理由”で好みなんだよ。

人物A:なるほどね。僕としては、まだちょっと違和感はあるけど、モノレポが使われる理由は分かったような気がするよ。

“ある理由”はかなり長いので、同じ会話を何度も繰り返さなくていいように、ここに書き留めておこうと思います。

シンプルな構成

複数リポジトリでは、通常、リポジトリごとに1つのプロジェクト、またはリポジトリごとに関連プロジェクトのまとまりがありますが、その場合、作業に関わる特定のチームあるいは企業にとって、”プロジェクト”の定義をする必要があります。更に、時には純粋にオーバーヘッド的な側面から、リポジトリの分割やマージが必要になることもあるでしょう。しかし、プロジェクトが大きすぎたり、バージョン管理システム(VCS)の履歴が多すぎたりすることでプロジェクトを分割しなければならないというのは、理想的とは言えません。

モノレポでは、バージョン管理システムが特定の方法で物事を構成するよう促さずとも、論理的に一貫性があると思われる方法でプロジェクトをまとめてグループ化することが可能です。また単一のリポジトリを使うことは、依存関係の管理で生じるオーバーヘッドの削減にもつながります。

シンプルな構成の別の恩恵として、プロジェクトのナビゲートが容易になることが挙げられます。私がこれまでに使ってきたモノレポではどれも、基本的に全てがネットワークのファイルシステム上にあるような感じでナビゲートすることができ、プロジェクト内でナビゲートに使っていたイディオムを再利用することができました。一方、複数リポのセットアップでは、通常、2つの独立したナビゲーションレベル(プロジェクト内で使われるファイルシステムのイディオムとプロジェクト間のナビゲーションのためのメタレベル)があります。

上に挙げた別の恩恵の更に別の恩恵を言いましょう。モノレポでは、ほとんどの場合で、簡単に開発環境を設定してビルドやテストを実行できます。cdに相当するプロジェクト間のナビゲートができると思われる場合、cd; makeもできるはずです。恐らくできるだろうと思われる状況では、そのために必要なツールさえあれば、大抵うまくいくでしょう1。ちなみに複数リポでも、技術的には、上記のような使いやすさを実現することは可能ですが、それに必要な作業はあまり一般的ではなく、その作業をすることは、ある意味で不自然です。

シンプルな依存関係

これは、恐らく言うまでもないことだと思いますが、複数リポでは、リポジトリ間の依存関係を指定してバージョンを管理する何らかの方法が必要です。これは一聴すると簡単に思えますが、実際に適用されるほとんどの対応策はややこしく、オーバーヘッドは大きくなります。

モノレポでは、全てのプロジェクトに対して、簡単に共通のバージョン番号を持たせることができます。プロジェクト間のアトミックなコミットが可能なので、リポジトリを常に一貫した状態に維持でき、commit #xで全てのプロジェクトビルドが機能します。依存関係については、ビルドシステムでは指定する必要がありますが、MakefileであれBazelのBUILDファイルであれ、他の全てと同様にバージョン管理のチェックができます。なお、バージョン番号が1つしかないため、選択したMakefileやBUILDファイルにバージョン番号を指定する必要はありません。

ツール

ナビゲーションと依存関係の簡素化により、ツールを書くのが非常に簡単になります。なぜなら、リポジトリ間の関係やリポジトリ内のファイルの特性を把握する必要がなく、単にファイル(リポジトリ内のユニット間の依存関係を示すファイルフォーマットを含む)が読めればいいだけだからです。

これは些細なことのように思えるかもしれませんが、これによってビルドがどれほど簡単になるかは、Christopher Van Arsdale氏のこちらの例をご覧いただければお分かりいただけるでしょう。

Google内部のビルドシステムでは、モジュール化された大規模なコードブロックを使用することで、ソフトウェアの構築が信じられないほど容易になっています。例えば、クローラが必要な時にはこちらから数行、RSSパーサが必要な時にはあちらから数行、そしてフォールトトレランス機能を持った大規模な分散データストアが必要な時には数行を追加、といった具合です。これらは多くのプロジェクト間で共有されているビルディングブロックやサービスで、統合も簡単にできます。…こうした、まるでレゴのような開発プロセスは、オープンソースの世界ではあまり見られないものです。…このような(より投機的な)状態の結果、オープンソースの世界には複雑さの障壁が立ちはだかることになりました。そしてその状況は、ここ数年の間は大きく変わってはいません。このことが、オープンソースプロジェクトと、Googleのような企業において簡単に入手できるものとの間にギャップを作っているのです。

Arsdale氏が言及しているシステムはとても便利なもので、オープンソース化される前に、FacebookTwitterに在籍するGoogleの元エンジニアたちは、同じ利点を確保するために独自のBazelバージョンを作成しました。

理論的には、モノレポでなくとも、依存関係のないシンプルなものを構築できるビルドシステムを作ることは可能ですが、それには多大な労力が必要で、私自身それをシームレスに実現しているシステムをいまだかつて目にしたことはありません。Mavenやsbtは、見方によればかなり良いと言えます。しかし、バージョン依存性の問題を追跡して修正するのに多くの時間を費やすというのは、あまり一般的ではありません。また、rbenvやvirtualenvのようなシステムでその問題を回避しようとすると、開発環境が膨れ上がってしまいます。一方で、HEADが常に整合性の取れた有効なバージョンを指しているモノレポを使用すると、複数リポのバージョンを追跡するという問題を完全に取り除くことが可能です2

モノレポ上で実行させることで恩恵を受けるのは、ビルドシステムだけではありません。例えば静的解析は、余分な作業なしにプロジェクトの境界を越えて実行できますし、その他にもプロジェクト間の統合テストやコード検索など、多くの機能を簡略化することができます。

プロジェクト間の変更

リポジトリが増えてくると、リポジトリ間に変更を適用するのは非常に大変です。ほとんどの場合、各リポジトリやスクリプト間で面倒な調整が必要となってきます。更に、スクリプトが機能しても、リポジトリ間の依存関係を正しく更新しなければならないというオーバーヘッドもあります。もし、数十のアクティブな内部プロジェクトで使われているAPIをリファクタリングすることになったとしたら、丸一日は優にかかるでしょう。それが数千ともなると、想像したくもありません。

一方モノレポでは、1回のコミットでAPIとその全ての呼び出し元をリファクタリングすればいいだけです。この作業は、常に楽とは限りませんが、小さなリポジトリがたくさんある場合に比べると、はるかに簡単と言えるでしょう。数百ものプロジェクトにまたがって数千もの使用法を持つAPIがリファクタリングされる現場を見たことがありますが、モノレポでのセットアップの場合、特にやり方を熟考する必要がないほどに簡単です。

今では多くの人が、CVSやRCS、ClearCaseなどのバージョン管理システムを使用するのは不合理だと考えています。それらでは複数のファイルにわたって単一のアトミックコミットを行うことは不可能なため、ファイル間に適用される変更の特定のセットが”本当に”アトミックかどうか決定するためのメタ情報を保持するか、手動でタイムスタンプを見てメッセージをコミットしなければなりません。SVNやhg、gitなどであれば、そうしたファイル間のアトミックな変更の問題を解決することができるでしょう。しかしモノレポであれば、同じ問題をプロジェクト間で解決できます。

こうしたことは、何も大規模なAPIリファクタリングのみに有用なわけではありません。Twitterで複数リポからモノレポへの移行を担当したDavid Turner氏は、分野横断的な小さな変更と、それらのためにリリースを行うオーバーヘッドの例を挙げています。

私は”プロジェクトA”を更新する必要がありました。しかし、それをするには、その依存関係の1つを同僚に直してもらう必要があります。それを”プロジェクトB”としましょう。同僚は、”プロジェクトB”を変更することで、”プロジェクトC”を修正しなければならないと言います。Aを直してデプロイするためにCがリリースするのを待ち、更にBを待たなければならないのであれば、私はそうするしかありません。しかし全てが単一のリポジトリ内にあれば、同僚が変更とコミットを行った段階で、私はすぐに自分の変更を行うことができるのです。

もし全てがgitのバージョンでリンクされていれば私にもできたかもしれませんが、いずれにしても同僚は依然として2つのコミットをしなければならなかったでしょう。そしてそこには常に、バージョンを選び、”安定化”(つまり停滞)させるという誘惑が存在します。プロジェクトが1つだけであれば大した問題にはならないでしょうが、相互に依存するプロジェクトが山ほどある場合、目も当てられません。

“別の方向から見ると”、依存されるものを強制的に更新することは、実際にはモノレポのもう1つの利点と言えます。

プロジェクト間の変更が簡単になるだけでなく、それらの追跡も簡単になります。複数のリポジトリにまたがってgit bisectと同じことをしようと思えば、メタ情報を追跡するツールの使用法を学ぶ必要が生じてくるでしょう。しかし、ほとんどのプロジェクトではそんなことはしませんし、仮にした場合、1つのツールで十分なところに2つのツールを持つことになってしまいます。

Mercurialとgitは、間違いなく素晴らしい

これらの点に関する一般的な反応は、CVSまたはSVNからgitやhgに切り替えると、生産性が大幅に向上するというものです。それは間違いないでしょう。ただし、それは小さなリポジトリ自体が優れているというよりも、複数の点から見て、gitやhgが優れているため(例:よりよいマージ機能)と言えます。

実際、巨大なモノレポをサポートするために、Twitterではgitにパッチを当てており、FacebookではMercurialにパッチを当てています。

欠点

もちろん、モノレポを使うことには欠点もあります。ただし、その欠点についてはすでに広く議論されているため、ここでは触れません。monorepoはmanyrepoより厳密に優れているわけではなく、かと言って、厳密に劣っているわけでもありません。ここで言いたいのは、今すぐモノレポに切り替えるべきだということではなく、モノレポを使うこと自体に不合理性はないということであり、GoogleやFacebook、Twitter、Digital Ocean、Etsyなどが数百、数千、数万のより小さなレポを使わずにモノレポを使うのには、それだけの理由があるということです。

他の議論

Gregory Szorc氏、FacebookBenjamin Pollack氏(Kilnの共同創設者の1人)、Benjamin EberleiSimon StewartDigital OceanGoogle、Twitter、theduferPaul Hammant

今回の投稿に関して広範な議論で助けてくれたKamal Marhubi、David Turner、Leah Hansonに感謝します。投稿した記事のアイデアの少なくとも半分は彼らのものです。また、校正に関しては、Leah Hanson、Mindy Preston、Chris Ball、Daniel Espeset、Joe Wilder、Nicolas Grilly、Giovanni Gherdovich、Paul Hammant、Simon Thulbournに感謝します。


  1. これは、以前勤めていた会社で、ネットワークファイルシステムからRCS内をバージョン管理してモノレポを作成していた時にも当てはまります。もちろん中央リポジトリ内のファイルを生編集することはできないので、基本的にこれをPerforceに変えるスクリプトを書きました。この方法は推奨しませんが、信じられないほど出来が悪いモノレポでさえ、モノレポの多くの利点を享受できます。 

  2. 少なくとも、上流の依存関係をベンダリングするメカニズムを持っている限りにおいてです。例えば、使用するコードの大部分を自身で書き、外部依存関係の全てをモノレポに投じるのに十分な従業員を擁し、なおかつ全ての従業員にわたって償却の負担が低いGoogleにとっては、これは最適ですが、中小企業でこの利点を生かそうとすると、コストが高くなることが想像されます。