Open3

reposoup: `git commit` の代わりにtreeを直接コミットする

okuokuokuoku

gitリポジトリをデータベース代わりに使うと、割と10〜100万件くらいからスケーラビリティ的な限界が出てくる。特にWindowsはファイルシステムが超クッソ激烈に遅いためgitを普通に使うんではパフォーマンス的に厳しいものがある。

git commit の代替

データベースをGitリポジトリに格納する...というと、

  1. ファイルシステム上にデータファイルをばらまく
  2. git add .
  3. git commit -m "Update" ← これが超時間掛かる
  4. git push

のような流れでデータの追加を行いたくなる。

しかし、Windows上では、100〜1000万ファイルあるリポジトリ上でGit commitを呼ぶだけで数時間掛かってしまう。というわけでデータベースにレコードを追加する場合は:

  1. 追加されるディレクトリを覚えておく(データベースを設計する上で少くとも意味のあるディレクトリ単位でデータを追加するように配慮する)
  2. git add <path> ... のようにしてpathを直接指定してGitが無駄なディレクトリを見なくても良い様にする
  3. git write-treeadd したデータをtreeに変換する
  4. git commit-tree で tree を参照するコミットオブジェクトを作成する
  5. git update-ref で、 master ブランチを更新する

のような流れにすると、ほぼ git add に掛かるコストだけでコミットを作成できる。

...というか何で git commit はあんなに遅いんだろう。。

標準ではコミットメッセージのデフォルトに変更されたファイルや add していないファイルの一覧が出るので、それらを出すためにリポジトリのあるディレクトリをスキャンしているのではないかと思ったけど、 git commit -m "Update" のようにコミットログメッセージをコマンドラインから与えても全く改善しない。

okuokuokuoku

CMakeスクリプトでのやり方

    message(STATUS "Git Add ...")
    execute_process(
        COMMAND git add
        ${targetdirs} # ★ 変数にあらかじめgit addが必要なパスを入れておき、add . での手抜きは避ける
        WORKING_DIRECTORY ${ROOT})
    message(STATUS "Git write-tree...")
    execute_process(
        COMMAND git write-tree # ★ write-treeには特に引数は不要。indexがtreeに変換される
        WORKING_DIRECTORY ${ROOT}
        OUTPUT_VARIABLE out # ★ treeオブジェクトのハッシュ(と改行)を受けとる
        RESULT_VARIABLE rr)
    if(rr)
        message(FATAL_ERROR "Write-tree error: ${rr}")
    endif()
    string(STRIP "${out}" tree) # ★ 余計なスペースを消す
    message(STATUS "Git create commit(${tree})...")
    execute_process(
        COMMAND git commit-tree -p HEAD -m "Update"
        ${tree} # ★ commit-tree コマンドでコミットを作成
        WORKING_DIRECTORY ${ROOT}
        OUTPUT_VARIABLE out # ★ sha1を受けとる
        RESULT_VARIABLE rr)
    if(rr)
        message(FATAL_ERROR "Commit-tree error: ${rr}")
    endif()
    string(STRIP "${out}" commit)
    message(STATUS "Git update ref(${commit})...")
    execute_process( # ★ update-ref でmasterを更新
        COMMAND git update-ref refs/heads/master ${commit}
        WORKING_DIRECTORY ${ROOT}
        OUTPUT_VARIABLE out
        RESULT_VARIABLE rr)
    if(rr)
        message(FATAL_ERROR "Update-ref error: ${rr}")
    endif()
okuokuokuoku

↑ この方法だとgcがトリガされない。。?

数百回コミットして300万ファイルくらいにしてみたけど一度もgcした様子が無い。。

まぁ別に手動gcを発動するようにスクリプトを組めば良い話ではあるけど。。