1
0

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 3 years have passed since last update.

bulk_insertより高速!?postgres-copyで大量データinsertしてみた

Last updated at Posted at 2021-06-24

はじめに

本記事では、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開発者ブログもよろしくね!

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?