@derrickstolee は最近、いくつかの異なる git clone
の方法について検討しましたが、それらの選択肢は実際に Git のパフォーマンスにどのような影響を与えるのでしょうか?あなたのクライアントではどの選択肢が一番速いのでしょうか?あなたのビルドマシンではどの選択肢が一番速いのでしょうか?これらの選択肢はサーバーのパフォーマンスにどのような影響を与えるのでしょうか?もしあなたが GitHub Enterprise Server の管理者であれば、複数の同時リクエストがあった場合にサーバーがこれらの選択肢にどのように反応するかを理解しておくことは重要です。
GitHub では、これらの疑問に答えるためにデータ駆動型のアプローチを採用しました。私たちは、これらの異なるクローンの選択肢を比較する実験を行い、クライアントとサーバーの挙動を測定しました。 git clone
の時間を比較するだけでは十分ではありません。クローンは Git リポジトリとのやり取りの始まりに過ぎないからです。特に私たちは、それぞれのクローンの選択肢が git fetch
のような以後の Git 操作の挙動にどのように影響を与えるのか調べたいと考えていました。
この実験では、以下の疑問に答えることを目指しました:
- それぞれの
git clone
コマンドはどれくらいの速さか? - リポジトリをクローンしたら、以後の
git fetch
コマンドはサーバーやクライアントにどのような影響を与えるのか? - フルクローン、シャロークローン、パーシャルクローンは Git サーバーにどのような影響を与えるのか?これは、GitHub Enterprise Server の管理者にとって重要なことです。
- リポジトリの構成やサイズは、全体のパフォーマンスに何か違いをもたらすか?
実験の結果は、私たちの管理された環境で行ったシミュレーションに基づくものであり、多くの Git ユーザーが使用するかもしれない複雑なワークフローをシミュレートしたものではないことを特に強調しておきます。お使いのワークフローやリポジトリの特性によっては、これらの結果は変わるかもしれません。しかしこの実験は、ワークフローがこれらの選択肢によってどのような影響を受けるかを測定するためのフレームワークとなり得るでしょう。皆さん自身のワークフローを分析したい場合は、GitHub のプロフェッショナルサービスチームまでお気軽にお問い合わせください。
実験結果の概要については、結論と推奨事項を御覧ください。
実験の計画
実験の再現性を最大限に高めるために、サンプルデータにはオープンソースのリポジトリを使用しています。こうすることで、皆さんのリポジトリの構成と実験で使用したリポジトリを比較して、どのシナリオが皆さんのケースに最適かを確認することができます。
私たちは、jquery/jquery
、apple/swift
、torvalds/linux
のリポジトリを使用することにしました。これら三つのリポジトリは、サイズやコミット数、ブロブ、ツリーの数がそれぞれ異なります。
これらのリポジトリを、GitHub Enterprise Server バージョン 2.22 を 8 コアのクラウドマシン上に動作させた環境にミラーしました。Gatling をベースにした社内の負荷テストツールを使用して、テストインスタンスに対する git
リクエストを生成しました。五つの異なるロードジェネレーターを使って、特定のユーザー数のテストを 30 分間実行しました。すべてのロードジェネレーターは git version 2.28.0
を使用しており、デフォルトではプロトコルバージョン 1 を使用しています。プロトコルバージョン 2 は Git の参照の広告処理を改善するだけなので、それがテストに影響を与えるとは考えていません。
テストが完了すると、Gatling の結果と、 ghe-governor
とサーバーの健全性に関するメトリクスを組み合わせてテストを分析します。
テストリポジトリの特性
git-sizer というツールは、Git リポジトリのサイズをさまざまな側面で測定します。今回は特にディスク上の総サイズとオブジェクトタイプごとの数に注目します。下の表は、三つのテストリポジトリの情報をまとめたものです。
リポジトリ | ブロブ | ツリー | コミット | サイズ (MB) |
---|---|---|---|---|
jquery/jquery |
14,779 | 22,579 | 7,873 | 40 |
apple/swift |
390,116 | 649,322 | 132,316 | 750 |
torvalds/linux |
2,174,798 | 4,619,864 | 968,500 | 4,000 |
jquery/jquery
リポジトリは、ディスク容量が 40MB しかないかなり小さなリポジトリです。apple/swift
は中規模のリポジトリで、コミット数は約 13 万件、ディスク容量は 750MB です。torvalds/linux
リポジトリは、オープンソースリポジトリの Git パフォーマンステストの定番です。ディスク容量は 4 ギガバイトで、コミット数は 100 万件近くになります。
テストシナリオ
今回は以下のクローンオプションを検討します:
- フルクローン
- シャロークローン (
--depth=1
) - ツリーレスクローン (
--filter=tree:0
) - ブロブレスクローン (
--filter=blob:none
)
クローン時のこれらのオプションに加えて、フェッチ時に --depth=1
を使って取得することもできます。ツリーレスクローンとブロブレスクローンには、 git fetch
時にダウンロードされるオブジェクトを減らす独自の方法があるので、シャローフェッチをテストするのはフルクローンとシャロークローンだけにしました。
テストシナリオは、T1 から T10 までの 10 種類のカテゴリに分類しました。T1 から T4 は、四種類の異なる git clone
をシミュレートしたものです。T5 から T10 は、これらのクローンされたリポジトリへのさまざまな git fetch
操作をシミュレートしたものです。
テスト番号 | git コマンド | 説明 |
---|---|---|
T1 | git clone |
フルクローン |
T2 | git clone --depth 1 |
シャロークローン |
T3 | git clone --filter=tree:0 |
ツリーレスパーシャルクローン |
T4 | git clone --filter=blob:none |
ブロブレスパーシャルクローン |
T5 | T1 + git fetch |
フルクローンリポジトリでのフルフェッチ |
T6 | T1 + git fetch --depth 1 |
フルクローンリポジトリでのシャローフェッチ |
T7 | T2 + git fetch |
シャロークローンリポジトリでのフルフェッチ |
T8 | T2 + git fetch --depth 1 |
シャロークローンリポジトリでのシャローフェッチ |
T9 | T3 + git fetch |
ツリーレスパーシャルクローンリポジトリでのフルフェッチ |
T10 | T4 + git fetch |
ブロブレスパーシャルクローンリポジトリでのフルフェッチ |
パーシャルクローンでは、新しい参照先にある新しいブロブは、そのコミットに移動して作業ディレクトリ上にそのブロブの内容を設置するまではダウンロードされません。フルクローンとシャロークローンを公平に比較するために、T5 から T10 のすべてのテストで git reset --hard origin/$branch
を実行しています。T5 から T8 では、この追加ステップは大きな影響はありませんが、T9 と T10 ではブロブのダウンロードもコストに含まれていることを保証します。
上記のすべてのシナリオにおいて、一人のユーザーがリポジトリ内のランダムな三つのファイルを繰り返し変更し、他のユーザーがクローンしてフェッチしているのと同じブランチにプッシュするように設定しました。これはリポジトリの成長をシミュレートするもので、git fetch
コマンドは実際に新しいデータをダウンロードすることになります。
テスト結果
それでは、実験結果を見てみましょう。
git clone
のパフォーマンス
完全な数値は下の表に記載しています。
当然のことながら、シャロークローンがクライアントにとって最も高速なクローンであり、次いでツリーレス、次にブロブレスのパーシャルクローン、そして最後にフルクローンと続きます。この性能は、クローンリクエストを満たすために必要なデータ量に比例します。フルクローンにはすべての到達可能なオブジェクトが必要であり、ブロブレスクローンにはすべての到達可能なコミットとツリーが必要であり、ツリーレスクローンにはすべての到達可能なコミットが必要であることを思い出してください。シャロークローンは、リポジトリの履歴が成長してもクローンサイズに影響しない唯一のクローンタイプです。
これらのクローンタイプのパフォーマンスへの影響は、リポジトリのサイズ、特にコミット数に比例して大きくなります。例えば、torvalds/linux
のシャロークローンはフルクローンの 4 倍の速度ですが、ツリーレスクローンは 2 倍の速度しか出ませんし、ブロブレスクローンは 1.5 倍の速度しか出ません。Linux プロジェクトの開発文化では、非常に小さなブロブが推奨されているため、とても効率よく圧縮することができる点に注意してください。他のほとんどの多くのブロブや大きなブロブを持つプロジェクトでは、パフォーマンスの差は大きくなると予想しています。
サーバーのパフォーマンスに関しては、クローンあたりの Git の CPU 時間は、ブロブレスパーシャルクローン(T4)が高いことがわかります。ghe-governor
を使ってもう少し詳しく見てみると、Git CPU 時間が高くなっているのは、主にパーシャルクローンのシナリオ (T3 と T4) で pack-objects
の処理が多くなっているためだとわかります。torvalds/linux
リポジトリでは、ツリーレスパーシャルクローン(T3)では、フルクローン(T1)と比較して、pack-objects
に費やす Git CPU 時間が 4 倍になります。一方、小規模な jquery/jquery
リポジトリでは、フルクローンがパーシャルクローン(T3 と T4) に比べてクローンあたりの CPU 消費量が多くなっています。三つの異なるリポジトリのシャロークローンは、クローンあたりの Git CPU 及び総 CPU の消費量が最も少ないです。
フルクローンがより多くのデータを送っているのであれば、なぜパーシャルクローンの方がより多くの CPU を消費しているのでしょうか?Git がすべての到達可能なオブジェクトをクライアントに送信する場合、ほとんどの場合、データを展開したり変換したりすることなく、ディスク上にあるデータを転送します。しかし、パーシャルクローンやシャロークローンは、クライアントに送信するために、そのデータのサブセットを抽出してパッケージしなおす必要があります。私たちは、パーシャルクローンでのこの CPU コストを削減する方法を調査しています。
本当の 楽しみは、リポジトリをクローンしてユーザーが開発を開始し、コードをサーバーにプッシュする時に始まります。次のセクションでは、シナリオ T5 から T10 までを分析します。そこでは git fetch
と git reset --hard origin/$branch
コマンドに注目します。
jquery/jquery
のクローンパフォーマンス
テスト番号 | 説明 | クローン平均実行時間 (ミリ秒) | クローンあたりの git の CPU 消費時間 |
---|---|---|---|
T1 | フルクローン | 2,000ms (最も遅い) | 450ms (最も多い) |
T2 | シャロークローン | 300ms (T1 より 6 倍高速) | 15ms (T1 より 30 倍少ない) |
T3 | ツリーレスパーシャルクローン | 900ms (T1 より 2.5 倍高速) | 270ms (T1 より 1.7 倍少ない) |
T4 | ブロブレスパーシャルクローン | 900ms (T1 より 2.5 倍高速) | 300ms (T1 より 1.5 倍少ない) |
apple/swift
のクローンパフォーマンス
テスト番号 | 説明 | クローン平均実行時間 (秒) | クローンあたりの git の CPU 消費時間 |
---|---|---|---|
T1 | フルクローン | 50s (最も遅い) | 8s (T4 よりも 2 倍少ない) |
T2 | シャロークローン | 8s (T1 より 6 倍高速) | 3s (T4 よりも 6 倍少ない) |
T3 | ツリーレスパーシャルクローン | 16s (T1 より 3 倍高速) | 13s (T4 と同等) |
T4 | ブロブレスパーシャルクローン | 22s (T1 より 2 倍高速) | 15s (最も多い) |
torvalds/linux
のクローンパフォーマンス
テスト番号 | 説明 | クローン平均実行時間 (分) | クローンあたりの git の CPU 消費時間 |
---|---|---|---|
T1 | フルクローン | 5m (最も遅い) | 60s (T4 よりも 2 倍少ない) |
T2 | シャロークローン | 1.2m (T1 より 4 倍高速) | 40s (T4 よりも 3.5 倍少ない) |
T3 | ツリーレスパーシャルクローン | 2.4m (T1 より 2 倍高速) | 120s (T4 と同等) |
T4 | ブロブレスパーシャルクローン | 3m (T1 より 1.5 倍高速) | 130s (最も多い) |
git fetch
のパフォーマンス
完全なフェッチ性能の数値は下の表にまとめてありますが、最初に我々の実験結果をまとめてみましょう。
最大の発見は、シャローフェッチは最悪の選択肢であるということです。これは特にフルクローンの場合に顕著で、その技術的背景としてはサーバー上の重要なパフォーマンスの最適化を無効にする「シャローバウンダリー」の存在があります。これにより、サーバーはクライアントからみた時に何が到達可能かを見つけるためにコミットやツリーを渡り歩くことになります。フルクローンの場合はより多くのコミットやツリーがクライアント上に存在するので、重複を避けるためにサーバーが行うチェック処理にコストがかかるのです。また、より多くのシャローコミットが蓄積されると、クライアントはそれらのシャローバウンダリーを伝えるために、より多くのデータをサーバーに送信する必要があります。
二つのパーシャルクローンオプションは、git fetch
と git reset --hard origin/$branch
の挙動がそれぞれで大きく異なります。ブロブレスパーシャルクローンからのフェッチは reset
コマンドがわずかに増加していますが、ユーザーの視点から見ると大きな違いが出るほどではありません。対照的に、ツリーレスパーシャルクローンからフェッチすると時間が大幅に増えます。これは、サーバーが git reset --hard origin/$branch
コマンドを満たすために、コミットのルートツリーから到達可能なすべてのツリーとブロブを送信しなければならないためです。
リポジトリが大きくなるにつれてこのような余分なコストがかかるため、シャローフェッチやツリーレスパーシャルクローンからのフェッチはお勧めしません。ツリーレスパーシャルクローンの唯一の推奨シナリオは、コミット履歴にアクセスする必要があるビルドマシンで素早くクローンを作成し、ビルド後すぐにそのリポジトリを削除するというものです。
ブロブレスパーシャルクローンはサーバー上の Git の CPU 消費を多少増加させますが、ネットワークデータの転送量はフルクローンやシャロークローンからのフルフェッチよりもはるかに少なくて済みます。私たちがテストで用いたリポジトリよりも大きなブロブを持つリポジトリの場合は、余分な CPU コストはそれほど重要ではないでしょう。さらに、全てのコミット履歴にアクセスできるので、これらのリポジトリを利用する実際のユーザーにとっては価値の高いものになるかもしれません。
また、テスト中に発見した驚くべき結果も注目に値します。Linux リポジトリの T9 と T10 のテスト中に、私たちのロードジェネレーターはメモリの問題に遭遇しました。これは私たちが実行したような負荷の高いシナリオでは、より多くの自動ガベージコレクション(GC)を引き起こしたためです。Linux リポジトリの GC はコストが高く、すべての Git データを完全にパックしなおさなければなりません。私たちは Linux クライアントでテストしていたので、フォアグラウンドコマンドをブロックしないように、GC プロセスはバックグラウンドで起動されていました。しかし、フェッチを続けているうちに、いくつかのバックグラウンドプロセスが同時に実行されるようになってしまいました。これは現実的なシナリオではありませんが、私たちの人工的負荷テストに影響を与えます。これがテスト結果に影響を与えないように、git config gc.auto false
を実行しました。
注目すべきは、ブロブレスパーシャルクローンはフルクローンよりも頻繁に自動ガベージコレクションを引き起こす可能性があるということです。これは、データをより多くの小さなリクエストに分割したことによる当然の副産物です。私たちは、Git のリポジトリのメンテナンスをより柔軟にするための作業を進めています。特に、完全なパックのしなおしに時間がかかりすぎるような大規模なリポジトリでは、そういった作業が必要になります。この機能のアップデートについては、GitHub ブログで紹介する予定なのでお楽しみに。
jquery/jquery
のフェッチパフォーマンス
テスト番号 | シナリオ | フェッチ平均実行時間 | git reset –hard | フェッチあたりの git の CPU 消費時間 |
---|---|---|---|---|
T5 | フルクローンリポジトリでのフルフェッチ | 200ms | 5ms | 4ms (最も少ない) |
T6 | フルクローンリポジトリでのシャローフェッチ | 300ms | 5ms | 18ms (T5 よりも 4 倍多い) |
T7 | シャロークローンリポジトリでのフルフェッチ | 200ms | 5ms | 4ms (T5 と同等) |
T8 | シャロークローンリポジトリでのシャローフェッチ | 250ms | 5ms | 14ms (T5 よりも 3 倍多い) |
T9 | ツリーレスパーシャルクローンリポジトリでのフルフェッチ | 200ms | 85ms | 9ms (T5 よりも 2 倍多い) |
T10 | ブロブレスパーシャルクローンリポジトリでのフルフェッチ | 200ms | 40ms | 6ms (T5 よりも 1.5 倍多い) |
apple/swift
のフェッチパフォーマンス
テスト番号 | シナリオ | フェッチ平均実行時間 | git reset –hard | フェッチあたりの git の CPU 消費時間 |
---|---|---|---|---|
T5 | フルクローンリポジトリでのフルフェッチ | 350ms | 80ms | 20ms (最も低い) |
T6 | フルクローンリポジトリでのシャローフェッチ | 1,500ms | 80ms | 300ms (T5 よりも 13 倍多い) |
T7 | シャロークローンリポジトリでのフルフェッチ | 300ms | 80ms | 20ms (T5 と同等) |
T8 | シャロークローンリポジトリでのシャローフェッチ | 350ms | 80ms | 45ms (T5 よりも 2 倍多い) |
T9 | ツリーレスパーシャルクローンリポジトリでのフルフェッチ | 300ms | 300ms | 70ms (T5 よりも 3 倍多い) |
T10 | ブロブレスパーシャルクローンリポジトリでのフルフェッチ | 300ms | 150ms | 35ms (T5 よりも 1.5 倍多い) |
torvalds/linux
のフェッチパフォーマンス
テスト番号 | シナリオ | フェッチ平均実行時間 | git reset –hard | フェッチあたりの git の CPU 消費時間 |
---|---|---|---|---|
T5 | フルクローンリポジトリでのフルフェッチ | 350ms | 250ms | 40ms (最も低い) |
T6 | フルクローンリポジトリでのシャローフェッチ | 6,000ms | 250ms | 1,000ms (T5 よりも 25 倍多い) |
T7 | シャロークローンリポジトリでのフルフェッチ | 300ms | 250ms | 50ms (T5 よりも 1.3 倍多い) |
T8 | シャロークローンリポジトリでのシャローフェッチ | 400ms | 250ms | 80ms (T5 よりも 2 倍多い) |
T9 | ツリーレスパーシャルクローンリポジトリでのフルフェッチ | 350ms | 1250ms | 400ms (T5 よりも 10 倍多い) |
T10 | ブロブレスパーシャルクローンリポジトリでのフルフェッチ | 300ms | 500ms | 140ms (T5 よりも 3.5 倍多い) |
これは何を意味するのでしょうか?
私たちの実験では、これらの異なるクローンオプションとフェッチオプションの間で、いくつかのパフォーマンスの変化があることが実証されました。人によって適切な使い方は異なります! 私たちの実験負荷は人工的なものであり、あなたのリポジトリの構成はこれらのリポジトリとは大きく異なるかもしれません。
ここでは、自分の使用法に適したシナリオを選択するのに役立つと思われる共通のテーマをいくつか挙げてみます:
単一のリポジトリのみを使う開発者であれば、最善のアプローチはフルクローンを作成してから、常にフルフェッチを実行することです。多くの大きなブロブのせいでリポジトリサイズが非常に大きい場合は、より素早く使い始めることができるブロブレスパーシャルクローンに移行しても良いでしょう。トレードオフは、git checkout
や git blame
のようなコマンドで、必要に応じて新しいブロブデータをダウンロードするという点です。
一般的に、シャローフェッチの計算はフルフェッチに比べて計算量が多くなります。フルクローンリポジトリでもシャロークローンリポジトリでも、常にシャローフェッチではなくフルフェッチを使用するようにしましょう。
CI ビルドのようなワークフローで、クローンを作成してすぐにリポジトリを削除する必要がある場合は、シャロークローンが良いオプションです。シャロークローンは、最新のコミットで作業ディレクトリのコピーを取得する最速の方法です。ビルドの際にコミットの履歴が必要な場合は、フルクローンよりもツリーレスパーシャルクローンの方がうまくいくかもしれません。torvalds/linux
リポジトリのような大きなリポジトリでは、フルクローンと比べるとクライアント側では時間を節約できますが、Git サーバーの負担が少し大きくなることを覚えておきましょう。
ブロブレスパーシャルクローンは、Git の sparse-checkout 機能を使って作業ディレクトリのサイズを小さくする場合に特に効果的です。この組み合わせにより、作業に必要なブロブの数を大幅に減らすことができます。sparse-checkout では、シャロークローンのために必要なデータ転送を減らすことはできません。
特筆すべきは、torvalds/linux
リポジトリよりもかなり大きなリポジトリは今回テストしていないことです。このようなリポジトリは公には見かけませんが、プライベートリポジトリではますます一般的になってきています。もしあなたのリポジトリが私たちのテストリポジトリとは大きく異なると感じたら、私たちの実験を自分で再現してみることをお勧めします。
ご覧のように、私たちのテストプロセスは、ユーザーが異なるワークフローを持ち、異なるブランチで作業するという現実の状況をシミュレートしているわけではありません。また、この調査で分析した Git コマンドの種類は少なく、ユーザーが日常的な Git に使用するコマンドを表していません。私たちは、これらのオプションがユーザーエクスペリエンスをどのように変化させるのか、全体像を把握するために調査を続けています。
謝辞
この調査のための努力とスポンサーシップに尽力してくださった Derrick Stolee と当社のプロフェッショナルサービスチームに感謝の意を表します。