Gitの驚愕の真実:1億行のファイルに1行追記するとレポジトリ容量が200MB増える[※補足あり]

1億行のファイルに1行追記するだけでレポジトリ容量が2倍になった

以前の記事「Gitレポジトリはパッチの集積ではなくてスナップショットの集積である。」を確認するために、1億行のファイルを作って実験してみました。

結果は、なんと1行追記しただけでレポジトリ容量が200MB増加し、サイズが2倍になりました。

実験手順

  • 空のレポジトリを作る
  • 1億行のファイルを作ってgit addしてgit commit コミットする
  • そのファイルに1行だけ追記してgit addして git commitする
空のレポジトリを作る
$ git init
1億行のファイルを作る
1億行のファイル(1から1億までの数字が書かれたファイル)を作ります。
$ seq 1 100000000 > numbers.txt
この時点で、ワーキングツリーとレポジトリ容量を調べてみます。
$ ls -lh
合計 848M
-rw-r--r-- 1 vagrant vagrant 848M  9月 18 18:35 2013 numbers.txt
ワーキングツリーのファイルサイズは848Mあります。
巨大ですね。

次にレポジトリのデータベースをのぞいてみます。
$ find .git/objects
.git/objects
.git/objects/pack
.git/objects/info
空のディレクトリがあるだけで、中身は何もありません。
まだgit addしてないのだから当然です。

git add 1億行
さあ1億行のファイルをaddするのです!
git add numbers.txt
ここでめちゃくちゃ待たされます。
PCの負荷がもりもり上昇します。
待つこと数分、git addが完了しました。

すかさずレポジトリを調べます。
$ find .git/objects
.git/objects
.git/objects/pack
.git/objects/pack/pack-abcd03ce4d1730bfe5f3071d10391fe79e71cc2b.pack
.git/objects/pack/pack-abcd03ce4d1730bfe5f3071d10391fe79e71cc2b.idx
.git/objects/info
お、なんかできてる!
ではレポジトリ容量を調査。
$ du .git/objects -sh
211M    .git/objects
でか!!
1ファイルaddしただけで211M!!
git commit 1億行
コミットします。
$ git commit -m "created 100,000,000 numbers file"
[master (root-commit) 30332ce] created 100,000,000 numbers file
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 numbers.txt
コミットは一瞬で終わりました。

コミット後のレポジトリはこんな感じです。
$ find .git/objects
.git/objects
.git/objects/pack
.git/objects/pack/pack-abcd03ce4d1730bfe5f3071d10391fe79e71cc2b.pack
.git/objects/pack/pack-abcd03ce4d1730bfe5f3071d10391fe79e71cc2b.idx
.git/objects/info
.git/objects/30
.git/objects/30/332ced8b3ef7a7ea5260177fce6c6b59ea4d81
.git/objects/ba
.git/objects/ba/a8f41a0d784c37b5d796397b12d6adaa75f9e8
解説すると、
packファイルが実際のコンテンツ、
30/3332ceファイルがコミットオブジェクト、
ba/a3f4ファイルがツリーオブジェクトです。

コミットオブジェクトとツリーオブジェクトは100バイト程度のファイルでした。
つまり、コミットしただけでは容量はほとんど増加しませんでした。
git add 1行
$ echo 100000001 >> numbers.txt
$ git add .
重い!
マシン負荷がもりもり上がる!!
待つこと数分・・・
終わりました。

ではレポジトリ容量を調べます。
$ find .git/objects
.git/objects
.git/objects/pack
.git/objects/pack/pack-abcd03ce4d1730bfe5f3071d10391fe79e71cc2b.pack
.git/objects/pack/pack-abcd03ce4d1730bfe5f3071d10391fe79e71cc2b.idx
.git/objects/pack/pack-aa469d2462b1283b3555906a2153acb3b7712975.pack
.git/objects/pack/pack-aa469d2462b1283b3555906a2153acb3b7712975.idx
.git/objects/info
.git/objects/30
.git/objects/30/332ced8b3ef7a7ea5260177fce6c6b59ea4d81
.git/objects/ba
.git/objects/ba/a8f41a0d784c37b5d796397b12d6adaa75f9e8
packファイルが増えました!
$ du -sh .git/objects
422M    .git/objects
うわ!!
でかくなってる!!
2倍に肥大化してる!!
1行追記しただけなのに!!

git commit 追記1行

いちおうコミットします。
$ git commit -m "added 1 line"
.[master 4490fac] added 1 line
 1 file changed, 0 insertions(+), 0 deletions(-)
一瞬でおわりました。 いちおう容量を調べます。
$ du -sh .git/objects
422M    .git/objects
やはりコミットでは容量は増えないみたいです。

まとめ

  • 1億行のファイルに1行追記すると、Gitレポジトリ上は新たに"1億1行"分のコンテンツが追加される。
  • git addした時点でレポジトリは肥大化する
  • gitは差分の管理などしていない。ただただバカみたいに単純に、addされたコンテンツを保存している(いちおう圧縮して) → 言い過ぎでした。その後 git gc すると、差分のみ格納されて肥大化は解消されます。補足記事をご参照ください。
というわけで、前回の記事の考察が正しかったことが証明されました。

なお誤解のないように言っておくとGitをdisりたいわけではありません。
単に知的好奇心からGitの内部構造について確認したかっただけです。

「バカみたいに単純に」というのは尊敬語で、いわゆるKISS (Keep It Simple & Stupid)というやつですね。
memcachedサーバがバカなのと同じアレです。

補足記事

すみません上記の内容は言い過ぎでした。 おっしゃるとおりです。
Gitプロジェクトは当初「バカで単純なコンテンツ管理システム」としてスタートしましたが、後から"Pack Object"というよりスマートなコンテンツ圧縮管理機構が導入されました。
上記の操作でほとんどのケースでは、肥大化したレポジトリは"git gc"することによって(または数日経過することで自動で)圧縮されます。
誤解を招く書き方をしてしまい申し訳ありませんでした。

ただ、このGitの"Pack Object"の機能はあくまで最適化の手段の一つであって、Gitからこの機能を取り払ったとしてもGitの各機能は依然として(多少は遅いけど)動作するはずです。

Gitをバカ呼ばわりするなという件について

Linus・・・
カテゴリ: