11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FusicAdvent Calendar 2018

Day 25

巨大CSVインポート処理を見直して50分短縮

Posted at

割と手垢のついたテーマではありますが、Railsを使ったCSVインポート処理を、1時間->10分に短縮したので、その時のポイントを列挙します。

前提

  • CSVは1万件のレコードかつ、100程度の列数
  • CSVインポートはRooを使用 https://github.com/roo-rb/roo
  • Parentのhas_many関係にChildがある

見直し前のコード(重要な部分のみ)


xlsx = Roo::Spreadsheet.open(file)
xlsx.sheet(0).each(
  column1: '列1',
  column2: '列2',
  ...
).with_index do |item, i|
  parent = Parent.find_or_create_by(id: xxx)
  child = Child.new
  child.column1 = item[:column1]
  master_item = MasterItem.find_by(name: item[:column2]) ...(1)
  child.column2 = master_item.item
  parent.children << child ...(3)
  parent.save ...(2)
end

見直し(1): いちいちDBにアクセスしない

コード上の(1)ですが、DBから条件に一致するデータを取り出しています。1万件近いCSVデータのループ内なので、同数のselect文が走ることに。そのselect対象のテーブルのレコード数にもよりますが、あらかじめ配列等に格納していた方が良さげです。

見直し(2): こまめにsaveしない

重要な書類の作業中には定期的に保存した方が吉ですが、このケースではかえって逆効果です。

bulk insert を使って一気にinsertを行います。
https://github.com/zdennis/activerecord-import

見直し(3): <<を使うとその時にsaveされる

parent.children << childといった書き方で子データを格納すると、この瞬間にsaveが走ります。どのみちsaveされるので別にこれでもいいのですが、bulk insertで一気に保存しようとしているのに、この部分で足を引っ張ることになります。

見直し後のコード

master_items = MasterItem.all.pluck(:name, :item) ...(1)
parents = []
xlsx = Roo::Spreadsheet.open(file)
xlsx.sheet(0).each(
  column1: '列1',
  column2: '列2',
  ...
).with_index do |item, i|
  parent = Parent.find_or_create_by(id: xxx)
  master_item = master_items.find{|m| m[0]==item[:column2]} ...(1)
  parent.children.build(
    column1: item[:column1]
    column2: master_item[1]
  ) ...(3)
  parents << parent...(2)
end
Parent.import parents, recursive: true, on_duplicate_key_update: [:id] ...(2)

見直し後(1): 必要なDBのデータだけメモリ常にもつ

今回のケースで欲しかったのは、MasterItemnameに条件が一致するitemなので、この2つだけ配列で格納して、検索自体はその配列上で行うことにしました。

見直し後(2): bulk insert & bulk update

saveしたいparentを配列に格納して、Parent.import parents とするだけで bulk insertが走ります。
ここでのポイントは recursiveon_duplicate_key_updateです。
has_many関係にあるchildrenも一緒にinsertしたい場合は、recursive: trueというオプションを渡します。
また、今回は条件によってはupdateにしたいため、on_duplicate_key_update: [:id]でupdateにも対応します。

見直し(3): buildを使う

せっかくのbulk insertを活かすためにbuildを使って子モデルデータを格納します。

結果

冒頭にも書いてますが、見直し前のコードでは1時間ほどかかっていた処理が50分近く短縮され、約10分で完了しました。
バリデーションやロールバックなどの整備をすると速度に変動が出てくるかもしれませんが、十分問題ないレベルかなと思います。

11
3
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
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?