この記事で言いたいこと
大量データのループ処理を扱う際にはfind_each
がメモリ節約に有効である。
きっかけ
先日、Ruby on Railsで大量データを扱う機会がありました。
その際find_each
というメソッドがあることを学んだので、each
との違いを明らかにしながらまとめていこうと思います。
eachを用いた例
Student
というモデルがあると仮定します。
studentsテーブルにある生徒の名前(nameカラム)を全員分出力したい場合、皆さんはどのようなコードを書きますか?
Student.all.each do |student|
puts student.name
end
上記のようにStudent.all
でレコードを全件取得してからeach
メソッドを使用することで、studentsテーブルにいるすべての生徒の名前を出力することができます。
レコード数が少ない場合は上記の例のように書くのがよいでしょう。
しかしレコードが大量にある場合は、上記のようにレコードを一気に取得する方法だとメモリを圧迫してしまいます。
ここで満を持してfind_each
の登場です。
find_eachを使ってみる
find_each
のメリットは「分割してレコードを取得できる」ことです。
以下に例を示します。
Student.find_each(batch_size: 500) do |student|
puts student.name
end
引数のbatch_size
は1回あたりに取得するレコード数を示しています(デフォルトでは1000件)。
はじめにレコードを500件取得してループ、また次の500件を取得してループ...といった様子です。
出力結果は前述のeach
を用いた例と全く同じです。
さらに理解を深めるため、両者で発行されるSQLを比較してみます。
SQLの比較
findの場合
SELECT * FROM students
Student.all
を使用しているため、一気に全レコードを取得しています。
find_eachの場合
-- 1回目
SELECT * FROM students ORDER BY student_id ASC LIMIT 500
-- 2回目(ループ処理後)
SELECT * FROM students WHERE student_id > 500 ORDER BY student_id ASC LIMIT 500
-- 3回目(ループ処理後)
SELECT * FROM students WHERE student_id > 1000 ORDER BY student_id ASC LIMIT 500
-- 以後同様に続く...
(student_id
はstudentsの主キーです )
- 主キーでソートする
- 取得できるレコードの取得数の上限を
batch_size
個にする - 2回目以降は
(主キー)>(前回取得したレコードのうち最大の主キー)
の条件を付与する
上記の仕組みによってレコードが分割して取得できていることがわかりました。
まとめ・感想
冒頭でも述べたように、大量データを扱う際にはメモリに気を遣う必要がありfind_each
は有効な手段です。
今回紹介した以外にもfind_each
は取得したいレコードの始点・終点の主キーを指定できたり、降順に並べたりすることもできます。また、似たようなメソッドにfind_in_batches
というものもあります。
気になる方は是非調べてみてください!
参考
Railsドキュメント(find_each)
Railsドキュメント(find_in_batches)
Railsガイド