酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

「Upsert」 大量のデータを一括でInsert/UpdateするGem!

Rails/Rubyで大量のデータを一括で新規登録・更新スクリプト を書く場合は、ActiveRecordは生成コストが高くて、必ずしも向いていません。そんな状況での利用にオススメなのが、この 『upsert』というGemです。

このGemがサポートしているデータベースは、MySQL、PostgreSQL、SQLite3です。


Upsertが適した利用シーン

実装としては、OracleやMySQLのPL/SQL、PostgreSQLのPL/pgSQLのスクリプトを一時的に定義して、 その中で対象テーブルに一致するキーのレコードがあればupdate、なければinsert処理を行ってくれます。 DB内部でレコードの有無を判定して、insert/update処理までおこなってくれるので、当然速度は早くなります。

また、Commitを複数件単位でまとめてくれる機能も持っているので、ActiveRecordで同じ機能を 実装する場合に比べると、更に速度が早くなります。

一方でデメリットとして、ActiveRecord側に設定しているvalidationや便利機能が使えなくなるので、 そのデメリットを考慮しても速度を優先させたいような処理で限定して使うことをおすすめします!

Gemのインストール

Gemfileに以下を追加して、コンソールでbundle installを実行してください。

1
2
# Bulk Insert/Update
gem 'upsert'

ActiveRecordでUpsertを使う場合

Petモデルのキーがname、カラムがname, breedの場合 にUpsertする場合は次のように記述します。

1
2
3
require 'upsert/active_record_upsert'

Pet.upsert({name: 'Jerry'}, {breed: 'beagle', updated_at: Time.now.iso8601, created_at: Time.now.iso8601})

ActiveRecordで一括でUpsertを行う場合

Petモデルのキーがname、カラムがname, breedの場合 に2件のデータを一括で、Upsertする場合は次のように記述します。

1
2
3
4
5
6
7
# Bulk でinsert / updateを実行 (mass upsert)
self.connection_pool.with_connection do |c|
  Upsert.batch(c, table_name) do |upsert|
    upsert.row({name: 'Jerry'}, {breed: 'beagle', updated_at: Time.now.iso8601, created_at: Time.now.iso8601})
    upsert.row({name: 'Pierre'}, {breed: 'tabby', updated_at: Time.now.iso8601, created_at: Time.now.iso8601})
  end
end

あとがき

Ruby/ActiveRecordで大量のデータを登録するようなスクリプトを書くことは 必ずしも適していない。。。かもしれませんが、Rubyで複雑なデータを加工して 登録したいようなケースもあると思います。 ぜひ、そういった時の速度改善に使ってみてください!

おまけ

2009年の記事ですが、結構面白いのでオススメです!

Mass inserting data in Rails without killing your performance

おすすめの書籍