やっちまった
話を単純化してお伝えすると、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_each
のeach
との違いとして、SQLを発行する時にLIMIT
をかけてくれるのは想像つくが、ORDER BY id ASC
も入るらしい。
find_each参考
rake task参考
- https://docs.ruby-lang.org/ja/latest/class/Rake=3a=3aTask.html
- https://qiita.com/suzuki-koya/items/787b5562d2ae1a215d94
解決策2:SQLを直接発行する
結局今回は、上のfind_eachも行わず、DBクライアントアプリでSQLを書いて終わらせることにしました。
UPDATE students SET is_active = true;
このSQLだけ見ると、「最初からそうしなよ」と思われそうですが、実際はもうちっとだけ複雑なSQL!
これが今回サーバーへの負荷が少ない方法と判断されました。
1のupdate_columnだと結局はuser.update_column
のSQLとその処理が1万回以上連続的に走ることになるが、SQLでやればSQLの発行は一度で済み、めでたしめでたしでした。