LoginSignup
6
6

More than 5 years have passed since last update.

git-lagrgefile のコードリーディング

Last updated at Posted at 2014-10-07

git-lagrgefile は、

登録しておいた拡張子のファイルはハッシュ値だけをリポジトリに格納し、ファイルの内容は別のディレクトリやAmazon S3に格納する

ものです。

今回はこちらのgolangのコードを読んでみます。

では早速、

キャッシュのファイルパス生成部分
func cachePath(sha1hex string) (dirpath, filename string) {
    dirpath = filepath.Join(assetDir(), "data", string(sha1hex[0:2]), string(sha1hex[2:4]))
    filename = string(sha1hex[4:])
    return
}

ディレクトリ名とファイル名は
assetDir/sha1先頭2文字/sha1の先頭3文字目から2文字/sha1の4文字目以降
という感じなので、ファイル数が数万個等になっても1ディレクトリで多すぎて死ぬ事はなさそう。

S3に保存
func storeToS3(hex string, data []byte) error {
    bucket := getBucket()
    _, err := bucket.GetReader(hex)
    if err == nil {
        log.Println("Already exists in S3: ", hex)
        return err
    }
    return bucket.Put(hex, data, "application/octet-stream", s3.Private)
}

ファイルがS3上に既に存在すると エラーになっちゃう・・・
これはエラーじゃない方が、複数の人が同じバケットを使った場合は、便利だと思ったんだけど違うんだろうか。
logにメッセージを吐いて、errはnilで返しているので処理続行ですね。

後、気になるのはメモリにすべてファイルを読み出している前提になってるけど、事前にハッシュを計算ために読む必要があるから仕方ないかなぁ

gitのフィルタから呼ばれるstore
func store() {
    data, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        log.Fatal(err)
    }
    sha1hex := calcSha1String(data)
    storeToCache(sha1hex, data)
    if !localMode {
        if err = storeToS3(sha1hex, data); err != nil {
            log.Fatal(err)
        }
    }
    writeStdout([]byte(sha1hex))
}

標準入力から全部メモリに読み取って
ハッシュを計算して
キャッシュディレクトリにファイル保存
localModeフラグがfalseのとき(default)S3にストアする
そしてハッシュを標準出力に返す

gitのフィルタから呼ばれるload
func load() {
    hash, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        log.Fatal(err)
    }
    hex := string(hash)
    if !isValidHash(hex) {
        writeStdout(hash)
        return
    }
    contents, err := loadFromCache(hex)
    if os.IsNotExist(err) {
        contents, err = loadFromS3(hex)
        if err != nil {
            log.Fatal(err)
        }
        storeToCache(hex, contents)
    } else if err != nil {
        log.Fatal(err)
    }
    writeStdout(contents)
}

標準入力からハッシュを読み取る
ハッシュのバリデーション
キャッシュディレクトリにファイルがあれば読む
無ければS3から全部メモリに読む −> キャッシュに保存
メモリに読んだコンテンツを標準出力で返す

事後アップロード用?
func upload() {
    datadir := filepath.Join(assetDir(), "data")
    store := func(path string, info os.FileInfo, err error) error {
        if err != nil {
            log.Println("Skip:", path, err)
            return nil // Skip this directory.
        }
        if info.IsDir() {
            return nil
        }
        data, err := ioutil.ReadFile(path)
        if err != nil {
            log.Println("Skip:", path, err)
            return nil // Skip this file.
        }
        sha1hex := calcSha1String(data)
        return storeToS3(sha1hex, data)
    }
    filepath.Walk(datadir, store)
}

これは、localModeをtrueして使った後に事後S3アップロードするときに使う機能っぽい
キャッシュディレクトリのファイルを1つづアップロードする

感想

  • S3はmd5を自動で保存されたりするので、sha1よりmd5を使った方が何か効率かできるような気がする
  • ファイルは全部読み出す前提の作りなので、でっかいファイルが来たときに逝っちゃうかもしれないのと、若干効率は良くないかも?
  • 並列処理化による高速を考えていたが、gitから1ファイルづつコマンドで起動されて標準入力でファイルを渡されるために、並列処理化はこのままだと難しそう。queue等を挟んでプロセスを別にして非同期化する必要がある
  • ただ、localModeを使う運用にしてしまえば、upload()だけを並列化するのは簡単

追記

並列アップロードの実装を入れてみた。 https://github.com/masahide/git-largefile/tree/add-parallel-upload

以下は79個のファイルをアップロードの実験です。

$ find ~/.gitasset/data/ -type f |wc
79      79    5767

まずは並列なし

$ time ~/gits3 -n=1 upload
real    0m8.841s
user    0m0.548s
sys 0m0.183s

20並列で実行

$ time ./gits3 -n 20 upload
real    0m0.787s
user    0m0.493s
sys 0m0.121s
6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6