Alstrocrack Tech Blog

考えたこと、学んだこと など

Ruby × CSV × AWS S3

はじめに

Ruby で CSV を扱う方法をいつも忘れるので、備忘録として記事にして残しておこうと思います。Claude Code や Cursor が何でも実装してくれる世界になってしまいましたが、発信することや備忘録を残しておくことは良いことだと思うので。

Ruby の CSV クラスについてこちら docs.ruby-lang.org

AWS の S3 にある CSV を取得して扱う

AWS S3 上にある CSV を取得して処理をする場合は一般的に AWS SDK for Ruby を利用するので、その前提で記述します。

以下のコードで client を初期化して、S3 にある CSV を取得してきます。

# SDKを用いたS3アクセス用のclientインスタンス初期化(もしくはアクセスキーを発行して、初期化するか)
client = Aws::S3::Client.new(region: "ap-northeast-1")

# S3からオブジェクトを取得
s3_object = client.get_object(bucket: "your-bucket", key: "path/to/your_csv_file.csv").body

AWS SDK for Ruby のドキュメントはこちら docs.aws.amazon.com

CSV の読み込み系メソッドの比較

CSV クラスにおける代表的な読み込みメソッドについて比較をします。 Benchmark の数値を記述していますが、上が 1 万行の CSV で下が 10 万行の CSV の場合です。

foreach(ファイルから一行ずつ)

用途

他のメソッドと違って一括で全てメモリに展開するわけではないので、メモリ不足に陥らずに使えるようです。大規模な CSV を扱いたい時などに使います。

# s3_object [StringIO] S3のCSVデータを保持しているStringIOオブジェクト
CSV.foreach(s3_object, headers: true) { |row| p row }

# row
#<CSV::Row "id":"1">

# Benchmark
# user     system      total        real
# 0.062323   0.037299   0.099622 (  0.099879) (1万行)
# 0.380158   0.233433   0.613591 (  0.613826) (10万行)

parse × each(文字列から一行ずつ)

用途

文字列や IO を一括でパースして扱いたい場合に利用します。一括でメモリに載せます。

# s3_object [StringIO] S3のCSVデータを保持しているStringIOオブジェクト
CSV.parse(s3_object, headers: true).each { |row| p row }

# row
#<CSV::Row "id":"1">

# Benchmark
# user     system      total        real
# 0.062451   0.030136   0.092587 (  0.092584)
# 0.366748   0.144115   0.510863 (  0.510955)

read × each(ファイルから一度に)

用途

ファイルや IO をそのまま読み込み、テーブルとして扱います。一括でメモリに載せます。

# s3_object [StringIO] S3のCSVデータを保持しているStringIOオブジェクト
CSV.read(s3_object, headers: true).each { |row| p row }

# row
#<CSV::Row "id":"1">

# Benchmark
# user     system      total        real
# 0.062829   0.022257   0.085086 (  0.085019)
# 0.340553   0.131258   0.471811 (  0.471852)

まとめ

Ruby での CSV の取り扱いについてまとめました。

StringIO とheaders: trueを渡せば、どれも row[(attr_name)]で値を取り出すことが可能なので、この場合の使い方にはあまり違いがなさそうですね。

parsereadの違いは自分もそこまできちんと分かっているわけではありませんが、foreachparse / readは速度を取るか、メモリを節約するかのトレードオフになりそうです。

いつどこでメモリをどのくらい消費しているかみたいなのは今回測定していません。

余談

CSV gem の変化について

CSV は今までは default gem でしたが、Ruby 3.4 からは bundled gem に変更されたようです。つまり、以前までと違って Gemfile に明記しないと動かなくなるということです。場合によっては、以下のような警告が出ることもあります。

warning: /usr/local/lib/ruby/3.3.0/csv.rb was loaded from the standard library, but will no longer be part of the default gems starting from Ruby 3.4.0.
You can add csv to your Gemfile or gemspec to silence this warning.

default gem と bundled gem についてや公式こちらを参照ください。

徹底解説! default gems と bundled gems のすべて gihyo.jp

標準ライブラリのアップデート - Ruby 3.4.0 リリース www.ruby-lang.org

StringIO を扱えるようになった件

CSV はどこかのバージョンまでは StringIO を渡しても動かなかったのですが、以下の Pull Request で動くようになったようです。

自分も以前からパスだけではなく IO や StringIO を渡せるようになれば良いのになぁと思っていたんですが、もうすでにその変更は入っていたようです。

感謝。

github.com