はじめに
BULK INSERT と COPY なら COPY の方が圧倒的強者なはず。これは、そんな些細な事が気になって暇つぶしがてらに遊んでみた記録。
制限
- PostgreSQL の事しか考えない
- 空のテーブルに新規データ登録する事だけを考える
- ユニーク制約についても考えない(衝突しない)
- 遊びなので全体的に雑
activerecord-import について
BULK INSERT の実装として zdennis/activerecord-import を使う。自分で BULK INSERT 書くのが面倒なので。
- バリデーションしてくれるけど、インスタンス化するからひどく遅くなる
- 成功行数とかとれるけど、必要ない場合を考える
- MySQL だったら
INSERT ... ON DUPLICATE KEY UPDATE
に対応してて上書きとかできるけど、これも必要ない場合を考える
環境
- MacBookAir7,1
- Intel Core i7 2.2 GHz
- DDR3 4GBx2
- APPLE SSD SD0256F
- Mac OS X 10.11.4
- Ruby 2.3.0p0 (rbenv)
- Rails 4.2.6
- pg 0.18.4
- postgres-copy 1.0.0
- activerecord-import 0.13.0
- ridgepole 0.6.4
- PostgreSQL 9.5.2
準備
テーブル
Schemafile
create_table 'hundred_columns', force: :cascade do |t|
100.times do |n|
t.string "column_#{n}"
end
end
モデル
$ rails g model HundredColumn
app/models/hundred_column.rb
class HundredColumn < ActiveRecord::Base
acts_as_copy_target
end
計測
require 'benchmark'
require 'csv'
require 'hundred_column'
Tempfile.create('benchmark-import') do |tmp|
row = Array.new(100) { |i| "column #{i}" }.join(',')
10_000.times { tmp.puts row }
tmp.flush
Benchmark.bm(35) do |x|
header = Array.new(100) { |i| "column_#{i}" }
HundredColumn.connection.execute('TRUNCATE TABLE hundred_columns')
x.report('activerecord-import/validate: true') do
csv = CSV.foreach(tmp.path)
csv.each_slice(500) do |rows|
HundredColumn.import header, rows
end
end
HundredColumn.connection.execute('TRUNCATE TABLE hundred_columns')
x.report('activerecord-import/validate: false') do
csv = CSV.foreach(tmp.path)
csv.each_slice(500) do |rows|
HundredColumn.import header, rows, validate: false
end
end
HundredColumn.connection.execute('TRUNCATE TABLE hundred_columns')
x.report('postgres-copy copy_from') do
HundredColumn.copy_from tmp.path, header: nil, columns: header
end
HundredColumn.connection.execute('TRUNCATE TABLE hundred_columns')
conn = HundredColumn.connection.raw_connection
enco = PG::TextEncoder::CopyRow.new
x.report('pg copy_data') do
conn.copy_data "COPY hundred_columns (#{header.join(',')}) FROM STDIN", enco do
CSV.foreach(tmp.path) do |row|
conn.put_copy_data row
end
end
end
end
end
__END__
user system total real
activerecord-import/validate: true 21.260000 0.270000 21.530000 ( 23.511967)
activerecord-import/validate: false 6.270000 0.080000 6.350000 ( 7.988115)
postgres-copy copy_from 0.050000 0.020000 0.070000 ( 0.270931)
pg copy_data 0.880000 0.030000 0.910000 ( 1.078610)
まとめ
- activerecord-import は遅い
- COPY は優秀
- 途中で問題がある行に遭遇した時、COPY は全部なかったことになる
- UPSERT やバリデーションなどが必要な場合についても要検討
以上、暇つぶしおわり。