登録しておいた拡張子のファイルはハッシュ値だけをリポジトリに格納し、ファイルの内容は別のディレクトリや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ディレクトリで多すぎて死ぬ事はなさそう。
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で返しているので処理続行ですね。
後、気になるのはメモリにすべてファイルを読み出している前提になってるけど、事前にハッシュを計算ために読む必要があるから仕方ないかなぁ
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にストアする
そしてハッシュを標準出力に返す
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