概要
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を実装しました。
参考の元記事にあるコードほぼそのままなんですが、
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でのパイプラインとキャンセルをつかった実装に修正しました。