ayumin.log

読みにくかったら脳内sedで整形してね

Goで圧縮しながら非同期的にS3にデータ送信する話

Goの非同期IO

Goはgoroutineによって非同期処理ができるが、ちゃんと使ったことがなかったので色々やりながら機能を把握した。

とりあえず今回は直接 channel を使わず、シンプルなIOのみの処理をする。

目的はtar.gzに固めながらのAWS S3へのデータ送信だったが、ライブラリを使ってAWSに直接送信する部分以外は、全部標準ライブラリでこなせる。

io.Pipeについて

readPipe, writePipe := io.Pipe()

とすることで、パイプを作れる。

書き込みたいデータのストリームをwritePipeに流し、readPipeでそのストリームを取り出していく。

注意点としてはwritePipeに書き込み終わったらwritePipe.Close()すること。忘れると一生処理が終わらない。この辺りはLinuxのreadシステムコールの挙動に近い。

あとはそこまで難しいことはなく、書き込みをgoroutineを使って非同期的に行えば、直感的に動くはず。

S3への送信は

&s3manager.UploadInput{
    Bucket: aws.String(bucketName),
    Key:    aws.String(keyName),
    Body:   readPipe,
})

のようにパイプを繋げるだけ。とてもお手軽。

tar.gzに固める

Goはtarもgzipも標準ライブラリにあるので、Goだけでシェルなどを叩かずともtar.gzを作れる。

gzipの方は簡単で、writePipeを使ってwriterを作るだけで良い。そのwriterにtarのデータを流すと、gzipで圧縮したデータがパイプにストリームとして送られる。

余談だが ふつうのLinuxプログラミング を読んでてよかった。ちょうど最近パイプの章を読んでいたので、直感的に理解ができた。

コードにするとこんな感じ。

gzipWriter := gzip.NewWriter(writePipe)
tarWriter := tar.NewWriter(gzipWriter)

ちょっと複雑なのはtarで、ディレクトリの配下をtarで固める場合、filepath.Walkを使う必要がある。

IsDir()でディレクトリを無視しながら、ファイルのみ相対パスでtarのHeaderに情報を渡す。

あとはパスを使ってos.Openで*os.File型を受け取り、それをtarWriterにデータをio.Copyでコピーする。

io.Copy(tarWriter, file)

なお、path/filepath はこれを見るととても参考になる。パス名によるOSの依存性も減らせるため、積極的に使うようにしている。

このコードを書くには相対パスの作成などfilepathを操作する必要があるため、かなり便利。

みんなのGo言語のCLIツール作成編とかもとても参考になるのでおすすめ。

mattn.kaoriya.net