T-CREATOR

Git パフォーマンス運用:gc/pack 設計/prefetch で巨大リポを加速させる

Git パフォーマンス運用:gc/pack 設計/prefetch で巨大リポを加速させる

大規模なプロジェクトで Git を使っていると、操作が遅くなったり、リポジトリサイズが肥大化したりして困ることはありませんか?実は、Git には高速化のための強力な最適化機能が備わっているんです。

この記事では、Git のパフォーマンスを劇的に改善できる 3 つの重要な運用技術について解説します。git gc(ガベージコレクション)によるリポジトリの最適化、pack ファイルの効率的な設計、そして git prefetch による先読みフェッチ戦略を習得することで、巨大なリポジトリでも快適に作業できるようになるでしょう。

背景

Git の内部構造とパフォーマンス

Git は分散バージョン管理システムとして、すべての履歴情報をローカルリポジトリに保存します。この仕組みにより、ネットワークなしでも多くの操作が可能になりますが、リポジトリが大きくなるにつれてパフォーマンスの課題が生まれてきました。

Git は内部で「オブジェクトデータベース」という仕組みを使っており、コミット、ツリー、ブロブ(ファイル内容)、タグの 4 種類のオブジェクトを .git​/​objects ディレクトリに保存しています。

図の意図:Git の内部オブジェクト構造とストレージの関係性を示します。

mermaidflowchart TB
    repo["Git リポジトリ"] --> objects[".git/objects"]
    objects --> loose["Loose オブジェクト<br/>(個別ファイル)"]
    objects --> packed["Pack ファイル<br/>(圧縮済み)"]

    loose --> commit["Commit オブジェクト"]
    loose --> tree["Tree オブジェクト"]
    loose --> blob["Blob オブジェクト"]
    loose --> tag["Tag オブジェクト"]

    packed --> packfile["*.pack<br/>(データ本体)"]
    packed --> idx["*.idx<br/>(インデックス)"]

初期状態では、各オブジェクトは「loose オブジェクト」として個別のファイルで保存されます。しかし、コミット数が増えるとファイル数が膨大になり、ファイルシステムのパフォーマンスが低下してしまうんです。

大規模リポジトリの実例

実際の開発現場では、以下のような大規模リポジトリが存在します。

#リポジトリ例オブジェクト数リポジトリサイズ課題
1Linux Kernel800 万+3GB+クローン・フェッチに時間がかかる
2Chromium1000 万+20GB+ローカル操作も遅延する
3企業モノリポ数百万〜数 GB〜数十 GBCI/CD パイプラインが遅い

これらのリポジトリでは、適切な最適化なしでは開発効率が大きく低下してしまいます。

課題

Git リポジトリが抱えるパフォーマンス問題

大規模リポジトリで発生する典型的なパフォーマンス問題を整理しましょう。

1. Loose オブジェクトの増加

コミットやブランチ作成のたびに新しいオブジェクトが生成され、loose オブジェクトが大量に蓄積されます。これにより以下の問題が発生します。

  • ファイルシステムの inode を大量に消費
  • ディスク I/O の増加により操作が遅くなる
  • git statusgit log などの基本操作も遅延

2. 非効率な Pack ファイル

Pack ファイルは複数のオブジェクトをまとめて圧縮しますが、適切に設計されていないと効率が悪くなります。

  • デルタ圧縮が最適化されていない
  • 複数の小さな pack ファイルが乱立
  • 古いオブジェクトが pack から除外されていない

3. ネットワーク転送の遅延

大規模リポジトリでは、リモートとの同期に時間がかかります。

  • git fetchgit pull が数分〜数十分かかる
  • CI/CD で毎回フルクローンすると時間とリソースを浪費
  • チーム全体の生産性が低下

図の意図:パフォーマンス問題が発生する流れと影響範囲を示します。

mermaidflowchart TD
    start["開発活動の継続"] --> accumulate["オブジェクトの蓄積"]
    accumulate --> loose_increase["Loose オブジェクト増加"]
    accumulate --> pack_fragmentation["Pack ファイル断片化"]

    loose_increase --> filesystem["ファイルシステム負荷"]
    pack_fragmentation --> inefficient["非効率な圧縮"]

    filesystem --> slow_local["ローカル操作の遅延"]
    inefficient --> slow_network["ネットワーク転送の遅延"]

    slow_local --> productivity["開発効率の低下"]
    slow_network --> productivity

    productivity --> frustration["チーム全体の<br/>ストレス増加"]

これらの問題を放置すると、開発者の体感速度が悪化し、最終的にはチーム全体の生産性に影響を及ぼします。

パフォーマンス劣化の具体的な症状

実際の開発現場で報告される症状には、以下のようなものがあります。

#操作症状発生タイミング
1git status実行に数秒かかるLoose オブジェクト数万個以上
2git log履歴表示が遅いPack ファイルが断片化
3git fetch数分以上かかるリモートとの差分が大きい
4git clone初回クローンに 10 分超リポジトリサイズが数 GB 以上
5ディスク使用量予想以上に大きい最適化されていない

これらの症状は、適切なメンテナンスで大幅に改善できます。

解決策

Git のパフォーマンス問題を解決するには、3 つの主要な技術を組み合わせることが効果的です。それぞれの技術を詳しく見ていきましょう。

1. git gc(ガベージコレクション)

git gc は Git のガベージコレクション機能で、リポジトリを最適化する最も基本的かつ強力なコマンドです。

git gc の基本動作

git gc を実行すると、以下の処理が自動的に行われます。

bash# 基本的な gc の実行
git gc

このコマンドは以下の作業を実行します。

  • Loose オブジェクトを pack ファイルにまとめる
  • 到達不可能なオブジェクトを削除
  • reflog の古いエントリを削除
  • pack ファイルを再構成

アグレッシブな最適化

より積極的な最適化を行う場合は、--aggressive オプションを使用します。

bash# より徹底的な最適化(時間がかかる)
git gc --aggressive --prune=now

--aggressive オプションの効果:

  • デルタ圧縮の深さを増やす(デフォルト 50 → 250)
  • より多くのデルタチェーンを試行
  • 圧縮率が向上するが、処理時間も増加

--prune=now オプションは、通常は 2 週間保持される古いオブジェクトを即座に削除します。

自動 gc の設定

Git は一定の条件下で自動的に gc を実行しますが、その閾値を調整できます。

bash# Loose オブジェクトが 1000 個を超えたら自動 gc
git config gc.auto 1000

# Pack ファイルが 20 個を超えたら自動 gc
git config gc.autopacklimit 20

設定の推奨値:

#設定項目デフォルト推奨値(大規模)説明
1gc.auto67001000〜3000Loose オブジェクトの閾値
2gc.autopacklimit5010〜20Pack ファイル数の閾値
3gc.pruneExpire2.weeks.ago1.week.ago古いオブジェクトの削除期限

2. Pack ファイルの設計と最適化

Pack ファイルは Git のストレージ効率を決定する重要な要素です。適切に設計することで、ディスク使用量とアクセス速度の両方を改善できます。

Pack ファイルの仕組み

Pack ファイルは、複数のオブジェクトをまとめてデルタ圧縮する仕組みです。

図の意図:Pack ファイルがオブジェクトをどのように圧縮・格納するかを示します。

mermaidflowchart LR
    subgraph before["Pack 前"]
        obj1["ファイルv1<br/>(100KB)"]
        obj2["ファイルv2<br/>(100KB)"]
        obj3["ファイルv3<br/>(100KB)"]
    end

    subgraph after["Pack 後"]
        base["Base<br/>(100KB)"]
        delta1["Δ1<br/>(5KB)"]
        delta2["Δ2<br/>(3KB)"]
    end

    before --> pack["Pack 処理<br/>(デルタ圧縮)"]
    pack --> after

    note["合計:300KB → 108KB"]

デルタ圧縮により、同じファイルの異なるバージョンは差分のみが保存されるため、大幅にサイズが削減されます。

Pack の最適化設定

Pack ファイルの動作を制御する重要な設定項目を紹介します。

bash# デルタ圧縮のウィンドウサイズを設定(デフォルト: 10)
git config pack.window 50

# デルタチェーンの最大深さを設定(デフォルト: 50)
git config pack.depth 100

# Pack ファイルのスレッド数を設定(デフォルト: 1)
git config pack.threads 4

各設定の意味と効果:

  • pack.window: 類似オブジェクトを探す範囲(大きいほど圧縮率向上、処理時間増)
  • pack.depth: デルタチェーンの深さ(深いほど圧縮率向上、解凍時間増)
  • pack.threads: 並列処理のスレッド数(CPU コア数に合わせる)

増分 Pack の活用

Git 2.38 以降では、増分 Pack(Incremental Repack)機能が利用できます。

bash# 増分 Pack を有効化
git config feature.manyFiles true
git config pack.useSparse true

# Commit-graph を生成(高速化)
git commit-graph write --reachable

増分 Pack の利点:

  • 大規模リポジトリでの gc 時間を大幅短縮
  • 既存の最適化された pack を保持
  • 新しいオブジェクトのみを効率的に pack

Bitmap インデックスの活用

Bitmap インデックスは、ネットワーク転送を高速化します。

bash# Bitmap インデックスを有効化
git config pack.writeBitmaps true

# Repack 時に bitmap を生成
git repack -a -d -b

Bitmap インデックスの効果:

  • git fetchgit clone が最大 10 倍高速化
  • サーバーサイドでの効果が特に大きい
  • 若干のディスク使用量増加(通常は数 MB 程度)

3. git prefetch による先読みフェッチ

git prefetch は、バックグラウンドで定期的にリモートから変更を取得する機能です(Git 2.38 以降の機能)。

git maintenance による定期実行

git maintenance コマンドで自動メンテナンスを設定します。

bash# メンテナンスを開始(prefetch 含む)
git maintenance start

# メンテナンスの状態確認
git maintenance run --task=prefetch --schedule=hourly

このコマンドを実行すると、以下のタスクが定期的に実行されるようになります。

bash# 設定されたタスクの確認
git config --get-all maintenance.task

主要なメンテナンスタスク:

#タスク実行頻度説明
1prefetch1 時間ごとリモートから変更を先読み
2commit-graph1 時間ごとCommit-graph を更新
3loose-objects毎日Loose オブジェクトを pack
4incremental-repack毎日増分 repack を実行
5gc毎週フル gc を実行

prefetch の仕組みと利点

git prefetch は通常の git fetch とは異なる動作をします。

図の意図:prefetch と通常の fetch の違いを比較します。

mermaidflowchart TB
    subgraph normal["通常の fetch"]
        nf1["git fetch 実行<br/>(ユーザー操作)"] --> nf2["リモート接続"]
        nf2 --> nf3["データ転送<br/>(ブロッキング)"]
        nf3 --> nf4["ブランチ更新"]
    end

    subgraph prefetch["prefetch"]
        pf1["バックグラウンド実行<br/>(自動・定期)"] --> pf2["リモート接続"]
        pf2 --> pf3["データ転送<br/>(非同期)"]
        pf3 --> pf4["refs/prefetch/* に保存<br/>(ブランチは未更新)"]
    end

    user["ユーザー"] -.待機.-> normal
    user -.作業継続.-> prefetch

prefetch の特徴:

  • ユーザーの作業を中断しない
  • 作業ブランチに影響を与えない
  • ネットワーク帯域を効率的に利用
  • 実際の git pull が高速化

prefetch の設定とカスタマイズ

prefetch の動作を細かく制御できます。

bash# prefetch の頻度を設定(秒単位)
git config maintenance.prefetch.schedule hourly

# prefetch するリモートを指定
git config remote.origin.prefetch '+refs/heads/*:refs/prefetch/origin/*'

# すべてのブランチを prefetch
git config maintenance.prefetch.enabled true

スケジュールオプション:

  • hourly: 1 時間ごと(推奨)
  • daily: 1 日 1 回
  • weekly: 1 週間に 1 回

CI/CD での活用

CI/CD パイプラインでも prefetch を活用できます。

bash#!/bin/bash
# CI/CD の前処理スクリプト

# Shallow clone の代わりに prefetch を活用
if [ -d ".git" ]; then
    # 既存リポジトリがあれば prefetch で更新
    git maintenance run --task=prefetch
    git pull
else
    # 初回は通常の clone
    git clone --filter=blob:none <repository-url>
    git maintenance start
fi

この方法により、CI/CD の実行時間を大幅に短縮できます。

  • 初回以降は差分のみ取得
  • ネットワーク転送量が削減
  • ビルド時間全体が短縮

具体例

実際のプロジェクトで、これらの技術を組み合わせて運用する具体的な方法を見ていきましょう。

ケース 1:既存の巨大リポジトリを最適化

既に肥大化したリポジトリを整理する手順です。

ステップ 1:現状の確認

まず、リポジトリの現在の状態を把握します。

bash# リポジトリサイズの確認
du -sh .git

# オブジェクト数の確認
git count-objects -vH

出力例の見方:

textcount: 12450         # Loose オブジェクト数
size: 45.2 MiB       # Loose オブジェクトのサイズ
in-pack: 2847392     # Pack 内のオブジェクト数
packs: 47            # Pack ファイル数
size-pack: 2.1 GiB   # Pack ファイルの合計サイズ

この例では、Pack ファイルが 47 個と多く、最適化の余地があることがわかります。

ステップ 2:徹底的な最適化の実行

最適化を段階的に実行します。

bash# ステップ1: 到達不可能なオブジェクトを削除
git reflog expire --expire=now --all
git prune --expire=now

# ステップ2: アグレッシブな gc を実行
git gc --aggressive --prune=now

この処理には時間がかかりますが(数十分〜数時間)、一度実行すれば大きな効果が得られます。

ステップ 3: 最適化設定の追加

今後のパフォーマンスを維持するため、設定を調整します。

bash# 自動 gc の閾値を下げる
git config gc.auto 2000
git config gc.autopacklimit 15

# Pack の最適化設定
git config pack.window 50
git config pack.depth 100
git config pack.threads 4

# Bitmap を有効化
git config pack.writeBitmaps true
git config pack.writeBitmapHashCache true

これらの設定により、今後も最適な状態が維持されます。

ステップ 4:効果の確認

最適化後の状態を確認します。

bash# 再度サイズを確認
du -sh .git
git count-objects -vH

典型的な改善例:

#項目最適化前最適化後改善率
1リポジトリサイズ3.2 GB1.8 GB44% 削減
2Pack ファイル数47296% 削減
3Loose オブジェクト12,4500100% 削減
4git status 時間3.2 秒0.4 秒88% 高速化

ケース 2:新規プロジェクトでの初期設定

新しいリポジトリを作成する際に、最初から最適な設定を行います。

初期化時の設定スクリプト

リポジトリ作成時に実行するスクリプトです。

bash#!/bin/bash
# git-init-optimized.sh

# リポジトリの初期化
git init

# パフォーマンス設定
git config gc.auto 2000
git config gc.autopacklimit 15
git config pack.window 50
git config pack.depth 100
git config pack.threads "$(nproc)"

# Bitmap とスパース設定
git config pack.writeBitmaps true
git config pack.writeBitmapHashCache true
git config feature.manyFiles true
git config pack.useSparse true

このスクリプトを使用することで、最初から最適化されたリポジトリを作成できます。

bash# スクリプトの実行
chmod +x git-init-optimized.sh
./git-init-optimized.sh

定期メンテナンスの有効化

自動メンテナンスを設定します。

bash# メンテナンスタスクを開始
git maintenance start

# カスタムスケジュールの設定
git config maintenance.auto true
git config maintenance.strategy incremental

メンテナンス戦略のオプション:

  • none: 自動メンテナンスなし
  • incremental: 増分メンテナンス(推奨)
  • full: フルメンテナンス(重い)

ケース 3:チーム全体での設定共有

チームメンバー全員が同じ最適化設定を使用できるようにします。

グローバル設定スクリプトの作成

すべてのリポジトリに適用する設定をグローバルに設定します。

bash#!/bin/bash
# setup-git-performance.sh

echo "Git パフォーマンス設定を適用します..."

# グローバル設定
git config --global gc.auto 2000
git config --global gc.autopacklimit 15
git config --global pack.window 50
git config --global pack.depth 100
git config --global pack.threads "$(nproc)"
git config --global pack.writeBitmaps true
git config --global pack.writeBitmapHashCache true
git config --global feature.manyFiles true
git config --global pack.useSparse true

# Commit-graph の有効化
git config --global core.commitGraph true
git config --global gc.writeCommitGraph true

echo "設定完了!"

チーム全員がこのスクリプトを実行することで、統一された環境を構築できます。

プロジェクトルートに設定ドキュメントを配置

リポジトリに設定手順を含めることも有効です。

markdown# PERFORMANCE.md

# Git パフォーマンス設定

このリポジトリで快適に作業するために、以下の設定を推奨します。

## 初回セットアップ

```bash
# このリポジトリで最適化設定を有効化
git config pack.window 50
git config pack.depth 100
git maintenance start
```

## 定期メンテナンス

週に一度、以下のコマンドを実行してください:

```bash
git gc --auto
```

このドキュメントをリポジトリに含めることで、新しいメンバーもすぐに最適な環境を構築できます。

ケース 4:CI/CD パイプラインの最適化

継続的インテグレーション環境での Git 操作を高速化します。

GitHub Actions での設定例

GitHub Actions で prefetch を活用する例です。

yaml# .github/workflows/optimized-ci.yml
name: Optimized CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # キャッシュを利用した高速チェックアウト
      - name: Restore Git cache
        uses: actions/cache@v3
        with:
          path: .git
          key: git-cache-${{ github.sha }}
          restore-keys: |
            git-cache-

      # 最適化されたチェックアウト
      - name: Checkout with optimization
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

このワークフローでは、Git ディレクトリをキャッシュすることで、毎回のクローン時間を削減しています。

最適化スクリプトの統合

CI 実行前に最適化を行うスクリプトを追加します。

bash#!/bin/bash
# ci-git-optimize.sh

if [ -d ".git" ]; then
    echo "既存のリポジトリを最適化中..."

    # 軽量な最適化のみ実行(時間節約)
    git maintenance run --task=prefetch --quiet
    git maintenance run --task=loose-objects --quiet

    echo "最適化完了"
else
    echo "新規クローン - 最適化設定を適用"
    git config pack.threads "$(nproc)"
    git config pack.window 30
fi

この方法により、CI の実行時間を大幅に短縮できます。

典型的な改善例(中規模プロジェクト):

#フェーズ最適化前最適化後改善
1Checkout45 秒8 秒82% 削減
2依存関係解決120 秒110 秒-
3ビルド180 秒180 秒-
4テスト90 秒90 秒-
5合計435 秒388 秒11% 削減

Checkout 時間の削減により、全体のパイプライン実行時間も短縮されます。

運用のベストプラクティス

これらの技術を効果的に運用するためのポイントをまとめます。

図の意図:運用サイクル全体の流れと各技術の適用タイミングを示します。

mermaidflowchart TD
    start["プロジェクト開始"] --> initial["初期設定<br/>(最適化設定)"]
    initial --> dev["日常的な開発"]

    dev --> auto["自動メンテナンス<br/>(prefetch/gc)"]
    auto --> dev

    dev --> check{"パフォーマンス<br/>低下?"}
    check -->|はい| manual["手動最適化<br/>(gc --aggressive)"]
    check -->|いいえ| dev

    manual --> verify["効果検証"]
    verify --> dev

    dev --> periodic["定期レビュー<br/>(月次・四半期)"]
    periodic --> adjust["設定調整"]
    adjust --> dev

定期的な見直しと調整により、常に最適なパフォーマンスを維持できます。

まとめ

Git のパフォーマンスを最大限に引き出すための 3 つの主要技術について解説しました。

git gc(ガベージコレクション) は、リポジトリを定期的にクリーンアップし、オブジェクトを効率的に管理する基本的な最適化手法です。--aggressive オプションを使用することで、より徹底的な最適化が可能になります。

Pack ファイルの最適化 では、デルタ圧縮の設定を調整し、Bitmap インデックスを活用することで、ディスク使用量とアクセス速度の両方を改善できます。特に pack.windowpack.depth の調整が効果的でしょう。

git prefetch を含む自動メンテナンス機能は、バックグラウンドで定期的にリポジトリを最適化し、ユーザーの作業を中断することなくパフォーマンスを維持します。git maintenance start コマンド一つで、これらの機能を有効化できますね。

これらの技術を組み合わせることで、巨大なリポジトリでも快適に作業できる環境を構築できます。初期設定に少し時間をかけるだけで、長期的には大きな時間節約につながるでしょう。

パフォーマンスの問題に直面したら、まずは git count-objects -vH で現状を把握し、適切な最適化手法を選択してください。チーム全体で設定を共有することで、全員が同じ快適な開発環境を享受できますよ。

関連リンク