概要
大量のデータをコピーしたい場合、SELECTして成形INSERTする作業をコード上で行おうとすると、メモリを食うしバッチサーバの性能が悪いとCPUネックで遅くなる
AWS上で出来るだけ早くうまいことしたい
手順
今回は以下のようにやってみた。
- Redshift → S3 に UNLOAD
- S3 → Batch Server にファイルをダウンロード
- Batch Server → RDS に LOAD DATA LOCAL INFILE
Redshift → S3
LOAD DATA LOCAL INFILE に流せるように CSV 形式で UNLOAD する
通常 UNLOAD は、ノードスライス単位で並列に行うので SELECT することを考えるとだいぶ速い
UNLOAD ( ${SELECT_STATEMENT} )
TO 's3:// ${S3_PATH} '
CREDENTIALS 'aws_access_key_id= ${ACCESS_KEY_ID} ;aws_secret_access_key= ${ACCESS_KEY} '
DELIMITER ','
ALLOWOVERWRITE
ESCAPE
ADDQUOTES
S3 → Batch Server → RDS
# ruby
bucket = AWS::S3.new.buckets[BUCKET_NAME]
tree = bucket.as_tree({prefix: S3_DIR_PATH})
# UNLOADしたファイルリスト
file_paths = tree.children.select(&:leaf?).map(&:key).reject{|f| f.match(/\/\z/)}
file_paths.each do |file_path|
local_file_path = "/tmp/#{file_path.gsub('/', '_')}_#{Process.pid}"
begin
# UNLOADしたファイルをBatch Serverに書き込む
File.open(local_file_path, 'w') do |f|
bucket.objects[file_path].read do |chunk|
f.write(chunk.force_encoding("UTF-8"))
end
end
# utf8mb4で読み込む
ActiveRecord::Base.connection.execute("SET character_set_database=utf8mb4")
# RDSにImport
ActiveRecord::Base.connection.execute(<<-SQL
LOAD DATA LOCAL INFILE '#{local_file_path}'
REPLACE INTO TABLE #{TABLE_NAME}
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
SQL
)
ensure
# ファイル削除
File.unlink local_file_path
end
end
追記: ESCAPEがうまくいかない
UNLOAD時に、不適切なところでESCAPEされ、RDSにLOADするときにエラーが起きる場合があった。
今回の場合、以下のようにtextカラムの最後の文字が(
だった場合におかしくなった。
# expected
"hoge","huga","longtext (","2016-01-01"
# actual
"hoge","huga","longtext (\,"2016-01-01"
対処法あれば教えて下さい。。