Git Multi-Pack-Index (MIDX): 大規模リポジトリの読み取りパフォーマンスを最適化するオブジェクトインデックス統合技術

Git tutorial - IT technology blog
Git tutorial - IT technology blog

リポジトリがボトルネックになるとき

この問題に気づいたのは約8ヶ月前、チームが大規模なモノレポ — 15年以上のコミット履歴、約2GBのpackファイル — を新しいサーバーに移行したときでした。git statusに3〜4秒かかり、git log --oneline -10もはっきりと遅延を感じました。最初はハードウェアの問題だと思っていましたが、プロファイリングしてみると原因はまったく別のところにありました:Gitがオブジェクトを検索するたびに、大量の個別packファイルをスキャンする必要があったのです。

Gitはオブジェクト(commit、tree、blob)を.git/objects/pack/内のバイナリ圧縮ファイルであるpackファイルに保存します。リポジトリをcloneしたばかりやgit gcを実行した直後は、通常1〜2個のpackファイルしかないため、すべてが高速に動作します。しかし数ヶ月の運用、継続的なfetch、24時間365日動くCI/CDを経ると、packファイルの数が数十個にまで増えることがあります。各packファイルには独自のインデックス(.idx)があり、Gitはオブジェクトを見つけるために各ファイルを順番にバイナリサーチしなければなりません — ファイルの数が多いほど、オーバーヘッドも大きくなります。

従来の解決策はgit gc --aggressiveですべてを1つのpackにまとめることでした。しかし2GBのリポジトリでは、このコマンドの実行に20〜30分かかり、その間すべての操作がブロックされます。本番環境では現実的ではありません。

Git Multi-Pack-Indexが問題をどのように解決するか

Multi-Pack-Index(MIDX)は — Git 2.21で導入され、Git 2.25以降で安定した機能で — 既存のpackファイルをまとめることなく、すべてのpackファイルに対する共通インデックスファイルを作成します。このファイルは.git/objects/pack/multi-pack-indexに配置されます。

N個の.idxファイルを順次バイナリサーチする代わりに、GitはMIDXで1回のルックアップを行うだけで — オブジェクトがどのpackファイルにあるか、オフセットがいくつかをすぐに把握できます。30個のpackファイルがある場合、元のデータに手を加えることなく、これは大幅な改善です。

最も重要な点:MIDXは既存のpackファイルを変更せず、データの損失もなく、ダウンタイムも不要です。作成後すぐに使用できます。

MIDXの有効化

Git 2.25以降にはMIDXが組み込まれており、追加のインストールは不要です。まずバージョンを確認しましょう:

git --version
# git version 2.43.0 — OK、MIDXが使用可能

リポジトリのMIDXを有効化:

# グローバルに有効化(すべてのリポジトリに推奨)
git config --global core.multiPackIndex true

# または現在のリポジトリのみ
git config core.multiPackIndex true

MIDXファイルを初めて作成:

cd /path/to/your/repo
git multi-pack-index write

# ファイルが作成されたことを確認
ls -lh .git/objects/pack/multi-pack-index

大きなリポジトリの場合、MIDXの初回作成に10〜30秒かかることがありますが、その間もGitは他の操作を通常通り処理し続けます。

詳細設定とワークフローへの統合

git maintenanceを使ったMIDXの自動更新

MIDXは一度作成しても自動更新されません。新しいpackファイルが追加されるたびに — fetchやgc後 — 古いMIDXはそれをカバーできなくなります。定期的にgit multi-pack-index writeを再実行する必要があります。

Git 2.29以降には、まさにこれを処理するgit maintenanceがあります:

# 現在のリポジトリのmaintenanceを有効化
git maintenance start

# 事前にdry-runで確認
git maintenance run --task=incremental-repack --dry-run

git maintenance startはcronジョブ(Linux/macOS)またはタスクスケジューラのエントリ(Windows)を追加して定期的にメンテナンスを実行します。incremental-repackタスクは手動介入なしにfetchのたびに自動でMIDXを更新します。

より手動でコントロールしたい場合は、post-mergeフックに追加します:

# .git/hooks/post-merge  (作成後にchmod +xを実行)
#!/bin/bash
git multi-pack-index write 2>/dev/null &

バックグラウンド実行(&)により、開発者のmerge/fetch操作を遅延させません。

commit-graphとの組み合わせ

MIDXを有効にした後、commit-graphも追加で有効にしました — この2つの機能は相互補完的に機能します。MIDXはオブジェクトルックアップを最適化し、commit-graphはgit log --graphやgit merge-baseなどの到達可能性クエリを高速化します:

git config --global fetch.writeCommitGraph true
git config --global core.multiPackIndex true

# 両方を同時に作成
git multi-pack-index write --bitmap
git commit-graph write --reachable --changed-paths

MIDX書き込み時の--bitmapフラグは追加のビットマップインデックスを作成します — サーバーサイドのbareリポジトリに有用です。ローカルの開発リポジトリにはあまり必要ありません。

サーバー上のbareリポジトリの設定

GiteaやGitLabをセルフホストしている場合、サーバーサイドの設定はローカルよりもはるかに重要です:

# bareリポジトリ(サーバー)上で実行
git config pack.writeBitmaps true
git config repack.writeBitmaps true
git config core.multiPackIndex true

# メンテナンスを実行 — クライアントのpush/fetchをブロックしない
git maintenance run \
  --task=gc \
  --task=commit-graph \
  --task=incremental-repack

ピーク時間を避けるため、このコマンドを午前2時に実行するようにスケジュールします:

# crontab -e
0 2 * * * cd /path/to/bare.git && git maintenance run --task=incremental-repack --task=commit-graph 2>>/var/log/git-maintenance.log

確認とモニタリング

整合性の検証

# MIDXの整合性を検証(正常ならサイレント、問題があればエラーを表示)
git multi-pack-index verify

# MIDXの詳細な統計情報を表示
git multi-pack-index verify --object-dir=.git/objects

# GitがMIDXをロードしているかデバッグ確認
GIT_TRACE2=1 git status 2>&1 | grep -i multi_pack

実際の改善を計測する

MIDXを有効化する前に、シンプルなベンチマークを行いました:

# 計測前にOSのページキャッシュをクリア(Linux)
sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'

# MIDX なしの時間を計測
time git rev-list --count HEAD
time git log --oneline -50 > /dev/null

# MIDXを作成
git multi-pack-index write

# キャッシュをクリアして再計測
sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time git rev-list --count HEAD
time git log --oneline -50 > /dev/null

チームのリポジトリ(28個のpackファイル、約1.8GB)での結果:git rev-list --count HEADが4.2秒から0.8秒に短縮されました。git log --oneline -50は2.1秒から0.4秒になりました。8人のチームで、すべての開発者マシンとCI/CDサーバーにこのGit設定を適用したところ、特に多数のブランチをマージするスプリント後に「gitが遅い」という不満の声が明らかに減りました。

MIDXの再構築が必要なタイミング

git multi-pack-index writeを再実行が必要な状況:

  • maintenanceフックを使用していない場合、新しいpackファイルを作成するgit fetchの後
  • git gcまたはgit repackの後 — これらのコマンドはpackファイルを変更します
  • 大規模なrebaseや多数のコミットを含む長いブランチのmerge後
  • looseオブジェクトの数が急増したとき
# 現在のpackファイル数を確認
ls .git/objects/pack/*.pack | wc -l

# 10個以上の場合、MIDXを再作成
git multi-pack-index write

packファイルの数が50〜100を超えた場合は、git repack -adfを一度実行してまとめることを検討すべきです — MIDXはその作業が必要になる時期を先延ばしにするものであり、永遠に回避できるわけではありません。

6ヶ月の本番運用で得た実践的な注意点

  • Git 2.25以降で十分 — Ubuntu 20.04 LTSにはGit 2.25.1が同梱されており、基本的なMIDXの使用には十分です。git maintenanceを使用するにはGit 2.29以降が必要です。
  • MIDXは読み取り操作のみを最適化します — git add、git commit、git pushは速くなりません。最も効果が明確なのは:git log、git status、git fetch、git mergeです。
  • MIDXファイルは意外に小さい — 1.8GBのpackファイルを持つリポジトリでも、multi-pack-indexファイルはわずか約2MBです。ストレージの消費は最小限です。
  • 削除とロールバックは安全 — MIDXファイルを削除してもGitが遅くなるだけで、データは失われません。いつでも自由にテストしてロールバックできます。
  • Windowsにも対応 — Git for Windows 2.25以降でフルサポート、追加設定不要です。

これは銀の弾丸ではありませんが、リポジトリが数百MBを超えて通常の読み取りコマンドで遅延を感じ始めたなら、すぐに有効にする価値のある調整です。

Share: