0
0

More than 1 year has passed since last update.

eachの危険性を体で覚えて、その代替手段を知る(Ruby, Rails)

Last updated at Posted at 2022-07-05

やっちまった

話を単純化してお伝えすると、1万件以上レコードがあるusersテーブルに対して、is_activeというカラムがあって、全員trueにしたかったです。

これを実現するのに、全usersにeachをかけてupdate_columnする処理を仕掛けたら、本番のサーバーを一瞬死なせてしまい、めちゃめちゃ焦りました

User.all.each do |user|
  user.update_column(:is_active, true)
end

User.all.eachすると、該当するレコードを全て取得してきて、メモリに置いて処理をするので、それで激烈に重くなってしまうようです。この場合、Userのインスタンスを1万件以上取得して配列にしてそれをメモリにドーン!と置いて処理しようとしたので、それがメモリを強烈に圧迫してしまったみたい。

解決策1:find_eachを使う

この時の対処方法の1つがfind_eachを使うこと。

これはレコードを持ってくる数に制限をかけながら処理を実行するので、メモリへの負担を軽減できます。
デフォルトでは1000件です。

users.find_each do |user|
  user.update_column(:is_active, true)
end

find_eacheachとの違いとして、SQLを発行する時にLIMITをかけてくれるのは想像つくが、ORDER BY id ASCも入るらしい。

find_each参考

rake task参考

解決策2:SQLを直接発行する

結局今回は、上のfind_eachも行わず、DBクライアントアプリでSQLを書いて終わらせることにしました。

UPDATE students SET is_active = true;

このSQLだけ見ると、「最初からそうしなよ」と思われそうですが、実際はもうちっとだけ複雑なSQL!
これが今回サーバーへの負荷が少ない方法と判断されました。
1のupdate_columnだと結局はuser.update_columnのSQLとその処理が1万回以上連続的に走ることになるが、SQLでやればSQLの発行は一度で済み、めでたしめでたしでした。

0
0
1

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