前回のDay 34では、WindowsとLinuxの差異に起因する「大文字・小文字ファイル名変更トラブル」のメカニズムと、安全な
git mv の作法を学びました。続く本日のDay 35は、第7部「Windows & C# (.NET) 固有の落とし穴完全制覇」の第3弾――
リポジトリの巨大化・クローン激重問題を解決する【巨大バイナリ・DLL・画像管理の決定版(Git LFS)】を徹底攻略します!
「数GBのインストーラーや画像アセット、ONNX機械学習モデル、古い社内DLLをGitにコミットしたら、
git clone に30分以上かかるようになって全員のPCが悲鳴をあげている…!」「VSSやSVNを使っていた30年前は、100MBのバイナリを普通にコミットしても平気だったのに、なぜGitだとこんなに重くなるのか…!?」
そんな疑問や絶叫を抱えたことはありませんか?
Gitはテキストの差分管理には最強ですが、巨大バイナリに対しては構造的な弱点を持っています。
しかし、公式の拡張機能 Git LFS(Large File Storage) を導入すれば、リポジトリを羽のように軽く保ったまま、大容量ファイルを安全・快適に運用できます。
本講義で、LFSの内部メカニズム(ポインタファイル参照)から実践的な導入・運用手順まで完全マスターしていきましょう!
① なぜGitはバイナリに弱いのか?(Git vs SVN)
「以前使っていたSVN(Subversion)では、大容量の画像やコンパイル済みDLLをコミットしてもリポジトリが爆発することはなかった。なぜGitに移行した途端、こんなにリポジトリが重くなるのか?」
これは、長年集中型バージョン管理システムに慣れ親しんだエンジニアが最も直面しやすい疑問の一つです。
決定的な違い:「中央サーバーのみ保持」 vs 「全員が全歴史を保持」
その理由は、集中型(SVN)と分散型(Git)の根本的なアーキテクチャの違いにあります。
- 集中型(SVN / VSS): 過去の全履歴はすべて中央サーバーにのみ保管されます。開発者がチェックアウトするのは「最新リビジョン(または指定したリビジョン)の作業コピー」だけです。過去にどれほど巨大なファイルが何十回更新されていようと、ローカルPCには最新の1ファイル分しか落ちてきません。
-
分散型(Git):
git cloneを実行した瞬間、リポジトリの「創世記から現在に至るすべてのコミット履歴・全ファイル」がローカルの.gitフォルダに丸ごとダウンロードされます。
Gitはテキストファイル(C#コード等)であれば、変更行だけを効率よく差分圧縮(Packfile)して保持します。
しかし、ZIPファイル、画像(PNG/JPG)、動画、コンパイル済みDLLなどのバイナリファイルは、1バイトでも変更されると中身全体が別データとみなされ、差分圧縮がほとんど効きません。
つまり、「100MBのバイナリを10回更新してコミットすると、リポジトリの容量は単純に1GB増加」し、チーム全員が新規クローンするたびにその1GBを毎回強制ダウンロードさせられる羽目になります!
② Git LFSの画期的な仕組み(ポインタファイルと外部退避)
この「全歴史を全員が丸ごと抱え込む」というGitの宿命的な弱点を克服するために開発されたのが、オープンソースの拡張機能 Git LFS(Large File Storage) です。
Git LFSの設計思想は極めてシンプルかつエレガントです。
Git本体には「わずか百数十バイトのポインタ」だけを記録
Git LFSを有効にすると、巨大なバイナリファイルの実体はGitリポジトリ(.git)には保存されず、専用の外部LFSストレージ(GitHub LFSサーバー等)へ直接アップロードされます。
そして、Gitリポジトリ本体の中には、その実体を指し示す「ポインタファイル(わずか百数十バイトの極小テキスト)」だけがコミットされます。
version https://git-lfs.github.com/spec/v1
oid sha256:4d7a282a51f1273ab929b818ec7548d9ac10ba5cb14202b803bae110c659d7b3
size 524288000 # 実体は500MBだが、Gitが持つのはこの3行(約130バイト)のテキストだけ!
開発者がブランチを切り替えたりチェックアウトしたりすると、Git内部のフィルタフック(Smudgeフィルタ)がポインタファイルを検知し、LFSキャッシュから作業フォルダへ必要な実体ファイルだけを瞬時に展開します。
過去の履歴にある何十世代もの巨大実体はローカルに落ちてこないため、リポジトリ本体は常に数MB〜数十MBという超軽量な状態を維持できるのです!
③ 実践!Git LFSの導入・追跡・コミット(基本3ステップ)
それでは、Windows / C#環境でGit LFSを導入し、実際にファイルを追跡・コミットする手順を実践していきましょう。
Git for Windowsを通常インストールしていれば、Git LFSのコマンドは既に同梱されています。
ステップ1: 環境全体でGit LFSを有効化する(初回1回のみ)
まずPC環境全体に対して、GitがLFSのフック処理を自動実行できるように初期設定を行います。
$ git lfs install
Git LFS initialized.
ステップ2: LFSで追跡したいファイル形式(拡張子・パス)を指定する
次に、リポジトリ内でLFS管理下に置きたいファイル形式を git lfs track コマンドで指定します。
$ git lfs track "*.zip"
$ git lfs track "*.onnx"
$ git lfs track "Assets/LargeVideos/**"
# .gitattributes が自動生成・更新されていることを確認
$ git status
Untracked files:
.gitattributes
git lfs track "*.zip" を実行すると、リポジトリ直下の .gitattributes に以下の行が自動追加されます:
*.zip filter=lfs diff=lfs merge=lfs -text
前回の改行コード講座でも登場した .gitattributes が、ここでも大活躍しています! このファイルこそが「どの拡張子をLFSとして処理するか」をチーム全員で共有するための命綱です。
ステップ3: .gitattributes と対象ファイルをコミット&プッシュ
設定ファイルである .gitattributes と、追跡対象の巨大バイナリを通常通りコミット&プッシュします。
$ git add .gitattributes model_weights.onnx
# コミット実行
$ git commit -m "feat: 機械学習モデルをGit LFS管理として追加"
# リモートへプッシュ(実体はLFSサーバーへ、ポインタはGitリポジトリへ自動アップロードされる)
$ git push origin main
Uploading LFS objects: 100% (1/1), 500 MB | 25 MB/s, done.
Writing objects: 100% (3/3), 420 bytes | 420.00 KiB/s, done.
git lfs ls-files
現在どのファイルがGit LFSによってポインタ管理されているかを確認したいときは、git lfs ls-files を実行します。OIDハッシュとファイル名が一覧表示されれば、正常にLFS管理下に入っています。
④ C# (.NET) 開発現場におけるGit LFS運用の3大鉄則
Git LFSは非常に強力ですが、運用ルールを定めずに安易に導入すると、思わぬ落とし穴や追加課金のトラブルに見舞われます。
特にC# / Windows開発現場で必ず徹底すべき「3大鉄則」を押さえておきましょう。
鉄則①: 再配布可能なDLLはLFSではなく「NuGet」で管理せよ!
C#開発現場で最も多い失敗が、「外部ベンダーから提供された巨大DLL」や「社内共通ライブラリのDLL」をすべてGit LFSに突っ込んでしまうことです。
ライブラリの依存関係管理は、Git LFSの役割ではなくパッケージマネージャー(NuGet / Azure Artifacts / GitHub Packages)の役割です!
- 外部DLLや社内共通ライブラリは
.nupkgパッケージ化してプライベートNuGetフィードへ公開する。 - プロジェクト側では
PackageReferenceとして参照し、コンパイル成果物(bin/やobj/)は.gitignoreで徹底的に除外する。 - Git LFSで管理すべきもの: インストーラー用リソース、大型画像・3Dモデル・音声アセット、機械学習モデルなど「NuGet化に適さない固定の大容量アセット」に限定する。
鉄則②: ファイルを追加する「前」に必ず git lfs track せよ!
「先にファイルをコミットしてしまってから、後で慌てて git lfs track を実行した」というケースが頻発します。
しかし、過去に一度でもGit本体へコミットされたバイナリファイルは、リポジトリの全履歴(.git)に永久に残り続けます!
後から git lfs track を設定しても、今後の変更がLFSになるだけで、過去の肥大化した履歴は消えません。
もし過去のコミット履歴に巨大ファイルが混入してしまった場合は、Git LFS公式のマイグレーションコマンド git lfs migrate import --include="*.zip" を使用して、過去の全履歴をポインタファイルへ一括書き換える作業が必要になります(※コミットハッシュがすべて再計算されるため、チーム全体での周知が必須です)。
鉄則③: GitHub LFSの「容量枠・帯域制限」を常に監視せよ!
GitHubでは、無料プランおよび有料プラン(Pro/Team)にかかわらず、リポジトリあたりのLFS無料枠は「ストレージ1GB、月間データ転送帯域1GB」に制限されています。
もしCI/CD(GitHub Actions)で毎回クローン時に500MBのLFSファイルをダウンロードしていると、わずか2回のビルドで月間1GBの帯域上限に達し、LFSのダウンロードが停止してCIが突然全滅する事故が起きます!
- GitHub Actionsでの対策:
actions/checkoutでlfs: false(デフォルト)のままにし、ビルドにバイナリが必要なワークフローでのみgit lfs pullを実行する。 - CIキャッシュの活用: 頻繁にダウンロードしないよう、ダウンロード済みLFSオブジェクトを Actions Cache で永続化する。
- 必要に応じたデータパック購入: 業務で日常的にGB単位のファイルを扱う場合は、Organizationの設定で「LFS Data Pack(月額$5/50GB)」をあらかじめ購入しておく。
📌 本日のまとめと最重要ポイント
- Gitは「全員が全歴史を丸ごと保持する」ため、バイナリの肥大化に極めて弱い!
- Git LFSは「ポインタ参照(百バイト)」と「外部専用ストレージ」を分離してリポジトリの爆速化を実現する!
- 導入は簡単3ステップ:
git lfs install→git lfs track→.gitattributesと一緒にコミット! - C#ライブラリDLLはLFSではなく「NuGet」で配る! bin/objは .gitignore を徹底せよ!
- 巨大ファイルを追加する「前」に track せよ! GitHub LFSの帯域枠(月1GB)に注意せよ!
【Day 36】Windowsの最大パス長制限(260文字問題 / core.longpaths)
「ディープなフォルダ階層や長大なファイル名で Filename too long エラーが発生してクローンできない…!」
Windowsの伝統的な260文字制限の壁を打ち破る core.longpaths 設定とOSレジストリ設定を徹底解説します! 次回もお楽しみに!
コメント
コメントを投稿