プログラミング
あらゆる規模でのGit
要約
この記事は、Gitリポジトリを大規模にホスティングすることの困難さについて論じています。Gitの分散設計は、個々の開発者のワークフローには適していますが、中央集権的なホスティングには課題をもたらします。特に、Gitの基本的なストレージ形式であるpackfileは、スケーラビリティと可用性の両面でボトルネックとなります。記事では、packfileに依存しないアプローチや、GitHubが直面したファイルシステムのスケーリング問題など、過去の試みと課題について解説しています。
全文翻訳
ブログ / リサーチ
Gitリポジトリを大規模にホスティングすることは悪夢です。Linus Torvaldsが最初のバージョンの「地獄の情報マネージャー」(これはGitの実際のタグラインです、調べてみてください)を設計したとき、彼は非常に特定のユースケースを念頭に置いていました。それは彼自身のユースケースです。彼はLinuxカーネルの開発に使用されていた分散バージョン管理システムであるBitKeeperに取って代わりたかったのです。もちろん、その代替品も分散型である必要がありました。カーネルは珍しいソフトウェアプロジェクトです。それは非常に分散化されており、多くのサブシステムに多くの異なるメンテナーがいます。分散バージョン管理システムは、このワークフローに自然に適合します。
20年後、Gitは業界標準になりましたが、その分散的な性質は利点というよりもむしろ障害であることが真実です。平均的なオープンソースソフトウェアプロジェクトは、分散ワークフローで運営されていません。平均的な企業も決してそうではありません。それらは分散モデルの多くの利点(オフラインで作業できる、プッシュを遅延できるなど)を使用しますが、中央集権的なホストに非常に依存しています。そして、Gitリポジトリのホスティングは、信じられないほど難しいことであることが判明しました。
#Gitの何が難しいのか?
Gitリポジトリを大規模にホスティングする際の課題は、Git自体の設計に固有のものです。分散バージョン管理システムとは、リポジトリのすべてのインスタンスが同一であることを意味します。Gitサーバー上のリポジトリと、開発者のラップトップ上のリポジトリとの間に特別なものはありません。一見すると、これはGitリポジトリのホスティングを単純にするように見えるかもしれませんが(リポジトリのディスク上のコピーの前にHTTPデーモンを置くだけでGitサーバーが完成します!)、実際には、スケーラビリティと信頼性に関する多くの困難な課題があり、実際にはその逆です。
通常のGitリポジトリでは、コードとメタデータ(ファイル、コミット、ツリー)はpackfileに圧縮されて保存されます。これは単純なバイナリシリアライゼーション形式であり、ローカルマシンで扱うには便利ですが、サーバー上で大規模に管理するには理想的ではありません。PackfileはGitストレージとGitネットワーキングの基本的な構成要素です。リポジトリからデータをプッシュまたはフェッチすると、それはpackfileとして転送されます。これはGitの設計上の仕組みですが、そうする必要はないと考えるのは公平でしょう。結局のところ、あなたはGitクライアントを制御できません(少なくともユーザーを煩わせたり、多くの摩擦を加えたりすることなく)。しかし、自分のサーバーの壁の中では、何でもできます。Linusが来てチェックすることはありません。唯一の制限は、すべてのGit操作でネットワーク経由でpackfileを送受信する必要があるということです。
長年にわたり、Gitリポジトリを大規模にホスティングしようとした企業は、このpackfileベースの設計が可用性とスケーラビリティの両方にとって大きな制限であることを発見しました。Packfileは、Gitがアクセスするためにファイルシステム上に存在する必要がある大きなバイナリファイルです。ディスク上のリポジトリの前にHTTPサーバーを置くという単純なアプローチは、非常に低い天井を持っています。理想的には、リポジトリを多くのディスクと多くのマシンに存在させたいでしょう(これにより、多くのGit操作を並列実行でき、サーバーがクラッシュしてもリポジトリを利用可能に保つことができます)。しかし、それをどうやって行うのでしょうか?これを達成するには、一般的に3つの可能なアプローチがあります。複雑さが増すにつれて:ファイルシステムを分散する、packfileを分散する、またはGit自体を分散する。
# PackfileなしのGit
Gitはコンテンツアドレス可能なデータストアです。Gitリポジトリ内のすべてのオブジェクト(blob、tree、commitなど)は、そのコンテンツのSHA-1によってキー付けされます。これは、分散キーバリューストア(キーはSHA-1、値は実際のオブジェクト)に非常にうまくマッピングされ、リポジトリのストレージをスケールアウトするためのクリーンな方法を提供できるものです。しかし、実際にはこれはうまくいきません。問題は次のとおりです。Gitリポジトリの実際のレイアウトは有向非巡回グラフ(DAG)です。SHAで任意のオブジェクトをルックアップできますが、リポジトリで最も些細な操作を実行するためでさえ、DAGをステップバイステップで歩く必要があります。
コミットDAG
ツリー/メイン → c8f3?コミット · c8f3ネットワーク↓古いコミット
オブジェクト 0/54 · ラウンドトリップ 0
キー/バリューストア
aa42a112c8f3e816f0214b70e8c487ab19b4d5c277b22dc83f7dc43040c22d6e21aa729a0db50f627cf191fe3e81b19092d080a5f31134e06c811f6e7a196e42e147b9086a70ce192ee45d83f7a9b02ec2a70c49b8e25ac074b1a93d47dd9b519d2a8c14d431ef09e205
リポジトリの最近の変更をリストするなどの操作を行いたい場合、そのコミットを処理する必要があります。コミットを処理すると、そのツリーのルートへのポインタが得られます。そのツリーから、各ファイルと各サブツリーへのポインタを取得します。元のコミットから、その親(履歴でその前に来るもの)へのポインタを取得します。極めて重要なのは、このウォークの各ステップで、前のポインタを取得するまで次のポインタの値を知ることができないということです。各フェッチで分散ストアへのラウンドトリップが必要な場合、物事は非常に速く非常に高価になります。
このオブジェクトレベルでのGitの分散アプローチは以前から何度も試みられてきましたが、多くの場合、大規模では失敗しました。最も有望な実装は、私の元メンターであるShawn PearceがGoogleのバージョン管理システムチームで働いていたときに試みたものです。彼の方法は、オブジェクトを分散ハッシュテーブルに保存することでした。これは、JGit、JavaのカスタムGit実装のおかげで可能になりました。良い古いJavaライブラリのように、JGitは通常のGitリポジトリのすべての詳細を抽象化するための十分なインターフェースとファクトリとインターフェースファクトリを提供し、オンディスクのpackfileをDHTに置き換えることも含みます。システムは機能し、結果は通常のGit操作には十分でしたが、Gitプロトコルの制限(サーバー上のデータの保存方法に関係なく、ネットワーク経由でpackfileを送信する必要がある)により、git cloneのパフォーマンスが悪化し、設計を破棄せざるを得なくなりました。
# GitHubとファイルシステム
GitがLinuxカーネルのバブルから抜け出して数年後、サンフランシスコで新興企業が誕生しました。GitHubは2008年にソーシャルコーディングプラットフォームとして設立され、「Gitリポジトリホスティング:もう面倒なことではない」という先見の明のあるタグラインを持っていました。これも冗談ではありません、調べてみてください。2008年当時、Gitの分散設計にもかかわらず(あるいはそれゆえに)、Gitリポジトリをユーザーフレンドリーにするためには実際には中央集権的な方法が必要であり、それを実行するのは非常に困難であるという広範なコンセンサスがありました。GitHubはその変更に乗り出しました。
そのプラットフォームは、Railsモノリスとして(そしてほとんど今でも)開始されました。最初のバージョンは、単一の、しかし強力なマシンで、Rubyサーバーと、その横にあるディスク上のリポジトリのコピーで実行されていました。Railsアプリのスケーリングは簡単です:インスタンスをさらにデプロイします。しかし、この特定のケースでは、Gitが関与しているため、彼らはすぐに私たちがここで解決しようとしている繰り返し質問に直面しました。Railsアプリがディスク上のGitリポジトリにアクセスする必要がある場合、それらのコピーをさらにデプロイするにはどうすればよいですか?倹約家のならず者の集団であった初期のシステムエンジニアは、スケーリングの問題を解決できる最も単純なアプローチを試しました。考え方は、ファイルシステムを分散することに焦点を当てる(packfileやGit自体ではなく)ことで、Railsアプリを変更せずに済み、奇妙なことをする時間ではなく、増え続けるユーザーベースのためにさらに多くの機能をリリースすることに時間を費やすことができるということでした。非常に実用的です。それはうまくいきませんでした。
チームはGitデータ用の分散ファイルシステムについて多くの方法を試しました。最も明白な方法である、すべてのリポジトリを中央集権的なサーバーに保存するためにNFSを使用することは、すぐに却下されました。Gitのデフォルトの実装は、ファイルシステムのセマンティクス(ロック、ティアリング、読み取り、同期など)について多くの仮定をしており、遅い開発者のラップトップのローカルファイルシステムで良好なパフォーマンスを保証しますが、ネットワークファイルシステムでの動作には注意を払っていません。それは遅く、バグがありました。さらに、(率直に言って、後から考えると恐ろしい)テクノロジーを使用して、fiを複製する試みが行われました。