ActiveRecord の `#all`, `#find_each`, `#find_in_batches`, `#in_batches` の違いについて
表題の件、ずっと曖昧に理解していた。
1.2 複数のオブジェクトをバッチで取り出す - Active Record クエリインターフェイス - Rails ガイド
Rails のコードも読みながら ↑ の内容にプラスα してまとめる。
結論
処理するレコードが 1000 件以下なら何使っても一緒。
1000 件以上になってくると、 #all
ではなく、 #find_each
, #find_in_batches
, #in_batches
など ActiveRecord::Batches
のメソッドを用途に合わせて使い分けていくのが良い。
#all
Returns an ActiveRecord::Relation scope object.
該当コードは ここ。 ActiveRecord::Relation オブジェクトを返す。
users = User.all users.size # Fires "select count(*) from users" and returns the count users.each {|user| puts user.name } # Fires "select * from users" and loads user objects
テーブル全体を一度に取り出し、レコード 1 行ごとにオブジェクトを生成し、そのオブジェクトの配列をメモリに載せるので、メモリを圧迫する可能性がある。
#find_each
, #find_in_batches
, #in_batches
大事なのはここから。
#find_each
, #find_in_batches
, #in_batches
は ActiveRecord::Batches
として定義されており、バッチサイズで指定したレコード数ごとに処理できるため、大量のレコードを扱う際にメモリ消費量を削減できる。デフォルトのバッチサイズが 1000 件なので、それよりレコード数が少ない場合は #all
を使っても変わらない。
実は上記メソッドの関係は #find_each
の中で #find_in_batches
を呼び出し、 #find_in_batches
の中で #in_batches
を呼び出す、という関係になっている。
柔軟性でいうと #in_batches
> #find_in_batches
> #find_each
というわけだ。
#in_batches
から見ていく。
#in_batches
Yields ActiveRecord::Relation objects to work with a batch of records.
該当コードは ここ。長いので読めてない。 ActiveRecord::Relation オブジェクトをブロックに渡すので、一番柔軟な使い方ができる。
User.where("age > 20").in_batches do |relation| # Relation オブジェクトに対する処理が書ける relation.destroy_all end
relation.where
でつなげて絞り込みしたりとか relation.pluck
的な形でも使える。
#find_in_batches
Yields each batch of records that was found by the find options as an array.
該当コードは ここ。 #in_batches
で取り出したバッチを #to_a
してブロックに渡す。
User.where("age > 20").find_in_batches do |users| # バッチ前処理が書ける # 配列に対する処理が書ける(Relation オブジェクトに対する処理は書けない) users.each { |user| user.drink_alcohol! } # バッチ後処理も書ける end
#find_each
The find_each method uses find_in_batches with a batch size of 1000 (or as specified by the :batch_size option).
該当コードは ここ。 #find_in_batches
で取得したモデルの配列を #each
してブロックに渡す。
User.where("age > 20").find_each do |user| # バッチ処理の前後に処理がないなら一番スッキリ書ける user.drink_alcohol! end