- モノレポ の導入には、組織の一貫性、コード共有、共通ツール環境の強化といった利点がある一方、ビッグテックの事例をそのまま真似すると 新たな問題と課題 に直面することになる
- 成功するモノレポのためには、すべての主要な作業を O(repo) ではなく O(change) にするという原則を守る必要があり、ビルド・テスト・CI/CD の各段階でそれに合ったツールと戦略が必要になる
- ソース管理 は git を出発点としつつ、規模が大きくなるにつれて sparse checkout や仮想ファイルシステムなどの段階的な拡張を検討する必要がある
- ビルドシステム は可能な限り 単一言語 を維持し、各言語の標準ビルドツールでできる限り粘り、どうしても必要になった時点で Bazel/Buck2 などへ段階的に移行するのが望ましい
- テスト・CI/CD は変更の影響範囲だけを素早く検出してビルド・テスト・デプロイすべきであり、大規模モノレポではテストの自動再試行や flaky test の隔離など、信頼性を確保する戦略も不可欠である
序論: モノレポへの旅の始まり
- 新しい Developer Productivity チームのエンジニアであれば、モノレポ導入の決定後にどのような準備と努力が必要かについて悩みが大きくなる
- Google、Meta、Uber など大企業のベストプラクティスは魅力的に見えるが、現実的には彼らと同じレベルの結果を得ることは不可能である
- 各組織は自分たちなりの理由と必要性に合わせてモノレポ導入を決めるべきであり、その過程で 一貫性(consistency)、組織的統合、共通ツール などの利点を追求できる
モノレポの必要性を明確にする
- 大企業の事例 はあくまで最終的に到達した姿であり、初期段階で参考にする根拠としては適切ではない
- 実際には新しい問題が発生し、従来の複数リポジトリ管理とは異なる種類の課題が生じる
- モノレポ導入の目的は、一貫性の維持、組織全体にわたるツール統合、エンジニアリング標準や規約の適用などにある
- 各チームは自らの文化と方向性に合った目標を明確にしてこそ、効果的な結果を得られる
ゴールデンルール: O(change) の原則
- すべての リポジトリ関連ツール は高速に動作するために、O(repo) ではなく O(change) の計算量を持つべきである
- 実際には大規模モノレポであればあるほど既存ツールの非効率さが目立つため、性能問題を克服するための構造的な設計が必須となる
- 大企業の技術ブログで語られる革新の多くも、結局は O(repo) に起因する非効率の克服に集中している
ソース管理
- 多くのソフトウェア組織では Git を基本として使っているが、Git は集中型モノレポ環境で大規模に拡張する際に性能上の限界がある
- 現実的には、ほとんどの組織は git+GitHub でかなり長い間やっていける
- 成長が速くなるほど、sparse checkout(部分チェックアウト)機能や 仮想ファイルシステム(必要時にファイルをサーバーから動的にダウンロードする仕組み)のような構造が必要になる
- 大企業はこれに対応するため Git をフォークしたり、別個のシステムを開発したりしている(Microsoft: 独自 Git フォーク、Meta: Mercurial フォーク、Google: Piper など)
- Jujutsu などの次世代ソース管理も検討に値する
- 規模が小さいうちは Git を無理なく使えるが、成長の過程で拡張戦略を念頭に置く必要がある
- ソースコードに IDL(Interface Definition Language) から生成されたコードが含まれると、リポジトリサイズが幾何級数的に増大するという現実的な問題もある
ビルドシステム
- Bazel、Buck2 などは代表的なモノレポ向けビルドツールであり、多言語と複雑なビルドグラフをサポートする
- ビルドを単一言語で維持できれば、運用ははるかに楽になる。各言語のビルドシステム(例: Maven、Gradle、Cargo、Go など)も高い拡張性を持つ
- ビルドシステムの中核的な役割 は、「指定したビルドターゲットを効率よくビルドする(効率的にアーティファクトを生成する)」ことと、「変更されたファイルによって影響を受けるターゲットを迅速に算出する」ことである
- このために target determinator(ターゲット決定ツール)という概念が必要であり、Rust、Go、Bazel などのエコシステムにはすでにさまざまな解決策がある
- Remote execution とキャッシュは超大規模でのみ実際に必要となることが多く、一般的な企業では target determination のほうがより実用的に活用される
テスト
- 毎回すべてのテストを実行するのは非効率なため、変更の影響範囲だけをテスト する仕組みが求められる
- Flaky テスト は大規模テストシステムではさらに深刻な問題になりうる
- テストシステムには、自動再試行、テスト影響範囲の自動判定、Flaky テストの隔離などが必要である
- 一部の言語(例: Rust の nextest、Java の JUnit など)は、こうした高度な機能を標準または拡張として提供している
- モノレポのテスト体制は、ビルドシステムと密接に統合されてこそ効果を発揮する
継続的インテグレーション(CI)
- CI システムは変更内容に応じて、必要な ビルドアーティファクト と 妥当性検証 を自動で実行しなければならない
- Target determinator の性能と効率が、CI パイプラインの中核要素として機能する
- 現代の CI は “Merge Queue” などさまざまな戦略を用い、コード品質の維持とマージ速度の最適化のバランスを取る必要がある
- 単一のコミット/PR ごとにすべての検証作業を実行するのか、一部だけを選別するのか、複数の PR をバッチ処理するのか
- Throughput(処理量)、Correctness(正確性)、Tail latency(最大待ち時間) の間のトレードオフを、自分たちで定義して設計しなければならない
- 大規模モノレポのマージ管理と CI 効率の強化は、今なお完全な解法のない課題である
- Rust(bors)、Chromium、Uber などは、それぞれ異なるマージ/検証戦略を採用している
継続的デプロイ(CD)
- モノレポ内のすべての変更が原子的にデプロイされるという 幻想 は現実とは異なる
- 1つの PR で複数サービスのインターフェース、実装、クライアントまで一度に変更できても、実際のデプロイは最終的に非同期で進むため、デプロイ時点で問題が発生する可能性がある
- サービス間の契約(Contract)を壊す変更は、デプロイ時に深刻な障害を引き起こしかねない
- 効果的なモノレポ CD 戦略には、デプロイシステムの周期、サービス契約の検証、問題発生時の迅速な検知と対応能力が必要である
結論
- モノレポは、組織の一貫性とエンジニアリング文化を強化するための強力な手段 だが、継続的なエンジニアリング投資とツール整備が必要である
- 各段階で O(change) の原則に沿った自動化・ツール・文化を構築することが核心である
- 成長に合わせてツールも継続的に進化させつつ、組織の目標と文化を反映した体系的な管理努力が重要である
- 十分な覚悟と献身、継続的な投資があれば、モノレポは最終的にそれに見合う価値を生み出してくれる
4件のコメント
とてもためになる文章ですね。強力なツールだけでなく、必要であれば必要なツールを自ら作る覚悟も必要です。だからこそ、うまく回り始めれば得られる利益も大きいです。
大学院時代、指導教授がGoogle出身のエンジニアの方と食事をしながらモノレポの話を聞いてきたのか、うちも今後はモノレポで管理しようと提案したことがあり、それを止めるのに苦労したことがありました……
モノレポには良い点も多いですが、私たちの研究室は性質上、成果物を外部の人たちに共有しなければならないことが多く、もしモノレポで成果物を管理していたら、この点で特に苦労していたと思います。マルチレポなら、成果物ごとに公開範囲を調整すればいいだけですから。
モノレポで苦労するケースの多くは、たいてい既にプロジェクトを細かく分割しすぎている場合だと思います。本来は1つか2つで十分なプロジェクトを10個前後に分割して、それをモノレポとして統合管理しようとすると、モノレポ管理ツールも必要になりますし、複雑さも増してしまいます。単純にプロジェクト自体を1つか2つに統合するほうがよく、2つ以上のプロジェクトであっても管理ツールを別に使わず、単にディレクトリを分けて1つのリポジトリに入れるという発想で考えたほうが、より気楽に管理できます。
Hacker Newsの意見
このスレッドを見ていて、昔の complexity merchants の話を思い出したという体験談の共有依頼。モノレポに移行すると技術的な犠牲があるという意見にはまったく同意しない立場。階層型ファイルシステムの力を理解すれば、モノレポの価値は分かる。CI/CD も、あちこちに散らばった構成よりモノレポ 1 つで構成したほうがはるかに明快。モノレポの核心は、組織全体がアトミックなコミットを行える点にある。多くの開発者を調整するとき、その効用は圧倒的。一度リベースして一度大きなミーティングをすれば十分。チームメンバー同士の仲が悪くて協業しなくても、管理面ではモノレポは大きな HR ツールとして機能する。
最近の開発者は、過度に分離、マイクロサービス、多数の小さなリポジトリ、そしてモノリスを極端に避ける傾向がある。これは複雑性を増大させ、組織構造の問題を将来の技術的問題へと転換する結果を招く。ソフトウェアシステムの内部依存関係もきちんと認識できていない。前職で protocol buffer のスキーマファイルを更新するのに無駄にした時間は信じがたいほどだった。幸い、今の会社はそうではない。
複数プロジェクトにまたがるコミット追跡は、あれば便利という程度で、実際には依存関係の追跡や downstream テストのトリガーという面で大きな差はない。マルチレポの自動化でも十分に可能。モノレポは助けにはなるが万能ではなく、コストも大きい。デプロイやビルドもアトミックには処理されない。モノレポの規模が大きくなると git から離れて新しいツールが必要になり、これは非常に大きな作業になる。経験がなければ簡単に語れる部分ではない。
モノレポの利点は確かにあるが、管理コストは polyrepo より高い。どんな状況でも無条件にモノレポが良いわけではない。詳しい説明は この記事を参照。費用対効果は状況次第で異なる。
プログラミング環境の設計では、チームにより多くのパワーを与えるほど問題も増える、というのが有益な経験則。技術的にはアトミックコミットはより強いパワーではなく、むしろ弱いパワーだが、悪いインターフェースのまま作業することを可能にするため、かえって問題を引き起こすパワーでもある。
モノレポに変えれば変更がよりアトミックになるという信念は罠だ、という意見。[原文引用: モノレポ最大の幻想は、コードベース全体に対してアトミックなコミットができること。実際には多様なデプロイアーティファクトがあり、サービスやクライアントなどを一度に変えても、デプロイは非同期に行われる。複数レポでは複数の PR で作業する必要があるため、リスク認識が前提になる。モノレポの CI は主にサービス契約(CI job)の検証役を担い、必要に応じて変更理由の明示が求められる。]
ビッグテックのモノレポには 2 つのタイプがある。1 つ目は記事で述べられている、全社単位の単一 "THE" モノレポで、カスタム VCS/CI が必要であり、200 人のエンジニアが支えている。Google、Meta、Uber がこの方式。この境地に達するまでの苦労は想像以上で、通常はより小さく分けた「チーム単位」のモノレポから徐々に拡張していく。各スタック/言語/チームごとに Bazel、Turborepo、Poetry のようなツールで個別に管理し、時間が経つとより大きなモノレポへ統合される。しかし、どちらの場合も開発者にもビジネスにも数百万ドル、数百万時間規模の投資が必要で、最終的にはその過程を耐え抜いた開発者たちの支えで維持される。
大規模モノレポの会社で働いていたときは、モノレポをずっと強く好んでいた。単一モノレポは、サービスグラフやコードの呼び出し構造など全体を透明に把握するのに非常に役立つ。ポリレポでは知識がチームごとに分散し、新しいコードの引き継ぎも難しく、コードアーカイブの把握はまるで迷宮探検のようだ。ポリレポは古い Discord/Slack のメッセージのように忘れ去られていく感じがある。モノレポのコストが高いなら、ポリレポも別の形のコストを生む。モノレポは巨大な大陸の草食動物で、ポリレポは多様な種が闇に埋もれる世界だ。
今の会社ではバックエンドが約 11 個の git リポジトリに分かれていて、1 つの機能のために 4〜5 件のマージリクエストが必要になり、とても煩雑。複数プロジェクトをまとめるためにモノレポ導入を検討中。ただ、リポジトリを統合できないなら、モノレポの代替は何なのか気になる。
言語に依存せず簡単かつ強力なモノレポオーケストレーションシステムは、いまだに存在しない。Bazel は複雑で学習しづらいが、最近はドキュメントがかなり改善された。Buck、NX、Pants など他の選択肢もあるが、それぞれ癖があり、特に Web サポートは限定的。大半の CI はこうしたツールをきちんとサポートしておらず、設定が難しい。ちなみに Microsoft の Rush は最高の体験を提供しており、特にフロントエンド/NodeJS モノレポには Rush を勧める。 Rush 公式サイト
ほとんどのモノレポは、Google、Uber、Meta のような大企業規模まで大きくならないという現実への言及。サービス数も会社ごとに異なり、多くても 100 個程度なら VCS のスケール問題はなく、LSP タグもノート PC 上で問題なく動く。すべてのテストを CI で無差別に回しても、ほぼ問題ない。結論として、すべての会社が Google 規模を必要としているわけではない。
今の会社では、言語スタックごとにモノレポを構築している。かなり良い折衷案だ。
モノレポ vs マルチレポの議論であまり語られないポイントは、「逆コンウェイの法則」が起こること。リポジトリ構造が組織構造や問題解決の仕方に影響を与えるという点だ。モノレポは共通インフラチームに英雄的な作業を強い、共通領域に手を入れると潜在的な破壊箇所が増えるため、1 つの機能を開発するだけでも難度が上がる。マルチレポではチーム間で複数の PR や調整、社内政治が必要になるが、その分より多様な開発者に役割を分散して処理できる。
モノレポでも中央に深くつながる変更であれば、複数段階に分けて適用できる。その過程で複数の PR や調整、政治的な問題にも対処する必要はあるが、むしろモノレポだからこそロールアウト状況をより明確に把握できる。
ポリレポでは、共通領域での変更が downstream のリポジトリに反映されず、各リポジトリが異なるバージョンに固定され、何年も更新されずに苦労するケースのほうがはるかに多い。
組織が最初に方向性をリポジトリ構造で選び、その後に技術選択が続く、という前提が正しいのかという問い。実際には、具体的な repo 構造よりもさらに根本的な組織哲学(分断か共有か)の決定が先にある。方向が変わっても、コード管理の方式は修正できる。マルチレポでもエンジニアがほぼすべてのコードにアクセスできることはあるし、モノレポでも強い分離や別個の CI、デプロイ管理ルールを適用できる。
モノレポではプロジェクト間の変更が容易なので、ポリレポでは煩雑すぎて最初から試されないような変更が、はるかに多く実行される。
大規模テック企業での経験では、ビルドシステムの管理には専任チームが必要。大規模モノレポは、必要に応じてソースファイルをダウンロードする仮想ファイルシステムを基盤にしている。記事で触れられていない点として、ほぼすべての開発はデータセンター内で動く開発サーバー上で行われ、50〜100 コア環境やオンデマンドコンテナ(随時最新コミットに更新)を活用していた。IDE は dev server と統合され、言語やサービスごとの事前準備や自動設定まで chef/ansible で自動化されている。ノート PC で直接大規模モノレポ開発をすることは非常にまれ(例外: モバイル/Mac アプリなど)。
おそらく同じビルドチームで働いた経験者。モノレポ開発環境では、ローカルでもリモートでも、再現性(reproducibility)のほうが重要。イメージ化されたリモート dev server なら、より簡単で信頼性も高い。
小規模チームでもデータセンター開発環境を活用した経験あり。今どきのハードウェア価格と密度を考えると、自前のラックを組んで dev/staging/test などのオンデマンドツールをすべて回すほうがずっと合理的。プロダクションに近い開発環境を共有するようになると、モノレポの見え方は大きく変わる。ただし中小企業にはビルドシステムへ投資する余力がなく、そうした大規模ビルドシステム特有の問題自体が発生しない(最小で 10〜20 人規模、非常に複雑な製品でも保守はパートタイムだけという場合すらある)。
Molnett(serverless cloud)で、Bazel ベースのモノレポにより驚異的な効率を得た小規模チーム(フルタイム 1.5 人)の話。Tilt+Bazel+Kind でプラットフォーム全体と Kubernetes オペレーターまでノート PC 上で起動し、Mac/Linux の両方をサポート。Bottlerocket ベースの OS や Firecracker までローカルで検証可能。tool layer を構築したことで、すべての開発者が同じバージョンの go/kubectl を使え、ローカルインストールも不要。管理には努力が要るが、元 Google SRE メンバーがいたおかげで実現できた。今後もこういうやり方だけを望む。(主要言語は Golang、Bash、Rust)
1.5 人の小規模なら単一リポジトリが当然。Bazel の経験は非常に悪かったが、大規模プロジェクトでは使う価値があるかもしれない。2 人未満の規模なら、むしろ Kind+Tilt だけで十分。tool layer も Go ならすでに go.mod である程度解決できる。kubectl も似たようにできる。元 Googler の給与水準も考える必要がある。Bazel の維持コストが今後も見合うことを願う。
うちの会社では systemd ベースのサービスと ansible playbooks でデプロイし、tmuxinator でバックエンド/DB/検索エンジン/フロントエンドまで、すべてのサービスを dev モードでターミナルから一括自動起動している。root で
tmuxinatorコマンドを一度打つだけで、開発環境全体がすぐ整う。単一モノレポは以前より圧倒的に便利だ。似たような状況で、Bazel 導入の効果を最大化できたという経験共有。tool layer のおかげで一貫した開発環境を維持できる。直接
bazel runを使う必要があるが、もっと良い自動化方法がないか気になっている。どう動いているのか教えてほしいという依頼。2 人規模でマイクロサービス/K8s パターン自体がオーバーエンジニアリング。この程度の人数規模では、どんな方式でも問題ない。昔は Dropbox/SVN/MS VCS など、どんな方式でも回っていた(不便はあっても)し、どれも致命的ではなかった。この規模では全員が全プロセスを頭の中で描ける。複雑なツールやインフラが成功要因ではない、という経験談。
この 4 年で複数の会社で 3 回もモノレポ設定を行ったフリーランスの経験共有。フロントエンドに限定し、JavaScript/TypeScript エコシステムだけを使っていたので、まだ管理しやすかった。実際に良いモノレポは、内部的にはポリレポのように振る舞い、各プロジェクトが独立して開発/デプロイ/ホスティング可能でありながら、1 つのコードベースに共存し、共通コンポーネント(UI など)も自由に共有でき、一貫したルックアンドフィールを保証できる。実践ガイドとして 参考資料 を推薦。
結局のところ、すべてはケースバイケース。うちの会社は約 40 個の git リポジトリを別個の CI で管理し、ビルド/テスト/パッケージングの後、最終的に統合ファイルシステムイメージを作って統合テストしている。コンポーネント間は Flatbuffers メッセージで通信し、flatbuffers もサブモジュールで管理している。downstream 依存関係の処理は大変だが、progressive enhancement によりある程度の柔軟性は確保している。この場合、マルチレポなのか、サブモジュールの多いモノレポなのか、診断自体が曖昧。モノレポに変えて利点があるかは未知数。結局はトレードオフと、どんな種類の不便を受け入れるかの選択になる。
モノレポツール関連ブログ の著者の経験。人はモノレポの利点ばかり強調しがちだが、実際に成功しているモノレポ運用の複雑さの大半は、裏で devops/devtools チームが負担している。したがって導入は慎重であるべきだが、うまく構築できれば十分な価値を提供する。
うまく管理されたモノレポ体験は素晴らしすぎて、他のワークフローには戻りたくない。しかし、準備不足の「うちもモノレポをやろう」式は悪夢。もし整備済みのモノレポ環境とツールをパッケージとして売れれば、大きなビジネス機会になると思う。
大規模組織では、モノレポが逆にチーム間依存を極端に制限し、コード再利用を低下させることもあるという経験。ライブラリチームが変更しようとすると、下流の利用者をすべて更新する必要があるが、想定外の使い方をしているチームのせいで変更が複雑に絡む(Hyrum's Law)。結局、大企業では内部コピペ、フォーク、厳格なアクセス制御、遅い変更承認などに行き着く。
汎用的に使うライブラリを作るなら、API 設計は慎重にすべき。可能なら API は変えず、変える必要があるなら大規模変更を確実に計画するか、新しい関数に置き換えたうえで旧版を deprecated にするのが望ましい。小規模なコードならコピペでも構わない。
それでもモノレポの利点は、すべての使用箇所を簡単に見つけられ、必要ならアトミックに変更/修正できることだ。
すべてのソフトウェアは依存関係を考慮する必要があり、モノレポはむしろライブラリ側にも利用者側にも、互いを変更する権限を増やす。
モノレポでは自分の状況に合わせて変更しやすいため、コード再利用の確率はポリレポより高い。