Feedforce Developer Blog

フィードフォース開発者ブログ

AWS SDK for Ruby で S3 Select を使って S3 にある CSV ファイルからデータを抽出する

こんにちは、id:daido1976 です。つい先日 30 歳になりました。

S3 に置いてある大きめの CSV ファイルから特定のデータだけ抽出して取得するのに S3 Select は大変便利です。

Amazon Athena とは違い単一のファイルのみしか対象にできませんが、その分手軽に利用することができます。

Python や Go の日本語記事はあるものの、Ruby の記事がなかったので書かせていただきます。

実行環境

  • Ruby 2.7.0
  • aws-sdk-s3 1.60.2

コード例

今回は複数の id をキーに CSV ファイルからデータを抽出する例を見てみましょう。

少し長いですが、以下のコードで動きます。

Aws::S3::Client#select_object_content が Ruby から S3 Select を使うためのメソッドです。

require 'aws-sdk-s3'
require 'csv'

s3_client = Aws::S3::Client.new(region: 'ap-northeast-1')

bucket = 's3-select-sample'
key = 'target_sample_users.csv'

# サンプル用のデータを準備する
s3_client.put_object(bucket: bucket, key: key, body: File.read('sample_users.csv'))

# 与えられた ids 配列の id で検索し、id と name だけ抽出するクエリを作成する
def build_query(ids)
  <<~QUERY
    SELECT
    s.id
    , s.name
    FROM S3Object s
    WHERE s.id
    IN ('#{ids.join("', '")}')
  QUERY
end

# `#select_object_content` に渡すパラメータを作成する
def build_params(bucket, key, query)
  {
    bucket: bucket,
    key: key,
    expression_type: 'SQL',
    expression: query,
    input_serialization: {
      # field_delimiter を "\t" にすることでタブ区切りのファイルにも対応可能
      csv: { file_header_info: 'USE', allow_quoted_record_delimiter: true, record_delimiter: "\n", field_delimiter: ',' }
    },
    output_serialization: {
      csv: { record_delimiter: "\n", field_delimiter: ',' }
    }
  }
end

# id が 1 と 50 のユーザを検索する
query = build_query([1, 50])
params = build_params(bucket, key, query)

# S3 Select を使ってレスポンスを取得する
response = s3_client.select_object_content(params)

# `#event_type == :records` の中にレコード(抽出されたデータ)が含まれる
csv_list = response.payload.select { |p| p.event_type == :records }.map(&:payload).map(&:read)
csv = CSV.parse(csv_list.join)

# マルチバイト文字列を扱う場合
csv.map do |row|
  row.map { |r| r.force_encoding('UTF-8') }
end

p csv

sample_users.csv は以下のような CSV を想定しています。(値は ffaker gem を使って作成しました)

id,name,email,address,job
0,小出整,idell@boyle.us,773-9409 群馬県大府市安芸区福山市0丁目4番4号,運転手
1,坂口昭三,clifford.steuber@walshkutch.name,663-8507 東京都豊島区裾野市842,管理栄養士
2,池間悠希,taunya.kerluke@hellerweimann.ca,898-7832 東京都港区鯖江市8丁目5番3号,理学療法士
3,北野真美,rick.nitzsche@harvey.info,411-8019 静岡県大野市中村区駒ヶ根市9丁目4番9号,テレビディレクター
4,関根京子,francene@cummings.com,809-0176 東京都練馬区夕張市524,クラブDJ
5,熊本とうこ,bo.oconner@bradtkemiller.biz,423-3182 青森県柏原市佐渡市2丁目5番9号,指揮者
...
50,酒向彩香,seema.denesik@fahey.us,481-5386 宮崎県上高井郡にかほ市9丁目1番1号,クラブDJ

上記コードを実行すると、出力は以下のようになります。

[["1", "坂口昭三"], ["50", "酒向彩香"]]

補足

レスポンスのサイズが不明な場合、S3 Select はデータを複数のイベントに分割し、ストリームとして返す仕様になっています。

今回は全てのデータが到着するのを待ってから処理するようなコードになっていますが、AWS Developer Blog にはストリームを活かして複数のイベントを非同期に処理する方法も記載されていますので、ご興味のある方はぜひご覧になってください。

ちなみに S3 Select を S3 Management Console から試したい場合は以下の箇所から利用できます。ご参考まで。

f:id:daido1976:20200404205717p:plain
最初この位置にあるの気づかなかった…

結論

S3 Select めっちゃ便利。

参考