Log目次

【Rails】find_eachによるメモリの使用量の変化

作成日 2019-07-29更新日 2019-07-29

はじめに

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")

find_each未使用

回数MEM USAGEMEM(%)
1回目1.382Gib70.79%
2回目1.414Gib72.43%
3回目1.398Gib71.61%

3回実行し、MAXで72.43%ほどを使用していることが分かりました。

find_each使用

実行時のメモリ消費量

回数MEM USAGEMEM(%)
1回目438.9Mib22.48%
2回目404.5Mib20.72%
3回目430.5Mib22.05%

3回実行し、MAXで22.48%ほどを使用していることが分かりました。

結果

find_eachを使用することで、メモリの消費が大幅に抑えられることが分かりました。

バッチなどの大量データを使用する場合にはfind_eachを使用し、メモリの消費を抑えることを意識した方が良いと思います。

注意点

find_eachはソートの指定ができません。 下記は、find_each実行時のログです。

[1mUser Load (1.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` > 887000 ORDER BY `users`.`id` ASC LIMIT 1000 [1mUser Load (1.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` > 888000 ORDER BY `users`.`id` ASC LIMIT 1000 [1mUser Load (1.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` > 889000 ORDER BY `users`.`id` ASC LIMIT 1000

上記のように、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でソートされていることが分かります。

[1mUser Load (1.7ms) SELECT `users`.* FROM `users` WHERE `users`.`id` > 997000 ORDER BY `users`.`id` ASC LIMIT 1000 [1mUser Load (1.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` > 998000 ORDER BY `users`.`id` ASC LIMIT 1000 [1mUser Load (1.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` > 999000 ORDER BY `users`.`id` ASC LIMIT 1000
find_eachで指定できるオプションは、start(処理開始位置)とbatch_size(同時処理数)のみので、find_each + sortが必要なケースは注意が必要です。

参考

find_each リファレンス