Railsでバッチのような大量データを扱う際にメモリの使用量を抑える方法として、find_eachを使い分割してレコードを取得して処理をする方法があります。
今回は、実際にどれくらいメモリの使用量が抑えられるのか検証してみました。
実行環境
初期のコンテナの状態
検証コード
# find_each未使用の場合 p Time.zone.now.strftime("%H:%M:%S") items = [] User.all.each do | item | items << item.name end p items.length p Time.zone.now.strftime("%H:%M:%S") # find_each使用 p Time.zone.now.strftime("%H:%M:%S") items = [] User.all.find_each do | item | items << item.name end p items.length p Time.zone.now.strftime("%H:%M:%S")
回数 | MEM USAGE | MEM(%) |
---|---|---|
1回目 | 1.382Gib | 70.79% |
2回目 | 1.414Gib | 72.43% |
3回目 | 1.398Gib | 71.61% |
3回実行し、MAXで72.43%ほどを使用していることが分かりました。
実行時のメモリ消費量
回数 | MEM USAGE | MEM(%) |
---|---|---|
1回目 | 438.9Mib | 22.48% |
2回目 | 404.5Mib | 20.72% |
3回目 | 430.5Mib | 22.05% |
3回実行し、MAXで22.48%ほどを使用していることが分かりました。
find_eachを使用することで、メモリの消費が大幅に抑えられることが分かりました。
バッチなどの大量データを使用する場合にはfind_eachを使用し、メモリの消費を抑えることを意識した方が良いと思います。
find_eachはソートの指定ができません。 下記は、find_each実行時のログです。
[1m[36mUser Load (1.4ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` > 887000 ORDER BY `users`.`id` ASC LIMIT 1000[0m [1m[36mUser Load (1.2ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` > 888000 ORDER BY `users`.`id` ASC LIMIT 1000[0m [1m[36mUser Load (1.4ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` > 889000 ORDER BY `users`.`id` ASC LIMIT 1000[0m
上記のように、idのASC(昇順)でソートし、1000件ずつ取得しています。
ソートの指定をして、eachで取得した場合です。
# each + sort User.all.order(:name).each do | item | items << item.name end
結果の中から先頭の5件を抽出し表示した結果です。
ちゃんとソートされていますね。
"1太郎"
"2太郎"
"3太郎"
"4太郎"
"5太郎"
"6太郎"
次にfind_eachを使用したケースでソートを指定した場合です。
# find_each + sort User.all.order(:name).find_each do | item | items << item.name end
同じく、先頭5件を抽出した結果です。
結果を見るとソートされていないことが分かります。
"6太郎"
"1太郎"
"3太郎"
"5太郎"
"4太郎"
"2太郎"
発行しているクエリを確認するとidでソートされていることが分かります。
[1m[36mUser Load (1.7ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` > 997000 ORDER BY `users`.`id` ASC LIMIT 1000[0m [1m[36mUser Load (1.2ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` > 998000 ORDER BY `users`.`id` ASC LIMIT 1000[0m [1m[36mUser Load (1.3ms)[0m [1m[34mSELECT `users`.* FROM `users` WHERE `users`.`id` > 999000 ORDER BY `users`.`id` ASC LIMIT 1000[0m