🫠
update_all vs activerecord-import
update_all vs activerecord-import
高速なバルクアップデートを実現する方法として、update_all と activerecord-import の2つがあります。それぞれ特性が異なるので、それらについて書いていきます!
そもそもバルクアップデートとは?
バルクアップデート とは、複数のレコードを 個別に処理するのではなく、まとめて一括で更新する ことです。
通常の update
メソッドは 1レコードずつSQLを実行 するため、大量データを扱うことに比例し、速度が低下してしまいます。
バルクアップデートを使い、SQLの実行回数を減らしてパフォーマンスを向上 させることができます。
update_all
基本的な使い方
# 与えられた属性のデータを一括更新
User.update_all(active: true)
# 条件に該当したデータを一括更新
User.where("email LIKE ?", "%@example.com").update_all(active: true)
# 指定したデータ数を一括更新
User.where("email LIKE ?", "%@example.com").limit(50).update_all(active: true)
メリット
- 1回のSQL実行で条件に該当する全レコードを更新出来る
懸念点
- コールバックやバリデーションが実行されない為、データの整合性が崩れる可能性有り
- updated_at が自動更新されない為、手動で更新する必要がある。
- レコードごとに異なる値の更新が難しい。
例えば、users
テーブルに以下のデータがあり、それぞれの id
に対して 異なる points
の値で更新したい 場合、update_allでは難しいです。
id | name | points |
---|---|---|
1 | Taro | 100 |
2 | Hanako | 200 |
3 | Jiro | 300 |
updateを使えば、レコードごとに異なる値を更新できますが、1件ずつの処理でパフォーマンスは悪化します。これらの懸念点を補うものとして、activerecord-import があります。
activerecord-import
基本的な使い方
gem 'activerecord-import'
bundle install
update_users = []
User.find_each do |user|
user.points = calculate_new_points(user)
update_users << user
end
# MySQL の場合
User.import update_users, on_duplicate_key_update: [:points]
メリット
- UPSERT(ON DUPLICATE KEY UPDATE)を活用できる
- updated_at も自動更新
- コールバックやバリデーションが適用される
- 異なる値に一括で更新する事も出来る
比較
まず処理速度の比較をするために、6万件のデータを更新してみます。
update_all
User.where.not(birthday: nil).update_all(birthday: Date.new(2000, 1, 1))
# 0.15042175 seconds
activerecord-import
# コールバックは無し
users_to_update = []
User.find_each do |user|
user.birthday = Date.new(2000, 1, 1) unless user.birthday.nil?
users_to_update << user
end
# MySQL の場合
User.import users_to_update, on_duplicate_key_update: [:birthday]
# 3.462612252 seconds
結論、速度の面では update_all が上回った。これは発行されるSQLが異なるため(update_allは一括更新、activerecord-importは挿入、場合によって更新の為、多くの処理が有)、当然の結果とも言える。他の側面はこちら↓
手法 | コールバック、バリデーション | updated_at 自動更新 | 速度 | 柔軟性 |
---|---|---|---|---|
update_all | ❌ なし | ❌ なし | ◎ 非常に高速 | △ 条件ベースの一括更新 |
activerecord-import | ✅ あり | ✅ あり | ○ 高速 | ◎ 異なる値の更新も可能 |
まとめ
- 速度最優先なら update_all(ただし、updated_at の更新は update_all(updated_at: Time.current) などで手動対応)
- バリデーション・コールバック・異なる値の更新が必要なら activerecord-import
以上、update_all vs activerecord-import でした!それぞれに良さがあるので、バルクアップデートを適切に使いこなせるようにしていきましょう🚀
Discussion