世界を動かす技術を、日本語で。

生産的なモノレポの要素

概要

  • Developer Productivityチームmonorepo 導入を検討する際の実務的な課題整理
  • 大手企業の事例を 鵜呑みにせず、自社に合った理由と目的の明確化が重要
  • O(change)原則 に基づいたツール設計の必要性
  • ソース管理・ビルド・テスト における現実的な対応策と課題
  • 一歩ずつ地道な改善 が成功の鍵

モノレポ導入の現実と心得

  • Google、Meta、Uber などの大規模成功事例は、自社にそのまま適用できない現実
  • 導入理由 は「一貫性」「組織的な整合性」「共通ツール化」など、自社文化・価値観に根ざしたものに限定
  • 大手の成功ブログ は最終形の話であり、現状の自社リソースや規模では同じ体験は難しい
  • モノレポ導入 は新たな課題も生み出すため、短期的には後退も覚悟
  • エンジニア間の協力コーディング規約統一 など、長期的な組織的メリットを重視

モノレポツール設計の黄金律

  • O(change)(変更範囲のみ)で完結する高速処理 の徹底
  • 従来ツールやプロセスは O(repo)(全体規模依存) が多く、モノレポ拡大でパフォーマンス問題が顕在化
  • 大手企業の独自ツール開発 も、ほとんどがこのO(repo)問題への対応策
  • 設計時は常に「O(change)」を意識 してツール選定・実装

ソース管理の現実的アプローチ

  • git は分散型設計ゆえ、巨大モノレポでは 性能劣化 (git status等)が発生
  • 現状は git/GitHub で十分運用可能、規模拡大時に問題が顕在化
  • Microsoft はgitをフォーク、 Meta はMercurialをフォーク、 Google は独自実装やPerforce利用
  • サブセットチェックアウト (git sparse checkout等)や 仮想ファイルシステム (オンデマンドDL)の導入事例
  • IDL(protobuf/thrift等)による自動生成コード の管理でリポジトリ肥大化に注意

ビルドシステムの選定と実装

  • Bazel はマルチランゲージ・モノレポに強いが、専任チームが必要なレベルの複雑さ
  • 可能な限り 単一言語 でモノレポ運用を推奨、言語標準ビルドツール(Maven/Gradle/CMake/Cargo/Go等)で粘る
  • O(repo)問題 はあるが、意外と長く運用可能
  • ターゲット決定器(determinator) の実装で、最小限のビルド対象抽出が重要
    • Rustなら guppy crate、Goなら go/packagesライブラリ 等で実現可能
    • Bazel/Buck2等の高度なリモート実行・キャッシュは超大規模向け

テスト運用の課題と解決策

  • 全テスト実行 は非現実的、 ターゲット決定器 で影響範囲のみ抽出
  • フレーク対策 として自動リトライ・隔離機能が必要
  • テスト信頼性 が低いとCI失敗率が急上昇、開発者の不満増加
  • 言語標準テストツール では要件を満たせない場合が多い
    • Rustの nextest やJavaの JUnit 等、一部対応ツールあり

継続的インテグレーション(CI)の役割

  • プルリクエスト発生時 に、影響範囲のビルド・テストを効率的に実行
  • O(change)原則 をCIパイプラインにも適用し、全体最適化を図る
  • CIシステム自体のスケールやパフォーマンスにも注意

次のセクションや論点があれば、新たなタイトルで整理して続けてください。

Hackerたちの意見

大手テック企業での経験から言うと、ビルドシステムを管理するには、そのシステム自体に取り組むチームが必要になるんだよね。基盤となるリポジトリ技術もスケールで動かなきゃいけないし、ソースファイルは必要なときにダウンロードされる仮想ファイルシステムを使ってた。この記事で触れられていないことの一つは、ほとんどの開発がデータセンターで動いている開発サーバー(大体50~100コアくらい)か、数時間ごとに良いコミットで更新される短命なコンテナみたいな「オンデマンド」マシンで行われていたってこと。IDEは開発サーバーやマシンと統合されていて、一般的に言語サーバーや他のサービスは事前にウォームアップされていたり、ChefやAnsibleで自動的にセットアップされてた。大きなモノレポをノートパソコンで動かすことはほとんどないよ(例外はモバイルアプリやMac OSアプリとか)。

そうそう、私もそのビルドチームで働いてたかも!多くのユーザーにとって、モノレポの開発環境が特にローカルかリモートかよりも再現性が重要だと思う。リモートの開発サーバーで定期的にイメージを取得する方が確かにやりやすいよね。

この記事で触れられていないことの一つは、ほとんどの開発がデータセンターで動いている開発サーバー(大体50~100コアくらい)で行われていたことだ。私も多くの小さなチームでこれをやってきたよ。エンジニアが開発中に「ローカルに考える」のをやめさせるのはかなり難しい。現代のハードウェアのコストや密度を考えると、開発チームのためにどこかにラックを見つけるのは理にかなってるよね。開発、ステージング、テスト、必要なツールを動かすためのボックスをいくつか作るのは簡単だし、成長する余地もある。インフラに近くて、本番環境に似ていると、モノレポの中のコードがすごく違って見えてくる。 > 大規模なビルドシステムを管理するには、そのシステム自体に取り組むチームが必要になる。これが多くの小さなチームがモノレポに移行するのを妨げているんだ。あなたの10~20人のチームは、GoogleやFB、MSには決してなれない。大規模なビルドシステムの問題は起きない。すべてを維持するのは、20人のチームで非常に複雑な製品がある場合に誰かのパートタイムの仕事になるかもしれないけど、それでも厳しいかも。

大手テック企業のモノレポには2種類あるんだ。一つはこの記事で説明されている「THE」モノレポで、ほぼ全体のコードベースを含んでいて、カスタムVCSやカスタムCI、そして200人のエンジニアチームが支えているもの。UberやMeta、Googleも今はこういうやり方だよね。ここに到達するには何年も苦労が必要。もう一つの「モノレポ」は、個々のチームがプロジェクトを緩く組織化してモノレポに集め始める「マルチレポモノレポ」。フロントエンドの人たちはTurborepoを使いたがって、Bazelが大嫌い。Javaの人たちはBazelを使いたがっていて、他に何があるか知らない。Pythonの人たちはPoetryを諦めた後に好きなようにやってる... 最終的にはこれらが大きなモノレポにまとまるかもしれない。どちらのアプローチも数百万ドルと数百万時間の開発者の時間と努力がかかる。ビジネスリーダーには技術VPが巧みにその努力を正当化して、結果的に開発者たちはその苦労を忘れようとしている。

ほとんどのモノレポは、GoogleやUber、他のテックジャイアンツのリポジトリと同じサイズにはならないってことは覚えておく価値があるよ。毎日新しいサービスを導入する会社もあれば、サービスの数が安定している会社もある。もし会社が100のサービスを持っているなら、VCSのスケール問題は起きないし、LSPはノートパソコンのメモリに全コードベースのタグを収められるし、CIで全てのテストを実行するのもほぼ問題ない。要するに、すべての会社がGoogleの規模になるわけじゃないし、なるべきでもない。

大きなモノレポの会社で元ICだったけど、ポリレポよりモノレポの方が好きだった。これは「THE」モノレポで、会社のサービスグラフやコールグラフ、オーナーシップグラフなどがすごく明確に理解できた。クリスタルクリアで、鮮やかに。ポリレポは部族的な知識みたいなもので、どこに何があるか分からないし、探すこともできない。各チームがそれぞれのやり方でやってるから、新しいコードを引き継ぐのは呪いみたい。コード考古学は、隠された難解な書物の図書館での根本原因分析の冒険みたい。ポリレポはDiscordやSlackのチャンネルに閉じ込められたメッセージや知識みたいで、保持ポリシーが悪い。すべてが暗い隅で萎縮していく。モノレポが数百万のコストなら、ポリレポも別の形で同じくらいかかると思う。モノレポは巨大なメガファウナの大陸。大きなリソース、単一栄養。ポリレポは千の種が生きたり死んだりしていて、繁栄するものもあれば、知られることのないものもあり、大半は完全に暗闇の中。

うちの会社は言語スタックごとにモノレポに移行してるんだ。まあ、悪くない妥協だと思う。

今の職場では、バックエンドが約11個のGitリポに分かれてて、1つの機能が4〜5個のマージリクエストに分かれちゃってて、すごく面倒なんだ。これからモノレポを導入して、全部まとめることを検討する予定なんだけど、リポをまとめられない場合、モノレポ以外の選択肢は何になるんだろう?

モノレポとマルチレポの議論であまり見かけないことの一つは、逆コンウェイの法則が存在するってこと。どちらかを選ぶことで、組織の構造や問題解決の仕方に影響を与えるんだ。例えば、モノレポは共通のインフラチームの間で個々のヒーロー的な行動を促す傾向がある。多くの変更が同時に入るから、共通エリアに触れるものは潜在的な破損が多くなるので、たった一つの「機能」を提供するのにも膨大な努力が必要になる。マルチレポで同じことをするには、数週間にわたっていくつかのPRを調整したり、内部の政治が絡むこともあるけど、それは専用のビルドチームにいない異なる開発者の間で分担されることもある。

あなたの前提は、組織が最初からどちらかに進むことを望んでいなくて、技術的な選択によって後から押し出されるということ?たいていの場合、哲学的な決定(より共有された部分か、より良い分離か)は、リポジトリの扱いを決める前にされていると思う。もし組織が途中で方向を変えたとしても、コードの扱いは根本的にリポジトリの構造を切り替えなくても適応できる。多くの組織はマルチレポだけど、エンジニアはほとんどすべてのコードにアクセスできるし、モノレポチームも自分たちのやっていることを強く隔離することができる。異なるCIルールやデプロイ管理を持つこともできるんだ。

ポリレポの設定で起こることについて、楽観的な見方だね。一般的な代替案(おそらくこっちの方が多いと思う)は、共通エリアに変更が加えられるけど、下流のリポジトリには反映されないってこと。そうなると、みんな異なるバージョンの共通リポに固定されちゃって、数年も古くなると更新が大変になるんだよね。

たくさんの変更が一度に入るから、共通エリアに触れるものは壊れる可能性がすごく高くなる。そのため、単一の「フィーチャー」を提供するための労力が急増する。モノレポの特定の変更が非常に中心的に埋め込まれていて、原子的に行うためには信じられないほどの労力が必要な場合(モノレポを持つ利点)、それでも複数の段階的な変更に分けることができる(「数週間にわたっていくつかのPRを調整する必要があり、内部の政治も絡むけど、それは専用のビルドチームにいない異なる開発者の間で分けられることもある」)。だから、モノレポではマルチリポで説明したのと同じ利点を享受できるし、モノレポのおかげで段階的な変更の展開をより良く把握できるんだ。

もちろん、答えは「状況次第」だよね。私たちのプライベートGitLabリポには約40個のリポがあって、それぞれが独自のCIシステムを持ってる。コンパイルして、テストを実行して、配布用のパッケージをビルドするって感じ。それから、これらの約40個のリポのパッケージからファイルシステムイメージを統合するCIタスクがあって、統合タスクを実行するんだ。多くのコンポーネントは、flatbuffersで定義されたメッセージでお互いに通信してるけど、これ自体もサブモジュールなんだよね。幸運なことに、flatbuffersは段階的な拡張を可能にしてるけど、まあそれは置いといて。要するに、これらのコンポーネントは何らかの相互依存関係を持っていて、遅くとも統合段階でそれが浮き彫りになるんだ。これは実際にマルチリポなのか、それともたくさんのサブモジュールを持つモノレポなのか?モノレポに移行したらメリットがあるのかな(現在のフルインテグレーションのラウンドトリップCI時間は約35分で、多くのコンポーネントは10秒以内でコンパイルとテストが終わるけど)?どうだろうね。すべてはトレードオフだし、何でも機能する可能性があるけど、どれだけのストレスを我慢できるかってことだよ。

モノレポが好きだけど、大きな組織ではチームが他のチームに依存させないようにする逆説的なインセンティブが働くことがあるんだよね。これがコードの再利用を減らすことにつながることもある。ライブラリのユーザーは、ほぼ無限の摩擦をライブラリにかけることができるから、ライブラリチームが変更を加えようとすると、すべての使用箇所を更新しなきゃいけない。でも、ハイラムの法則があるから、ユーザーは本当に変なことをするんだよね。だから、トップ組織にとっては、他の多くのチームが優れたチームの実績のあるライブラリを利用できるのは良いことだけど、ライブラリチームにとってはただの負担になる(共通コードを作るのが彼らの仕事でない限り)。Googleみたいな場所では、内部コピーやフォーク、厳しいアクセス制御リスト、または変更が遅すぎるライブラリができちゃうんだよね。

共有を目的としたライブラリを作るときは、ほんとに一瞬立ち止まってAPIについて考えないとダメだよ。理想的にはAPIは変わらないべきで、もし変わるなら大規模な変更を計画しておくか、新しい関数を使って古いのを非推奨にするべきだね。便利なコードの一部をコピー&ペーストすることに何も問題はないと思うし、すべてが依存するライブラリである必要はないよ。小さいものなら特にね。

このスレッドは、複雑さを商売にしている人たちについての以前のスレッドを思い出させるね。モノレポに移行することで何らかの技術的な犠牲があるという意見をよく見かけるけど、これは全く馬鹿げてる。階層的なファイルシステムの力を理解していないとしか思えない。CI/CDのような大混乱が、設定ポイントを増やすことで簡単になるとは思えない。私にとってモノレポの全体的な目的は、組織全体のための原子的なコミットなんだ。この力は、多くの開発者の努力を調整しようとする時には本当に過小評価できない。1つのリポでリベースして大きな会議を開くのは、N回やるよりずっと楽だよ。たとえチームの人たちが互いに嫌いで、直接協力しようとしなくてもね。モノレポを避ける理由が見当たらない。このシナリオでは、モノレポは有用な管理や人事のツールになるんだ。

この世代の開発者たちの間で、断片化と原子主義への推進がすごく強いね。マイクロサービスや小さなリポジトリがたくさんあって、「モノリス」を恐れて何でも分けちゃう obsession。彼らがやってるのは、組織の問題を将来の技術的な問題に変えてしまう複雑さの塊を作ってるだけで、同時に自分たちが構築しているソフトウェアシステムの内部依存関係を認識していない。幸い、今の職場はそんな感じじゃないけど、前の職場はそうだった。protobufスキーマファイルのフィールドを更新するような簡単なことに無駄な時間を費やしてたのには信じられなかったよ。

モノレポの暗黙の真実は、みんながトランクでの開発にコミットしていて、トランクは決して壊れてはいけないってこと。これの結果、実行はランタイムで設定可能でなければならない。つまり、古いコードと新しいコードが並んでいる状態でのフィーチャーフラグや設定オプションが必要になる。モノレポを持っていても、各チームが自分たちのブランチで作業して、四半期のリリースプロセスが始まる1週間前にトランクに統合しようとすると失敗することがある。もしコアチームがマスターで新しいテストをすべて使った新しいバージョンの製品を構築して、すべてのコミットで緑色になっても、顧客がv2に準備ができていないためにコードがリリースできないこともあるし、v1の互換性を保つ必要があるんだ。

まだ四半期ごとのリリースをしてるところがあるんだね。それを解決する方がモノレポより重要な気がする。

過去4年間で、異なる会社のために3つのモノレポを契約作業として設定したよ。経験はポジティブだったけど、ツールを知っておくことが重要だね。私たちのモノレポはフロントエンドアプリケーション専用だったから、JavaScript/TypeScriptエコシステムに完全に依存できたおかげで、管理が楽だった。学んだことは、良いモノレポは「変装したポリレポ」のように振る舞うことが多いってこと。内部の各プロジェクトは独立して開発、ホスティング、デプロイできるけど、同じコードベースに共存しているんだ。主な利点は、すべてのプロジェクトがコード(UIコンポーネントなど)を共有できて、製品全体で一貫した見た目や感触を確保できることだね。もっと実用的なガイドが欲しいなら、[0]をチェックしてみて。 [0] https://www.robinwieruch.de/javascript-monorepos/

私は、Molnett(サーバーレスクラウド)がBazelで構築された厳格なモノレポを採用したことが、約1.5人のフルタイムエンジニアの小さなチームでプラットフォームを作る上で非常に重要だったと確信している。私たちは、Tilt + Bazel + Kindを使って、ローカルのラップトップ上でKubernetesオペレーターを含むプラットフォーム全体を立ち上げることができる。これはMacとLinuxの両方で動作する。つまり、個人の開発クラスターを必要とせずに、ほぼすべての機能をローカルで検証できるってこと。私たちはこのツールレイヤーを作ったから、リポ内でgokubectlを実行すると、それがBazelによってビルドされて提供される。これにより、私たちは常に同じバージョンのツールを使えて、ローカルインストールを維持する必要がなくなった。これは本当に大きな恩恵だよ。少しの努力が必要だったし、今後も継続的に努力が必要だけど、元GoogleのSREがチームにいることが重要だった。今後は別の方法で働きたくないな。編集:私たちのリポは基本的にGolang、Bash、Rustだけだよ。

ここでの疑問は、なぜ2人の開発者でマイクロサービスパターンとK8sを使ってるのかってことだよね。そのパターンはそんな小規模な運用には向いてなくて、余計な複雑さを増やすだけだし。1.5人のエンジニアがいる時に、どれを選んでも本当に重要なの?その規模では、両方のエンジニアがビルドプロセスをしっかり理解してるから、頭の中で全部把握できるし。私もその規模ではリポを全く使わなかったし、Dropboxにリポを保存したり、VCSやSVNを使ったりしても、全然問題なかったよ。成功に何もプラスになってないと思う。ちなみに、開発者が自分のノートパソコンでリポを立ち上げるのは今でも普通だし、2人の開発者でK8sを使うような無駄なことをしてない多くの開発ショップでは特にね。実際、キャリアの初めには、10人くらいの開発者と一緒に古いMSのやつで働いてたけど、ファイルをロックしないと他の人が使えなかったんだ。ファイルをチェックアウトして変更できるようにしてたけど(git checkoutとは全然違う)、そうしないと自分のドライブでは読み取り専用だった。ビルドは手動でパラメータを指定して実行しなきゃいけない大きなVBスクリプトだったけど、それでもちゃんと動いてたよ。SVNに移行した時も、古いシステムの方が良かったって文句があったけど、ファイルをロック解除してもらうために走り回らなきゃいけなかったのが馬鹿らしかった。特に、数日間オフィスにいない開発コンサルタントがいたから、ファイルを解除してもらうために管理権限を持ってるおじいさんに頼まなきゃいけなかったんだ(実際はそんなに年取ってなかったし、髭もなかったけど)。

約1.5人のフルタイムエンジニアの小さなチーム そうだね、1.5人のFTEならリポは1つだけでいいと思う。Bazelの経験はすごく悪かったけど、完全に避けるべきとは思わない。超大規模なマルチチームプロジェクトでは価値があるかもしれないけど、2人未満のFTEには過剰すぎる気がする。Kind(とTiltかも?)を使えば、Bazelなしでも必要なことはできると思う。 > 「このツールレイヤーを作ったから、リポ内でgokubectlを実行すると、Bazel自体がビルドして提供してくれる。これで、みんなが常に同じツールのバージョンを使えるし、ローカルインストールを維持する必要もない。」 Goはgo.modでそれをやってくれてるし、kubectlもGoプログラムだから、同じように実現できるよ。 > 「元GoogleのSREがチームにいるのは重要だった」 元Googleの人の給料を考えると、他に何人チームメンバーを雇えるかな。Bazelのメンテナンス費用が今後も価値があるといいな、心からそう願ってるよ。

私もあなたと似たような状況で、Bazelに全力投球してるおかげでかなりのメリットを感じてるよ。

「私たちはこのツールレイヤーを作ったから、リポジトリ内でgokubectlを実行すると、それがBazelによってビルドされて提供されるんだ。」 これのおかげで、みんなが常に同じツールのバージョンを使えるし、ローカルのインストールを管理する必要もない。今はbazel runを実行しなきゃいけないけど、あなたの解決策の方がずっと良さそうだね。あなたのはどうやって動いてるの?

大きなモノレポで働いたことがあるけど、最初は好きだったけど、今はちょっと疑問に思ってる。依存関係が多すぎて、今はチームが他のチームのコードをライブラリやAPIとして実際のリリースサイクルで再利用できるべきだと思う。「モノレポのどこかのランダムなビルドターゲットのHEADに依存しよう」なんてのはダメで、「特定のライブラリやAPIのリリースバージョンに依存しよう」っていうのが正しいと思う。この規律を採用すれば、基本的にモノレポは必要ない。各チームが自分のリポを持って、他のものをサードパーティとして依存できるようになる。これには多少の摩擦が生まれるけど、他の種類の摩擦を減らすことができるし、全体的にはいい妥協だと思う。

大きなモノレポで働いたことはあまりないけど、あなたのコメントはすごく興味深い。HEAD(あるいは実際にはコミットSHA)を指し示す能力は、モノレポを使わないことの良さの一つだと思うことがあるんだ。

これって、めっちゃ断片化を生むだけだよね。複数のチームが複数のバージョンに依存し始めたら、もう終わりだよ。複数のバージョンを維持しなきゃいけなくなって、それぞれのクセやバグに悩まされることになる。健康的なモノレポのためには、1つのバージョンルールが一番大事だと思う。