はじめに
本記事では、postgres-copyの使い方とRails6から追加されたinsert_allのパフォーマンスの比較を行います。
環境
Ruby 2.6.6
Rails 6.0.3
Docker for Mac
前提
下記のテーブルを作成済みとします。
- postsテーブル
name | type | description |
---|---|---|
id | bigint | 主キー |
title | string | 投稿タイトル |
body | text | 本文 |
created_at | datetime | 作成日時 |
updated_at | datetime | 更新日時 |
postgres-copyの導入
まずはGemfileにpostgres-copyを追加します。
gem 'postgres-copy'
その後bundle install(Dockerを使っています)
$ docker-compose run web bundle
次に、Postモデルにacts_as_copy_targetを追記します。
class Post < ApplicationRecord
acts_as_copy_target # 追加
end
これで、以下4つのメソッドがPostモデルに追加されます。
- copy_to
- copy_to_string
- copy_to_enumerator
- copy_from
今回使うのはcopy_from
のみになります。
パフォーマンス比較
結論から申し上げると、copy_fromの方が早かったです。
10万行のCSVをinsertしてパフォーマンスを比較してみます。
insert_all
$ require 'csv'
$ now = Time.zone.now
$ rows = CSV.read('/app/tmp/test.csv', headers: true).map do |row|
{
id: row[0],
title: row[1],
body: row[2],
created_at: now,
updated_at: now
}
end
$ Benchmark.realtime do
Post.insert_all(rows)
end
=> 8.32
約8秒でインサートできました。
copy_from
$ Benchmark.realtime do
Post.copy_from('/app/tmp/posts.csv')
end
=> 4.33
こちらは約4秒ですね。
結果
メソッド | 実行時間(秒) |
---|---|
insert_all | 8.32 |
copy_from | 4.33 |
ということで、copy_fromの方がおよそ2倍早いという結果になりました。
考慮すべきポイント
created_at, updated_atが自動で挿入されない
これはinsert_allもcopy_fromも同じなのですが、ActiveRecordで普通にcreateする際に挿入されるcreated_atとupdated_atが自動で挿入されないので、insert_allであれば、ハッシュの値に入れる必要があります。
copy_fromでは、created_atとupdated_atの列と値を用意する必要があります。
不正データのバリデーション
重複データ
insert_allは重複データがあった場合に無視します。なので、実務ではinsert_all!の方を使うことになるかと思います。(insert_all!の場合、レコードの重複があったら、ActiveRecord::RecordNotUniqueの例外がraiseされます)
copy_fromも同様に、重複データがあっても例外は上がりません。
値が不正なデータ
insert_allもcopy_fromも、Railsのモデルにvalidationを設定していてもバリデーションがスキップされます。
そのため、取り込むデータを作成する段階で、正しい値のみを入れておく必要があります。
子レコードの同時インサート
activerecord-importだとrecursiveオプションがある
insert_allもcopy_fromも、子レコードの同時インサートには対応していません。なので、同時インサートが必要なテーブル構成の場合は、activerecord-importを使わざるを得ないのかなと思っています。。。
MySQLにはrecursiveオプションはありませんのでご注意ください。
まとめ
実用的には大量seedデータ投入の高速化に用いるくらいかなとは思いました。
そのseedデータ作成もカラム追加するごとにメンテが必要になるので、運用面も考えると少々面倒かなとか思ったり、、、
ともあれ、insertそのものが早いのは事実なので、活躍の場を与えてあげたいものです。
icare開発者ブログもよろしくね!