emahiro/b.log

Drastically Repeat Yourself !!!!

GCSのObjectをCopyするときの挙動について調べたこと

業務でGoogle Cloud Strage にObjectをコピーするときの挙動について調べたのでまとめておきます。

GCSでのコピーは cloud.google.com/go/storage 配下にある copy.go に処理の中身が書いてあります。

github.com

実際にGCSでコピーをするときには以下のようなコードを書きます。
※ サンプルコードなので適当です。

client, _ := storage.NewClient(ctx)
defer client.Close()

srcObj = client.Bucket(srcBucketName).Object(srcObjName)
distObj = client.Bucket(distBucketName).Object(distObjName)

// copier の生成
copier := distObj.CopierFrom(srcObj)

// copy の実行
attr, err := copier.Run(ctx)
if err != nil {
    // copy失敗 -> (1)
    fmt.Errorf("copy error. err: %v", err)
}

// copyが成功したら生成される Object Attributes
return attr 

ここで (1) の copy時にエラーが発生したときにcopy途中だったオブジェクトはどうなるのか?ということが気になったので実際の copy 処理を行なっている Run メソッドの中を見ました。

// Run performs the copy.
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
    ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Copier.Run")
    defer func() { trace.EndSpan(ctx, err) }()

    if err := c.src.validate(); err != nil {
        return nil, err
    }
    if err := c.dst.validate(); err != nil {
        return nil, err
    }
    // Convert destination attributes to raw form, omitting the bucket.
    // If the bucket is included but name or content-type aren't, the service
    // returns a 400 with "Required" as the only message. Omitting the bucket
    // does not cause any problems.
    rawObject := c.ObjectAttrs.toRawObject("")
    for {
        res, err := c.callRewrite(ctx, rawObject)
        if err != nil {
            return nil, err
        }
        if c.ProgressFunc != nil {
            c.ProgressFunc(uint64(res.TotalBytesRewritten), uint64(res.ObjectSize))
        }
        if res.Done { // Finished successfully.
            return newObject(res.Resource), nil
        }
    }
}

新しいオブジェクトにCopyが完了した段階で if res.Done {} の分岐に入ってきてこの時だけcopyした結果をインスタンス化して返しているのでそもそもCopyが行われてる最中に失敗したら Copy 対象は中途半端な状態で残ることなくerrが返されるだけになります。
途中で失敗してもゴミが残らないように作られてました。