LoginSignup
2
6

More than 5 years have passed since last update.

[Rails]大規模データのseed

Posted at

数十万行のデータを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に処理を移譲するってちょいちょい使う。
ローカル環境のデータをどう揃えるかはフェーズによって変えていった方が良さそう。

2
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6