スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

巨大なGitリポジトリと戦う

課題

タイトルの通り、巨大なGitリポジトリと戦った記録です。

f:id:spookies-takano:20191219201248p:plain
(https://git-scm.com/)

前提

  • Gitにてバイナリファイルなどを扱うことにより、リポジトリの肥大化が起きている
    • バイナリファイルではバージョン間の差分圧縮が行われず、最適化されない
    • object系(.git/objects/以下)ファイルサイズの増大につながる
  • Git LFSなどは一旦使わず、あくまでGitで完結させる。

現象

  • git pullに時間がかかる
    • 差分が多いと、1~2時間以上かかるケースも
  • gitのobject系ファイル群がディスク容量を圧迫する
    • あるケースでは200GB程度にまで膨れていた

どうなる?

  • 反映がボトルネックになり動作確認などが遅れる
  • 他のメンバーから取り込みが終わらないことによる状況確認が多発
  • 検証環境にてディスクアラート頻発
    • 検証環境ではそこまでディスクサイズを確保していないケース
  • ローカルでもPCの利用可能サイズが30MB(!)とかになったことも

→単一ブランチ最新コミットのみでcloneしなおしたりして凌いでいたが、最近頻度が上がった。

どうしたい?

  • git pullの時間を短縮
  • gitのobject系ファイル群がディスク容量を圧迫しないように

原因

①git pullに時間がかかる

$ git pull origin master

remote: Counting objects: 128689, done.
remote: Compressing objects:  62% (59679/95740)   

...

remote: Total xxxxx (delta xx), reused 0 (delta 0)
Unpacking objects: 100% (xxxxx/xxxxx), done.

git pull実行時に走るプロセスのうち、このあたり↓に時間がかかっている。

ssh git@xxx git-upload-pack ‘yyy.git’

git index-pack --stdin --fix-thin --keep=fetch-pack 53466 on zzz --pack_header=2,119600
NAME

git-upload-pack - Send objects packed back to git-fetch-pack
DESCRIPTION

Invoked by git fetch-pack, learns what objects the other side is missing, and sends them after packing.

This command is usually not invoked directly by the end user. The UI for the protocol is on the git fetch-pack side, and the program pair is meant to be used to pull updates from a remote repository. For push operations, see git send-pack.
NAME
git-fetch-pack - Receive missing objects from another repository
DESCRIPTION

Usually you would want to use git fetch, which is a higher level wrapper of this command, instead.

Invokes git-upload-pack on a possibly remote repository and asks it to send objects missing from this repository, to update the named heads. The list of commits available locally is found out by scanning the local refs/ hierarchy and sent to git-upload-pack running on the other end.

This command degenerates to download everything to complete the asked refs from the remote side when the local side does not have a common ancestor commit.

git pullで取り込むコミット数(差分数)が大きくなると、その分1度に扱うobject数も大きくなってしまう。 これらpack, receiveするobject数を減らすことができれば、時間の短縮にはつながると考えられる。

②gitのobject系ファイル群がディスク容量を圧迫する

検証サーバーのディスク使用量を確認すると...

$ date; df -h
2019年 11月 27日 水曜日 15:48:51 JST
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        15G  7.5G  6.5G  54% /
tmpfs           7.8G     0  7.8G   0% /dev/shm
/dev/sdb1       197G  187G   18M 100% /var/data

「197G 187G 18M 100% /var/data」 「Used 187G」 「Use 100%」

何が容量を使っているのか?

$ du -sh /var/www/html/.git/
163G    /var/www/html/.git/

$ du -sh /var/www/html/.git/objects/pack
161G    /var/www/html/.git/objects/pack

これでした。 .git/objects/pack

①で話のあったobjectファイル群が配置されている。 objectをreceiveする量を減らせられればディスク圧迫を抑えられると考えられる。

対策

gitのobjectファイルは何に紐づくか。
→過去のコミット履歴の差分状況の保持

よって、過去のコミット履歴をそもそも持たないようにする。
常に最新の1コミットのみ履歴保持するように。

clone

一度リポジトリ自体入れ直し、不要なobjectファイルを整理。
指定ブランチの最新コミットのみ取り込む。
※初回は10~20分程度かかるケースもある。

$ git clone --branch master --depth 1 git@xxx

git pull

※実際にはpullではなく、checkoutした別ブランチと同名で入れ替えている

$ git fetch --depth=1 origin master:master_tmp
// git pull時と同様の差分出力
$ git diff --stat master master_tmp
$ git checkout master_tmp
$ git branch -D master
$ git checkout -b master
$ git branch -D master_tmp

※過去の履歴を見たくなったとき

$ git fetch --unshallow

※ディスクがまた悲鳴を上げる

対応後

場合によっては1~2時間かかったgit pullでの最新コミットの取り込みが、遅くても数十秒程度まで短縮されました。
都度cloneしなおすわけではないのでobjectファイル自体は蓄積していきますが、その蓄積スピードもかなり抑えられたと思われます。

まだ運用に載せ始めたところですが、進展があればまた共有しようと思います。
Git LFSも検討。