golang で S3 に大量ファイルを並列でアップロードする

More than 3 years have passed since last update.


概要

s3関連のコマンドは色々と既存のものがありますが、

javaだったり、rubyだったり、なんだかんだインストールが面倒。

大量のファイルをS3にアップロードするのに随分と時間がかかる。

複数のファイルを並列でアップロードできたらイイなというのと、

golangならインストールも実行ファイル1つだけで済むしなぁ・・・

という思いから、golangを使って「エイや!」で作って

とりあえず、動くものができました。-> https://github.com/masahide/s3cp


golangでAWS APIを使うには

goamzを使えばよさそうです。

しかしgithub上にgoamzのforkが一杯あり、どれが良いのか迷います。

今回は星の数等から github.com/crowdmob/goamz を選択しました。


s3へのアップロード

goamzを使えば簡単です。

https://github.com/golang-samples/goamz/blob/master/s3/simple/main.go

こちらのサンプルが分かりやすいですが、もっと省略して書くと

data := []byte("Hello, Goamz!!")

auth, _ := aws.EnvAuth()
s := s3.New(auth, aws.APNortheast)
bucket := s.Bucket(bucketName)
bucket.Put("path/to/sample.txt", data, "text/plain", s3.Private, s3.Options{})

こんな感じでしょうか。

しかし、このままだと、

メモリ上にファイルを全部読み込むことになるので、少し工夫してPutReaderを使います。

file, _ = os.Open("path/to/sample.txt")

defer file.Close()
fileinfo, _ = file.Stat()
bucket.PutReader("path/to", file, fileinfo.Size(), "application/octet-stream", s3.Private, s3.Options{})

これで、io.Readerを使ってくれるようになるので、メモリにファイルを読み込む必要がありません。


並列化

これに関しては「goroutineを使う」で終わりそうなんですが、

前から興味があった「Advanced Go Concurrency Patterns」に出てくるような「チャンネルのチャンネル」を使った実装に挑戦してみました。

調べてみると、ちょうど良さそうな記事が見つかったので、この実装を真似て、簡単なqueueとworkerを実装しました。

https://github.com/masahide/s3cp/blob/queueworker/queueworker/queueworker.go

参考の元記事にあるコードほぼそのままなんですが、

queueからworkerにworkを渡すディスパッチャーで goroutineを使っている部分に若干手を加えました。

この部分は、WorkQueueにworkを投げ込まれたら、すぐに無条件でgoroutineを起動することで、WorkQueueを常に空っぽに保つようになっています。

元記事ではgoroutineのコストが安いからこうしているようなことが書かれている(多分)のですが。

このままだと投げ込まれたworkの数だけgoroutineが起動する形になるので、メモリがある限りworkを受け付けてしまい、

最悪メモリ等のリソースが枯渇しそうな気がします。

幸い今回は、WorkQueueが一杯になって「待ち」が発生しても問題がありません。

というわけで、ここのgoroutine化を辞めることで、WorkQueueが一杯になったらWorkerが飽くまで待つようにしました。


追記

WorkQueueとWrokerQueueの残り数をカウントして処理の終了を確認しているのだけど、WorkQueueを受け取ってWorkerQueueを受け取る部分で、一瞬両方空としてカウントしてしまう瞬間が生まれそう?

なので、Goでのパイプラインとキャンセルで実装するのが正しそう。


追記 2014/10/13

Goでのパイプラインとキャンセルをつかった実装に修正しました。