行動すれば次の現実

Webエンジニアが書くテック中心の個人ブログ

RubyでS3にある巨大なファイルをストリーム処理する方法

最近の案件で、AWS S3に配置された1GB程度のCSVファイルを処理することがありました。 そのままメモリに載せてしまうとすぐにメモリオーバーになってしまうので、ストリーム処理で実装することにしました。 その時の内容を整理してみましたので、同様のことで悩んでいる方の参考になれば嬉しいです。

前提

S3クライアントとして「aws-sdk-s3」を使用します。

github.com

ローカルにS3のファイルダウンロードする

まずは、S3にあるファイルをローカルにダウンロードします。

S3から直接ストリーミングしながら処理をしていくことも可能なのですが、途中でネットワーク等に異常があった場合のリカバリを考慮する必要があるので、ローカルにダウンロードしてから処理することをオススメします。

実装

事前にS3のクライアントをインスタンス変数で作成しておきます。

Aws.config.update(
  region: 'ap-northeast-1',
  credentials: Aws::Credentials.new(ENV['S3_BUDGET_ACCESS_KEY'], ENV['S3_BUDGET_SECRET_ACCESS_KEY'])
)
@bucket = ENV['S3_BUDGET_NAME']
@s3_client = Aws::S3::Client.new

ローカル上の「tmp/temp_file.csv(任意の名前)」というファイルに、S3上の@bucket内にある「s3_file.csv(任意の名前)」の内容をコピーします。

コピー時に大量のデータ転送が発生しますので、ある程度の時間を要します。

File.open('tmp/temp_file.csv', 'wb') do |file|
  @s3_client.get_object({ bucket: @bucket, key:'s3_file.csv' }, target: file)
end

ファイルをストリーム処理する

ローカル上のファイルをCSV.newでCSVファイルに変換して、eachで一行ずつ読み込みます。 こうすることでストリーム処理でファイルを読み込むことができます。

メモリに乗るのは一行ごとのデータになりますので、メモリに優しく処理をすることができます。

File.open('tmp/temp_file.csv', 'r') do |file|
  CSV.new(file).each do |row|
    # ここに処理を書いていきます
  end
end

参考:

Downloading Objects from Amazon S3 using the AWS SDK for Ruby | AWS Developer Tools Blog