数十万行のデータをseedで入れようとするとローカルの環境だとメモリが膨れてエラーになる。
seedbankなどでファイル分割、activerecord-importで一括処理しても無理だったので、MySQLのLOAD DATA LOCAL INFILEを使う。
CSVのフォーマット
db/seeds/001_table_name.csv
id,hoge_id,name
1,1,'fugafuga'
db/seeds配下に
番号_table_name.csv
で配置する。使依存関係を記述する一番簡単な方法としてファイルに番号を付与した。
1行目にheaderで、文字列は'でくくり、文字列内の'は''とする(LOAD DATA LOCAL INFILEのオプションを変えればこの限りではない)
本番からデータを持ってくるコマンド。
QUOTEでシングルクオートにくくり、,区切りに置換、\'を''に置換している。このcsvをscpでローカルに持ってくる
mysql -u user -h hoge -ppass production -e "SELECT id, hoge_id, QUOTE(name) as name FROM table_name;" | sed -e 's/\t/,/g' | sed -e "s/\\\\\\\\'/''/g" > /tmp/001_table_name.csv
seed.rb
db/seeds.rb
# ビッグデータはmysqlのload data infileに任せる
Dir.glob(Rails.root.join('db/seeds/*.csv')).sort.each do |file_name|
table_name = file_name.match(/\d+_(.+).csv/)[1]
header = ""
# csvの1行目はheader
File.open(file_name) { |f| header = f.readlines[0] }
# 改行消す
header = header.gsub(/(\r\n|\r|\n)/, '')
sql = "
LOAD DATA LOCAL INFILE '#{file_name}'
REPLACE INTO TABLE #{table_name}
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '\\''
LINES TERMINATED BY '\\n'
IGNORE 1 LINES
(#{header})
set updated_at = NOW(), created_at = NOW()
"
ActiveRecord::Base.connection.execute(sql)
end
まとめ
大規模データ処理でRubyだとパフォーマンスが出ない時に、MySQLに処理を移譲するってちょいちょい使う。
ローカル環境のデータをどう揃えるかはフェーズによって変えていった方が良さそう。