Gitのスケーリング(と、その背景)

数年前、Microsoftは、社内全体のエンジニアリングシステムを活性化させるため、数年間にわたる投資を行う決定をしました。私たちは山のような数のチームを抱える大企業です。チームはそれぞれ、担当のプロダクト、独自の優先順位、プロセス、ツールを持っています。”共通の”ツールもありますが、チームによって様々に異なる点も多く、内部で開発した単発のツールも数え切れないほどあります(「チーム」とは社の部門のようなもので、数千のエンジニアの集まりです)。

この状況にはたくさんのマイナス面があります。

  1. 似たようなツールを構築しているチームがいくつもあり、巨額の冗長な投資が生まれている
  2. 「クリティカルマス(損益分岐点を超える生産量、普及率)」に向けた設備投資ができない
  3. 皆がバラバラのツールやプロセスを用いているため、従業員が異動しにくい
  4. 組織の垣根を越えてのコード共有が難しい
  5. “MS限定”ツールの過多のため、新しいものを導入すると干渉が起こる
  6. これ以外にも、まだまだ…

そこで、”One Engineering System(1ES)”と呼ぶ取り組みを始めました。つい昨日の1ESデーには、数千のエンジニアが集まって、現在の状況を確認し、次の計画を議論して、進捗をたたえ合いました。それは想像を超える良いイベントでした。

それは別として… 疑問に思われたかもしれません。「随分前からMicrosoftは、TFSを使ってるって言ってたよね? ウソだったってこと?」 いいえ、ウソではありません。常に5万人以上がTFSを使ってきたのですが、いつでも何にでも使用したのではないということです。常に使っている人もいれば、作業項目のトラッキングのみに利用する人も、バージョンコントロールだけ使う人も、ビルドだけ行う人もいたのです。実質、TFSが扱うあらゆる機能の社内バージョンもあり(しかも多くの場合2種類以上)、誰かがどこかで使っている状態で、正直言ってちょっとしたカオスでした。しかし、集約させてよく吟味すれば、TFSは他のいかなるツールセットよりも支持してもらえるはずだと自負しています。

また、ここではエンジニアリングシステムという用語を非常に幅広い意味で使っていることを伝えておきます。そこに下記の内容が含まれますが、すべてではありません。

  1. ソース管理
  2. 業務管理
  3. ビルド
  4. リリース
  5. テスト
  6. パッケージ管理
  7. 遠隔計測(テレメトリ)
  8. テスト配信
  9. インシデント管理
  10. ローカライゼーション
  11. 脆弱性検査
  12. アクセシビリティ
  13. コンプライアンス管理
  14. コード署名
  15. 静的分析
  16. そして、まだまだたくさん

話を元に戻します。このプロジェクトに乗り出した時、何を目指すか、何を第一にするか、など白熱した議論を交わしました。ご存知のとおり、開発者は意見など決して持たないものです:) 一度にすべて対処しようとしても、みじめな失敗をするだけです。そこでまずは3つの問題から取り組むことを確認し合いました。

  • 業務計画
  • ソース管理
  • ビルド

細かい理由は述べませんが、これらが基盤となり、他の数多くの要素がこの上に統合されたり、構築されたりします。ここから始めるのは当然でしょう。そして、私たちのプロダクトのサイズ(中には億単位の行のコードも)ゆえに、ビルドの時間、信頼性といったあたりに大きな不満があることが後に分かってきます。

その後、この最初の3つの投資は大きくなり、様々なレベルで、1ESの取り組みは社内のエンジニアリングプロセスのほぼすべての面とつながりました。

いくつか、興味深い柱を立てることができたのですが、その一部を紹介します。

クラウドこそが未来である - 私たちのインフラとツールの多くが内部でホスティングされています(TFS含む)。今回合意したのはクラウドこそが未来だということです。モビリティ、管理のしやすさ、発展性、弾力性、考え得るあらゆる理由がその根拠です。数年前はまだ異論がありました。Microsoftが社の全IPをどうやってクラウドに置くのか? パフォーマンスは? セキュリティは? 信頼性は? コンプライアンスとコントロールは?… 時間を要しましたが、結局はクリティカルマスがその概念にOKを出した、と言えるところまで来ました。そして数年が経過し、その決断は益々理にかなっていき、誰もが喜んでクラウドに移行するようになっています。

ファーストパーティ == サードパーティ - これは内輪で使っている表現で、「なるべく、出荷するものを自分たちでも使い、自分たちで使っているものを出荷しよう」という意味です。もちろん100%ではありませんし、常に一致するわけでもありませんが、これは方向性の話であり、「他のやり方を選ぶまともな理由がない限りは、この方法を取ろう」という前提です。

Visual Studio Team Servicesが基盤 - 私たちはTeam Servicesを背骨に据えることにしました。エンジニアリングシステムを結びつける骨組み、つまり、そこから情報を取り、何にでもアクセスできるようなハブが必要です。そのハブはモダンでリッチで、拡張性を備えていなければなりません。どのチームも、エンジニアリングシステムに独自のコントリビュートを行ったり共有できたりする必要があります。Team Servicesはその要件にぴったりでした。ここ数年の間に、Microsoft内でTeam Servicesにコミットするユーザは数千から5万以上にまで伸びています。TFSの事例と同様、まだ全チームがあらゆることに使っているとは言えませんが、それを目指す動機は強くなっています。

Team Servicesの業務計画 – Team Servicesを選んだので、当然それに紐づいた業務管理機能を選択することになりました。Windowsグループのようなチームを数千のユーザ、数百万の作業項目と共に1つのTeam Servicesアカウントに組み込んだのです。とはいえ、それを実行するには、相当のパフォーマンスとスケーリングが求められました。この時点で、実際にMicrosoftの全チームがこの移行を行い、全エンジニアリング作業がTeam Servicesの中で管理されるようになりました。

Team Services BuildオーケストレーションとCloudBuild – このトピックに関しては、ここには書き切れませんので掘り下げません。まとめると、Team Services Buildサービスをビルドオーケストレーションシステムに、Team Services Buildの管理エクスペリエンスをUIに選びました。また、社内でも特に大きなコードベースのために新しく”makeエンジン”(未出荷)を構築したのですが、これはスケーラビリティが極めて高く、粒度の高いキャッシングを行う、並列処理やインクリメンタルな処理を行うものにしました。これにより数時間のビルドを数分間にまで落とせるのを確認しています。本件についてはまた別の記事で説明しましょう。

背景についてはこのくらいにして、本題に入ります。:)

Gitでソース管理をする

おそらく、最も物議をかもしたのはソース管理に何を使うか、という決断です。元々、Source Depotと呼ばれる社内ソース管理システムがあり、2000年代初期までは実際全員が使っていました。時を経てTFSとそのTeam Foundation Version Controlソリューションが社内の大勢を占めるようになりましたが、WindowsやOfficeのような最大規模のチームには普及しませんでした。単に、そのような大所帯に移行をさせるコストが高過ぎるとか、2つのシステム(Source DepotとTFS)に大した違いはない、と判断された、とか、理由はたくさんあると思います。

しかし、ソース管理システムには、強い忠誠心が生まれがちです。他のどのような開発ツールよりもその傾向があるので、TFVC、Source Depot、Git、Mercurialなどのツールをめぐって激しい口論になり、正直な話としては、意見が一致しないまま決断をしました。そうするしかなかったのです。数多の理由から、Gitを標準とすることに決めました。今、時間が経てば経つほど、この決断の支持者は増えています。

Gitを選んだことに対してたくさんの異論がありましたが、最もはっきりしていたのはスケールでした。Microsoftと同規模のサイズのコードベースを扱っている企業は多くはありません。とりわけ、WindowsとOfficeは巨大です(さらに他にもあり)。数千のエンジニア、数百万のファイル、それを常時ビルドしている幾千ものビルドマシン。全く気が遠くなります。ちなみに、この記事内で言うWindowには、大雑把に言って、Windows PC版、Windows Mobile、Windows Server、HoloLens、Xbox、 IOTなども含まれると考えてください。そして、Gitは分散型バージョン管理システム(DVCS)です。リポジトリ全体とその全履歴をローカルマシンにコピーして使います。Windowsにしてみれば噴飯ものです(実際、随分笑われました)。TFVCとDepotは、どちらも膨大なコードベースとチームを想定して入念に最適化を重ねてきています。Gitは決してこのような問題に適用されたこともなく(またおそらく、この桁外れの規模にも)、絶対に機能しないだろう、と大勢が断言しました。

最初に、リポジトリをいくつ持たせるかが、大きな議論になりました。企業全体で1つにするのか、小さなコンポーネントごとに持たせるのか。広大な領域です。Gitは小さめのリポジトリを大量にさばくのが非常に得意だということは分かっていました。そこで、社内の巨大なコードベースをたくさんの維持可能なリポジトリに組み入れるにはどうすればいいか、長い時間をかけて研究しました。フム。20年分の巨大コードベースの中で作業した経験はありますか? さかのぼってそれを小さなリポジトリに分解しようとしたことは? もうお分かりでしょう。コードを分解するのは実に困難です。多額のコストもかかります。そのレベルの変更のリスクは青天井なのです。そして、1人のエンジニアが広範囲のコードをすべて一掃するような変更を行う可能性も想定しなければなりません。数百のリポジトリにまたがってその作業の段取りをつけるのはほとんど無理に思えます。

さんざん考え抜いた末、私たちの取るべき戦略を、”コードの特徴に基づく正しい数のリポジトリ”に決めました。分離可能なコード(マイクロサービスなど)は、複数の独立したリポジトリにするのに理想的です。分離できず(Windowsコアなど)、1つのリポジトリとして扱うべきコードもあります。強調したいのは、それは、コードの分解が難しいから、という理由だけではないということです。高度に関係し合っている大きなコードベースの場合、実際にコードベース全体をまとめて扱うほうが良いのです。いつか、Bingのコンポーネント化の苦労についても話したいと思います。コアのBingプラットフォームをパッケージに落とし込もうとして、バージョン管理に問題が起きたのです。現在Bingチームは先の戦略を敬遠しつつあります。

つまり、数千人の開発者が利用する数百ギガバイトの数百万のファイルのコードベースで機能させるため、Gitをスケーリングするところから着手しなければならなかったのです。ちなみに、Source Depotでも全Windowsコードベースを扱えるほどスケールしませんでした。スケールできるように40+のデポに分けていたのですが、その上にレイヤーが構築されていたので、ほとんどのユースケースで1つのデポのような扱いでした。その抽象化は完璧ではなく、当然干渉を起こしました。

Gitのスケールにおいて、私たちは少なくとも2度、間違った方向を取りました。最も広範囲に及んだのは、たくさんのリポジトリを1つの”スーパー”リポジトリにつなぎ込むためにGitサブモジュールを使ったことです。詳細は省きますが、6か月間その作業をした後で「このやり方ではダメだ」と気づいたのです。エッジケースが多すぎ、あまりに複雑で脆弱なのです。私たちには、ほとんどすべてのGitツール類をサポートする安定したソリューションが必要でした。

1年近く前、いったん方向性をリセットし、どのようにすればGitをWindowsのコードベース全体(増加分の見積もりと履歴を含む)を保持するシングルリポジトリとしてスケールさせ、開発者とビルドマシンの全体を支えられるのかという問題に集中することにしました。

そこで、Gitを”仮想化する”というアプローチを試しました。通常、Gitはクローンすればすべてをダウンロードします。しかしそれができない場合は? 必要なものだけダウンロードできるよう、Git管理下のストレージを仮想化してみるとどうでしょうか。そうすれば、300GBの大きなリポジトリのクローンもとても速くなります。Gitコマンドを実行したり、自分の登録のファイルを読んだり書いたりする時、システムはクラウドからシームレスにコンテンツを取得します(そしてローカルにストアし、将来そのデータへのアクセスはすべてローカルにします)。1つ欠点は、オフラインで動かないケースが出てくることで、オフラインでの作業が必要な時は、ローカルにファイルが現れるようにすべてに”touch”しなければなりません。それ以外には何も問題はありません。100%信頼できるGitのエクスペリエンスを享受できます。私たちの巨大コードベースには、それで十分です。

有望なアプローチだったので、プロトタイプを作り始めました。このプロジェクトは、Gitバーチャルファイルシステム、GVFSと呼ばれました。目標は、なるべくgit.exeを変えないことです。大参事を避けるため、絶対にGitをフォークしたくなかったのです。そして、コミュニティが後で私たちのコントリビューションを取り消せないような変え方も避けたいと思いました。そこで、可能な限り、仮想ファイルシステムドライバを使い、Gitの管理下で、微妙なバランスに気を配りながら作業を進めました。

ファイルシステムドライバが仮想化したのは基本的に2つです。

  1. .gitフォルダ – これはパックファイル、履歴などがストアされている場所です。初期化(git init)時はこのフォルダが”すべて”です。これを仮想化し、必要な時に必要なファイルだけをpullできるようにしました。

  2. “ワーキングディレクトリ(作業フォルダ)” – 実際にソースを編集したり、ビルドする場所です。GVFSはワーキングディレクトリを監視し、タッチしたファイルをすべて自動的に”checks out”します。全ファイルがそこにあるように感じられますが、実際にアクセスしない限り、負荷はかかりません。

作業を進めるにつれ、ご想像のとおり、多くを学びました。中でも、スマートなGitサーバは不可欠でした。必要以上にクライアントに送信しないよう、Gitファイルを最適な方法で固めなければならないからです。これをローカルの参照の最適化と考えてください。このために、Team Services/TFS Gitサーバをかなり増強しました。また、Gitには、本来必要のないものにタッチするシナリオがたくさんあることも知りました。以前はすべてがローカルで、適度に小さいリポジトリだったため動作が速く、全く問題なかったのです。しかしタッチすることで、それがサーバからダウンロードされたり、6百万個のファイルがスキャンされたりするようになると、話は違います。そこで、Gitのパフォーマンス最適化に重点的に投資してきました。その多くは”通常の”リポジトリにもある程度の利点をもたらしましたが、巨大リポジトリには欠かせないものばかりでした。私たちは、この件に関する多くの改善をGit OSSプロジェクトに送信しており、彼らとは良い関係値になっています。

では話を今日に進めて、状況はどうでしょうか。うまくいっています! VS Team Servicesでホスティングしている単一のGitリポジトリ内に40+ Windows Source Depotサーバの全コードを収めました。そして、実に便利です。数分で登録し、通常のGitのオペレーションを数秒で実行できます。そして、どう見ても透明性が高いですよね。誰もが知るただのGitです。開発者たちははいつものツールを使って自分たちの仕事を続けています。あなたのビルドも当然のように機能します。想像以上に素晴らしく、魔法のよう!

付け加えると、このアプローチは大きなバイナリファイルに向いているという利点もありました。LFSのように新しいメカニズムでGitを拡張するのではありません。大きなバイナリファイルを他のファイルと同じように扱えるのです。ただ、ダウンロードするのは実際にタッチした部分だけです。

Git Merge

今日、Brusselで開催されたGit Mergeカンファレンスで、Saeed Noursalehiが私たちのプロジェクトについて発表しました。かなり詳細にわたり、私たちが何をして何を学んだかを共有したのです。同時に私たちは、すべての仕事をオープンソースにしました。導入を考えている追加のサーバプロトコルも含まれています。GVFSプロジェクトはこちらで、Microsoft GitHub organizationのGit.exeでは私たちが行った変更を確認することができます。 GVFSは新しいWindowsフィルタドライバ(LinuxのFUSEドライバに相当)に依存するのですが、GVFSを試してもらえるよう、Windowsチームと協力してその一部を使えるようにしています。詳細とリソースは Saeedのブログ投稿を読んでください。チェックするだけでなく、ぜひインストールして試してみてください。

プロジェクトが機能しているのを喜んではいるものの、まだたくさんの案件が進行中です。完了した部分は1つもありません。コンセプトは明示できたと思いますが、まだまだ実現しなければならない事案も多いのです。今、この時期にこの発表を行ってソースを公開するのは、Gitが巨大なコードベースにスケールするよう、コミュニティと関わって協働していきたいからです。

長くなりましたが、何かしら発見していただけたなら嬉しいです。私も、Microsoftの1ES、Gitのスケールにとてもワクワクしています。