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

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

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


🚜 Upsertが適した利用シーン

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

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

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

🐯 Gemのインストール

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

# Bulk Insert/Update
gem 'upsert'

🎂 Active RecordでUpsertを使う場合

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

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

🐠 Active Recordで一括でUpsertを行う場合

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

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

😼 おまけ

2009年の記事ですが、結構おもしろいのでオススメです!

Mass inserting data in Rails without killing your performance

📚 おすすめの書籍