Gitの大規模ファイルによるパフォーマンス問題のトラブルシューティング

大規模ファイルが原因で遅くなるGitリポジトリを診断し、Git LFSを慎重に選択し、チームに驚きを与えずに履歴をクリーンアップする方法。

Gitの大規模ファイルによるパフォーマンス問題のトラブルシューティング

大規模ファイルは、リポジトリがすでに使いづらくなるまで気づかれにくい形でGitに悪影響を及ぼします。単一の動画、アーカイブ、データベースダンプ、デザインファイルは、誰かが追加した時点では危険に見えないかもしれません。問題が始まるのは、そのファイルが何度も変更されたときです。Gitは履歴を保持し、すべてのクローンがその履歴を引き継ぐ必要があります。

症状は最初は曖昧なことが多いです。git cloneが通常より時間がかかる。git fetchがホテルのWi-Fiで遅く感じられる。CIジョブがビルド前にリポジトリのチェックアウトに多くの時間を費やす。開発者は新しいクローンが面倒なので、古いローカルクローンを使い始める。それが、皆に辛抱するように言う代わりにリポジトリを調査するタイミングです。

大規模ファイルが問題であることを確認する

簡単なチェックから始めましょう:

du -sh .git
git count-objects -vH

du -sh .gitはローカルリポジトリデータベースの重さを教えてくれます。git count-objects -vHは疎オブジェクトとパックサイズを表示します。パックサイズが実際のソースツリーに比べて大きい場合、履歴が古いペイロードを運んでいる可能性があります。

現在のチェックアウト内の大規模ファイルを見つけるには:

find . -path ./.git -prune -o -type f -size +10M -print

これは現在存在するものだけを表示します。リポジトリは、数ヶ月前に削除されたファイルのために遅くなることがあります。履歴を調査するには、Git LFSが移行前でも有用なレポートを提供します:

git lfs migrate info --everything --above=10MB

Git LFSがインストールされていない場合でも、Gitの配管コマンドで調査できますが、上記のコマンドはこの特定の問題に対して最も直接的なビューであることが多いです。

Gitに何を属させるかを決める

すべての大規模ファイルが間違いというわけではありません。安定したバイナリアセットの小さなセットは問題ないかもしれません。インフラストラクチャコード用のリポジトリには、VMイメージ、データベースバックアップ、顧客エクスポート、ビルドアーティファクトを含めるべきではありません。ゲームリポジトリには、正当にアートやオーディオアセットが含まれる場合がありますが、それらのファイルは通常Git LFSまたは別のアセットシステムが必要です。

実用的なルールはこれです:Gitはソーステキストと小さな補助ファイルに優れています。Gitは頻繁に変更されるバイナリブロブには不向きです。ファイルが差分で意味のあるレビューができず、頻繁に変更される場合、通常のGitオブジェクトとして存在すべきではありません。

Git LFSの一般的な候補は次のとおりです:

*.psd
*.ai
*.mp4
*.mov
*.wav
*.zip
*.uasset
*.fbx
*.blend

広範な画像パターンには注意してください。デザイン重視のリポジトリではすべての*.pngをLFSで追跡することは役立つかもしれませんが、多くの小さなアイコンがあるWebアプリでは煩わしいことがあります。パターンは実際に問題を引き起こすファイルに一致させるべきです。

将来の大規模ファイルにGit LFSを使用する

Git LFSはGitに小さなポインタファイルを保存し、大規模コンテンツをLFSストレージに保持します。通常のGit履歴は軽量のままで、ユーザーはLFSがダウンロードしたときにワーキングツリーで実際のファイルを取得します。

インストールして初期化します:

git lfs install

実際に必要なファイルパターンを追跡します:

git lfs track "*.psd"
git lfs track "*.mp4"
git add .gitattributes
git commit -m "Track large design and video files with Git LFS"

.gitattributesファイルは重要です。全員が同じLFSルールを使用するようにコミットします。

その後、通常どおりファイルを追加します:

git add demo.mp4
git commit -m "Add product demo video"
git push origin main

共同作業者はリポジトリで作業する前にGit LFSをインストールする必要があります。LFSサポートなしでクローンすると、LFSをインストールして次のコマンドを実行するまで、実際のアセットの代わりにポインタファイルが表示される場合があります:

git lfs pull

また、Gitホストのストレージと帯域幅ポリシーを確認してください。Git LFSはGitオブジェクトの肥大化を解決しますが、大規模アセットの保存や転送を無料にするわけではありません。

既存の履歴を移行する

今日LFSを有効にしても、昨日のコミットが自動的に修正されるわけではありません。700 MBのアーカイブがコミットされ、後で削除された場合でも、履歴に残ることがあります。それをクリーンアップするには、履歴の書き換えが必要です。

履歴の書き換えはコミットIDを変更します。既存のクローンを持つ人は慎重に再同期する必要があり、オープンなプルリクエストはリベースまたは再作成が必要になる場合があります。メンテナンスウィンドウでこれを行い、最初にミラーバックアップを作成してください:

git clone --mirror [email protected]:ORG/REPO.git repo-backup.git

次に、新しいクローンで作業します。ワーキングツリーがクリーンであることを確認します:

git status

移行されるものを調査します:

git lfs migrate info --everything --above=10MB

可能な場合はパターンで移行します:

git lfs migrate import --everything --include="*.psd,*.mp4,*.zip"

または、リポジトリに多くの未知の大規模ファイルがある場合は、しきい値以上のファイルを移行します:

git lfs migrate import --everything --above=10MB

プッシュする前に結果を確認します:

git log --oneline --decorate -5
git lfs ls-files
git status
git lfs migrate info --everything --above=10MB

移行が期待どおりに機能した場合は、書き換えたブランチとタグを意図的にプッシュします:

git push --force-with-lease origin main
git push --force-with-lease origin --tags

多くのアクティブなブランチがあるリポジトリの場合は、どのブランチが重要かを判断します。放棄されたすべてのブランチを書き換える必要はないかもしれませんが、大規模オブジェクトを含むブランチはリモートリポジトリを重く保つ可能性があります。

履歴書き換え後

チームメイトに何が変わったかを正確に伝えます。最もクリーンな指示は、多くの場合、再クローンすることです。ローカルで作業がある場合は、最初に保存する必要があります:

git status
git branch my-work-before-lfs-migration
git fetch origin
git rebase origin/main

乱雑なローカルクローンの場合、古い履歴を外科的に修復しようとするよりも、再クローンの方がリスクが少ないです。

リモートストレージはすぐに縮小しない場合があります。ホスティングプロバイダーはしばらく到達不能オブジェクトを保持し、ストレージ数値が更新される前にサポートやリポジトリメンテナンスが必要な場合があります。ローカルでは、移行が良好であることを確認した後、古いオブジェクトを削除できます:

git reflog expire --expire=now --all
git gc --prune=now --aggressive

クリーンアップコマンドをレビューの代わりに実行しないでください。これらは古いローカルオブジェクトの回復を困難にします。

同じ問題を再発防止する

大規模な偶発ファイルが引き続き表示される場合は、pre-commitまたはpre-receiveチェックを追加します。ローカルのpre-commitフックは、開発者が大規模アーティファクトをコミットする前に警告できます。サーバー側のルールは、誰かがローカルフックをスキップした場合でも共有リポジトリを保護するため、より強力です。

簡単なローカルチェックでは、選択したサイズ以上のファイルを、LFSで追跡されていない限り拒否する場合があります。正確なしきい値はプロジェクトによって異なります。ドキュメントサイトとゲームプロジェクトは同じ制限を使用すべきではありません。

また、ファイルのソースを修正します。CIがリポジトリ内にdist/target/、カバレッジレポート、アーカイブ、スクリーンショットを作成する場合は、適切なエントリを.gitignoreに追加します:

dist/
target/
coverage/
*.log
*.zip

ファイルを盲目的に無視しないでください。無視されたパスが生成された出力であり、ソース入力ではないことを確認します。

LFSが答えではない場合

Git LFSは普遍的なアーティファクトストアではありません。ビルド出力は通常、パッケージレジストリ、オブジェクトストレージ、リリースアセット、またはCIアーティファクトストアに属します。データベースダンプはバックアップストレージに属します。大規模データセットには、データバージョン管理ツールまたは別のストレージワークフローが必要な場合があります。

目標は、すべての大きなファイルをGitから隠すことではありません。目標は、リポジトリを十分に高速に保ち、人々がツールと戦うことなくクローン、ブランチ、フェッチ、レビューできるようにすることです。

適切なクリーンアップは3つのものを残します:LFSに属するファイルの明確な.gitattributesルール、決してコミットすべきでないファイルの.gitignoreルール、および既存のクローンがどのように再同期すべきかを説明する短いチームノート。これにより、修正が四半期ごとに繰り返す1回限りのクリーンアップにならないようにします。