正式なドキュメントは英語版であり、この日本語訳はAI支援翻訳により作成された参考用のものです。日本語訳の一部の内容は人間によるレビューがまだ行われていないため、翻訳のタイミングにより英語版との間に差異が生じることがあります。最新かつ正確な情報については、英語版をご参照ください。

モノレポのパフォーマンス改善

モノレポとは、サブプロジェクトを含むリポジトリです。単一アプリケーションには、相互に依存するプロジェクトが含まれていることがよくあります。たとえば、バックエンド、ウェブフロントエンド、iOSアプリケーション、Androidアプリケーションなどがあります。モノレポは一般的ですが、パフォーマンス上のリスクをもたらす可能性があります。一般的な問題点:

  • 大規模なバイナリファイル。
  • 長い履歴を持つ多数のファイル。
  • 多数の同時クローンとプッシュ。
  • 垂直方向のスケール制限。
  • ネットワーク帯域幅の制限。
  • ディスク帯域幅の制限。

GitLab自体がGitに基づいています。GitストレージサービスであるGitalyは、モノレポに関連するパフォーマンス上の制約を受けます。私たちが学んだことは、あなた自身のモノレポをより良く管理するのに役立ちます。

  • パフォーマンスに影響を与える可能性のあるリポジトリの特性。
  • モノレポを最適化するためのツールと手順。

モノレポ用にGitalyを最適化する

Gitは、使用するスペースを削減するために、オブジェクトをパックファイルに圧縮します。チェックアウト、フェッチ、またはプッシュすると、Gitはパックファイルを使用します。これらは、ディスクスペースとネットワーク帯域幅を削減しますが、パックファイルの作成には多くのCPUとメモリが必要です。

大規模なモノレポは、小規模なリポジトリよりも、より多くのコミット、ファイル、ブランチ、およびタグを持ちます。オブジェクトが大きくなり、転送に時間がかかるようになると、パックファイルの作成はよりコストがかかり、遅くなります。Gitでは、git-pack-objectsプロセスは、以下の理由から最もリソースを消費する操作です:

  1. コミットの履歴とファイルを解析します。
  2. クライアントに送り返すファイルを決定します。
  3. パックファイルを作成します。

git cloneおよびgit fetchからのトラフィックは、サーバー上でgit-pack-objectsプロセスを開始します。GitLab CI/CDのような自動化された継続的インテグレーションシステムは、このトラフィックの多くを引き起こす可能性があります。大量の自動化されたCI/CDトラフィックは、多数のクローンとフェッチリクエストを送信し、Gitalyサーバーに負荷をかける可能性があります。

これらの戦略を使用して、Gitalyサーバーの負荷を軽減します。

Gitaly pack-objectsキャッシュを有効にする

Gitaly pack-objectsキャッシュを有効にすると、クローンとフェッチのサーバー負荷が軽減されます。

Gitクライアントがクローンまたはフェッチリクエストを送信すると、git-pack-objectsによって生成されたデータをキャッシュして再利用できます。モノレポが頻繁にクローンされる場合は、Gitaly pack-objectsキャッシュを有効にすると、サーバーの負荷が軽減されます。有効にすると、Gitalyは、クローンまたはフェッチの呼び出しごとに応答データを再生成する代わりに、インメモリキャッシュを維持します。

詳細については、パックオブジェクトキャッシュを参照してください。

GitバンドルURIを構成する

レイテンシーの低いサードパーティストレージ(CDNなど)にGitバンドルを作成して保存します。Gitは、まずバンドルからパッケージをダウンロードし、次に残りのオブジェクトと参照をGitリモートからフェッチします。このアプローチは、オブジェクトデータベースをより高速にブートストラップし、Gitalyの負荷を軽減します。

  • GitLabサーバーへのネットワーキング接続が悪いユーザーのために、クローンとフェッチを高速化します。
  • 事前にバンドルを読み込むことにより、CI/CDジョブを実行するサーバーの負荷を軽減します。

詳細については、バンドルURIを参照してください。

Gitalyネゴシエーションタイムアウトを構成する

フェッチまたはアーカイブリポジトリを試みるとき、次の場合は、fatal: the remote end hung up unexpectedlyエラーが発生する可能性があります:

  • 大規模なリポジトリ。
  • 多数のリポジトリを並行して実行。
  • 同じ大規模なリポジトリを並行して実行。

この問題を軽減するには、デフォルトネゴシエーションタイムアウト値を大きくします。

ハードウェアのサイズを正しく設定する

モノレポは通常、多数のユーザーがいる大規模な組織向けです。モノレポをサポートするために、GitLab環境は、GitLabテストプラットフォームおよびサポートチームが提供する参照アーキテクチャのいずれかに一致する必要があります。これらのアーキテクチャは、パフォーマンスを維持しながら、GitLabをスケールしてデプロイするための推奨される方法です。

Git参照の数を減らす

Gitでは、参照は、特定のコミットを指すブランチ名とタグ名です。Gitは、リポジトリの.git/refsフォルダーに参照をルーズファイルとして保存します。リポジトリ内のすべての参照を表示するには、git for-each-refを実行します。

リポジトリ内の参照の数が増えると、特定の参照を見つけるのに必要なシーク時間も長くなります。Gitが参照を解析するたびに、シーク時間が増加すると、レイテンシーが増加します。

この問題を修正するために、Gitはpack-refsを使用して、そのリポジトリのすべての参照を含む単一の.git/packed-refsファイルを作成します。このメソッドは、refsに必要なストレージスペースを削減します。また、1つのファイルでシークする方がディレクトリ内のすべてのファイルをシークするよりも速いため、シーク時間も短縮されます。

Gitは、新しく作成または更新された参照をルーズファイルで処理します。git pack-refsを実行するまで、.git/packed-refsファイルにクリーンアップして追加されません。Gitalyは、ハウスキーピング中にgit pack-refsを実行します。これは多くのリポジトリに役立ちますが、書き込み負荷の高いリポジトリには、依然として次のパフォーマンスの問題があります:

  • 参照を作成または更新すると、新しいルーズファイルが作成されます。
  • 参照を削除するには、既存のpacked-refsファイルを編集して、既存の参照を削除する必要があります。

Gitは、リポジトリをフェッチまたはクローンすると、すべての参照をイテレーションします。サーバーは、各参照の内部グラフ構造をレビュー(「ウォーク」)し、不足しているオブジェクトを見つけて、クライアントに送信します。イテレーション処理とウォーク処理はCPUを大量に消費し、レイテンシーが増加します。このレイテンシーにより、アクティビティーの多いリポジトリでドミノ効果が発生する可能性があります。各操作が遅くなり、各操作で後続の操作が停止します。

モノレポ内の多数の参照の影響を軽減するには:

  • 古いブランチをクリーンアップするための自動化されたプロセスを作成します。

  • 特定の参照をクライアントに表示する必要がない場合は、transfer.hideRefs構成設定を使用して非表示にします。Gitalyは、サーバー上のGit構成をすべて無視するため、/etc/gitlab/gitlab.rbでGitaly構成自体を変更する必要があります:

    gitaly['configuration'] = {
      # ...
      git: {
        # ...
        config: [
          # ...
          { key: "transfer.hideRefs", value: "refs/namespace_to_hide" },
        ],
      },
    }

Git 2.42.0以降では、オブジェクトグラフウォークを実行するときに、さまざまなGit操作で非表示の参照をスキップできます。

リポジトリの最適化タスクをスケジュールする

Gitリポジトリのオブジェクトデータベースにデータが保存される方法は、時間の経過とともに非効率になる可能性があり、Git操作が遅くなります。これらのアイテムをクリーンアップしてパフォーマンスを向上させるために、最大期間を設定して、Gitalyに毎日のバックグラウンドタスクを実行するようにスケジュールできます。

モノレポ用にCI/CDを最適化する

モノレポでGitLabのスケーラビリティを維持するには、CI/CDジョブがリポジトリとどのように相互作用するかを最適化します。大規模で長いパイプラインは、モノレポの一般的な問題点です。モノレポのパイプライン構成で、行われた変更のタイプを検出するビルドルールを使用します:

  • 不要なジョブをスキップします。
  • 子パイプラインで関連するジョブのみを実行します。

CI/CDでの同時クローンを削減する

実行する時間をずらすためにCI/CDパイプラインの並行処理を減らす。ほんの数分間隔でも効果があります。

パイプラインが特定の時間にスケジュールされているため、CI/CDの負荷は多くの場合、同時実行されます。リポジトリへのGitリクエストは、これらの時間帯に急増し、CI/CDプロセスとユーザーのパフォーマンスに影響を与える可能性があります。

CI/CDプロセスでシャロークローンとフィルターを使用する

CI/CDシステムでのgit cloneおよびgit fetchの呼び出しの場合、転送されるデータ量を次のオプションで制限できます:

CI/CDでのシャロークローン

--depthフィルターは、いわゆる_シャロークローン_を作成します。GitLabとGitLab Runnerは、デフォルトでシャロークローンを実行します。

クローン深度は、たとえばGIT_DEPTHを使用してGitLab CI/CDパイプライン構成で構成できます:

variables:
  GIT_DEPTH: 10

test:
  script:
    - ls -al

CI/CDでの部分クローン

--filterオプションを使用して_部分クローン_を作成します。この引数をgit-cloneに渡すには、GIT_CLONE_EXTRA_FLAGS変数を設定します。たとえば、blobの最大サイズを1MBに制限するには、次を追加します:

variables:
  GIT_CLONE_EXTRA_FLAGS: --filter=blob:limit=1m

パスとオブジェクトタイプを除外する

特定のタイプのオブジェクト、または特定のパスからオブジェクトを除外するには、git sparse-checkoutオプションを使用します。詳細については、ファイルパスでフィルタリングを参照してください。

CI/CD操作でgit fetchを使用する

リポジトリの実行コピーを使用可能な状態に保つことができる場合は、CI/CDシステムでgit cloneの代わりにgit fetchを使用します。git fetchは、サーバーからの作業が少なくて済みます:

  • git cloneは、リポジトリ全体を最初からリクエストします。git-pack-objectsは、すべてのブランチとタグを処理して送信する必要があります。
  • git fetchは、リポジトリにないGit参照のみをリクエストします。git-pack-objectsは、Git参照の合計のサブセットのみを処理します。この戦略は、転送されるデータの合計も削減します。

デフォルトでは、GitLabは大規模なリポジトリに推奨されるfetch Git戦略を使用します。

git cloneパスを設定する

モノレポがフォークベースのワークフローで使用されている場合は、リポジトリをクローンする場所を制御するためにGIT_CLONE_PATHを設定することを検討してください。

Gitは、フォークを個別のワークツリーを持つ個別のリポジトリとして保存します。GitLab Runnerは、ワークツリーの使用を最適化できません。指定されたプロジェクトに対してのみ、GitLab Runnerエグゼキュータを構成して使用します。プロセスをより効率的にするために、異なるプロジェクト間で共有しないでください。

GIT_CLONE_PATHは、$CI_BUILDS_DIRで設定されたディレクトリにある必要があります。ディスクから任意のパスを選択することはできません。

CI/CDジョブでgit cleanを無効にする

git cleanコマンドは、ワークツリーから追跡されていないファイルを削除します。大規模なリポジトリでは、大量のディスクI/Oを使用します。既存のマシンを再利用し、既存のワークツリーを再利用できる場合は、CI/CDジョブで無効にすることを検討してください。たとえば、GIT_CLEAN_FLAGS: -ffdx -e .build/は、実行間でワークツリーからディレクトリを削除しないようにすることができます。これにより、増分ビルドを高速化できます。

CI/CDジョブでgit cleanを無効にするには、GIT_CLEAN_FLAGSをそれらのnoneに設定します。

デフォルトでは、GitLabは次のことを保証します:

  • 指定されたSHAにワークツリーがあります。
  • リポジトリがクリーンです。

GIT_CLEAN_FLAGSで受け入れられる正確なパラメータについては、git cleanのGitドキュメントを参照してください。使用可能なパラメータは、Gitバージョンによって異なります。

フラグを使用してgit fetchの動作を変更する

CI/CDジョブが必要としないデータを除外するように、git fetchの動作を変更します。プロジェクトに多数のタグが含まれており、CI/CDジョブがそれらを必要としない場合は、GIT_FETCH_EXTRA_FLAGSを使用して--no-tagsを設定します。この設定により、フェッチをより高速かつコンパクトにすることができます。

リポジトリに多数のタグが含まれていない場合でも、--no-tagsを使用すると、場合によってはパフォーマンスが向上することがあります。詳細については、イシュー746およびGIT_FETCH_EXTRA_FLAGS Gitドキュメントを参照してください。

Runnerのロングポーリングを使用する

Runnerは、新しいCI/CDジョブのGitLabインスタンスを定期的にポーリングします。ポーリング間隔は、次の両方によって異なります:

  • check_interval設定。
  • Runner構成ファイルで構成されたRunnerの数。

サーバーが多数のRunnerを処理する場合、このポーリングにより、キュー時間が長くなったり、CPU使用率が高くなったりするなど、GitLabインスタンスでパフォーマンスの問題が発生する可能性があります。ロングポーリングは、新しいジョブの準備ができるまで、Runnerからのジョブリクエストを保持します。

構成手順については、ロングポーリングを参照してください。

モノレポ用にGitを最適化する

モノレポでGitLabのスケーラビリティを維持するには、リポジトリ自体を最適化します。

開発用にシャロークローンを回避する

開発用にシャロークローンを回避します。シャロークローンは、変更をプッシュするために必要な時間を大幅に増やします。シャロークローンは、チェックアウト後にリポジトリの内容が変更されないため、CI/CDジョブでうまく機能します。

ローカル開発では、代わりに部分クローンを使用して、次の操作を行います:

  • git clone --filter=blob:noneでblobを除外します。
  • git clone --filter=tree:0でツリーを除外します。

詳細については、クローンサイズの削減を参照してください。

リポジトリをプロファイルして問題を検出

大規模なリポジトリでは、通常、Gitでパフォーマンスの問題が発生します。git-sizerプロジェクトは、リポジトリをプロファイルし、潜在的な問題を理解するのに役立ちます。パフォーマンスの問題を防ぐための軽減策を開発するのに役立ちます。リポジトリの分析には、すべてのGit参照が存在することを確認するために、完全なGitミラーまたはベアクローンが必要です。

git-sizerでリポジトリをプロファイルするには:

  1. Git-sizer git-sizerをインストールします。

  2. このコマンドを実行して、git-sizerと互換性のあるベアGit形式でリポジトリをクローンします:

    git clone --mirror <git_repo_url>
  3. Gitリポジトリのディレクトリで、すべての統計情報を使用してgit-sizerを実行します:

    git-sizer -v

処理後、git-sizerの出力はこの例のようになります。各行には、リポジトリのその側面に関する懸念レベルが含まれています。懸念レベルが高いほど、アスタリスクが多く表示されます。懸念レベルが非常に高いアイテムは、感嘆符で示されます。この例では、いくつかのアイテムの懸念レベルが高くなっています:

Processing blobs: 1652370
Processing trees: 3396199
Processing commits: 722647
Matching commits to trees: 722647
Processing annotated tags: 534
Processing references: 539
| Name                         | Value     | Level of concern               |
| ---------------------------- | --------- | ------------------------------ |
| Overall repository size      |           |                                |
| * Commits                    |           |                                |
|   * Count                    |   723 k   | *                              |
|   * Total size               |   525 MiB | **                             |
| * Trees                      |           |                                |
|   * Count                    |  3.40 M   | **                             |
|   * Total size               |  9.00 GiB | ****                           |
|   * Total tree entries       |   264 M   | *****                          |
| * Blobs                      |           |                                |
|   * Count                    |  1.65 M   | *                              |
|   * Total size               |  55.8 GiB | *****                          |
| * Annotated tags             |           |                                |
|   * Count                    |   534     |                                |
| * References                 |           |                                |
|   * Count                    |   539     |                                |
|                              |           |                                |
| Biggest objects              |           |                                |
| * Commits                    |           |                                |
|   * Maximum size         [1] |  72.7 KiB | *                              |
|   * Maximum parents      [2] |    66     | ******                         |
| * Trees                      |           |                                |
|   * Maximum entries      [3] |  1.68 k   | *                              |
| * Blobs                      |           |                                |
|   * Maximum size         [4] |  13.5 MiB | *                              |
|                              |           |                                |
| History structure            |           |                                |
| * Maximum history depth      |   136 k   |                                |
| * Maximum tag depth      [5] |     1     |                                |
|                              |           |                                |
| Biggest checkouts            |           |                                |
| * Number of directories  [6] |  4.38 k   | **                             |
| * Maximum path depth     [7] |    13     | *                              |
| * Maximum path length    [8] |   134 B   | *                              |
| * Number of files        [9] |  62.3 k   | *                              |
| * Total size of files    [9] |   747 MiB |                                |
| * Number of symlinks    [10] |    40     |                                |
| * Number of submodules       |     0     |                                |

大規模なバイナリファイルにGit LFSを使用

バイナリファイル(パッケージ、オーディオ、ビデオ、グラフィックスなど)をGit Large File Storage(Git LFS)オブジェクトとして保存します。

ユーザーがGitにファイルをコミットすると、Gitはblob オブジェクトタイプを使用して、コンテンツを保存および管理します。Gitは大規模なバイナリデータを効率的に処理しないため、大規模なblobはGitにとって問題があります。git-sizerが10 MBを超えるblobをレポートする場合、通常、リポジトリに大規模なバイナリファイルがあります。大規模なバイナリファイルは、サーバーとクライアントの両方で問題を引き起こします:

  • サーバーの場合: テキストベースのソースコードとは異なり、バイナリデータは多くの場合、すでに圧縮されています。Gitはバイナリデータをさらに圧縮できないため、大規模なパックファイルにつながります。大規模なパックファイルでは、作成と送信により多くのCPU、メモリ、および帯域幅が必要になります。
  • クライアントの場合: Gitは、パックファイル(通常は.git/objects/pack/)と通常のファイル(ワークツリー)の両方にblobコンテンツを保存します。バイナリファイルは、テキストベースのソースコードよりもはるかに多くのスペースを必要とします。

Git LFSは、オブジェクトストレージなどの外部にオブジェクトを保存します。Gitリポジトリには、バイナリファイル自体ではなく、オブジェクトの場所へのポインターが含まれています。これにより、リポジトリのパフォーマンスが向上する可能性があります。詳細については、Git LFSのドキュメントを参照してください。