🫠

update_all vs activerecord-import

2025/02/20に公開

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