Railsでbulk insertする方法は主に
の2つの方法があります。パフォーマンス観点では、どちらが良いのか知りたくて検証してみました。
activerecord-importに関しては、READMEを読む限り、いくつかimportの方法がありますが、Rails6のinsert_allと同様にバリデーションを行わない以下2つの方法で比較しました。
- Works with raw columns and arrays of values (fastest)
- => カラム名と値の配列を使う方法
- Works with model objects (faster)
- => モデルオブジェクトを使う方法
結論
結論としては、activerecord-importのカラム名と値の配列を使う方法が一番早かったです。
以下は10万件のレコードをbulk insertした結果なのですが、件数を変えたり、何回か実行してみても、大体同じような結果になりました。レコード件数を変えて、複数回実行して平均を取った結果を以下の比較図に記載しています。
[1] pry(main)> Book.bulk_insert user system total real Rails6 insert_all 13.024230 0.051869 13.076099 ( 14.523158) import (カラム名と値の配列) 11.924948 0.048174 11.973122 ( 13.471458) import (モデルオブジェクト) 20.419098 0.155907 20.575005 ( 22.313445)
環境
検証環境のスペックは以下の通りです。
- OS: Ubuntu 18.04.2 LTS
- PC: メモリ 15.4 G、CPU Itel Core i7-8650U(1.90GHz [コア/スレッド数 4/8])
- MySQL: 5.7.29 (InnoDB)
検証方法
以下のコードを実行しました。
require 'benchmark' class Book < ActiveRecord::Base class << self def bulk_insert # Rails6で導入された insert_all で bulk insert するデータ insert_data = 100_000.times.each_with_object([]) do |_, array| array << { name: 'book name', author: 'book auther', created_at: Time.current, updated_at: Time.current } end # activerecord-import の import(カラム名と値の配列) で bulk insert するデータ import_data = [['book name', 'book auther', Time.current, Time.current]] * 100_000 # activerecord-import の import(モデルオブジェクト) で bulk insert するデータ import_model_data = 100_000.times.each_with_object([]) do |_, array| array << new(name: 'book name', author: 'book auther', created_at: Time.current, updated_at: Time.current) end Benchmark.bm 30 do |r| # Rails6で導入された insert_all で bulk insert transaction do r.report 'insert_all' do insert_all insert_data end raise ActiveRecord::Rollback end # activerecord-import の import(カラム名と値の配列で bulk insert transaction do r.report 'import (カラム名と値の配列)' do columns = [:name, :author, :created_at, :updated_at] import columns, import_data, validate: false end raise ActiveRecord::Rollback end # activerecord-import の import(モデルオブジェクト) で bulk insert transaction do r.report 'import (モデルオブジェクト)' do import import_model_data, validate: false end raise ActiveRecord::Rollback end end end end end
比較図
レコード10万件から1桁ずつ減らして、パフォーマンスを計測して図(値はBenchmarkのReal)にしてみました。Rails6 insert_allは、activerecord-import の import (カラム名と値の配列) よりちょっと遅いくらいだということがわかります。
感想
2020年3月時点では、Rails 6のinsert_allはパフォーマンス面ではactiverecord-importほどではなく、バッチサイズを指定できなかったり、モデルのバリデーションやコールバックが実行できないので、もう少し機能が増えてきたら用途が増えそうという感じがしています。
関わっている案件ではactiverecord-import使っているけど、置き換えるのはまだちょっと先かなと思ってます.