ken2merのブログ

Webエンジニア, Ruby on Rails, Go

ActiveRecord::Batches のメソッドについて

はじめに

バッチ処理などで大量のレコードに対して処理を行わなければいけない時、全てのレコードを一度に読み込んでしまうとメモリ負荷の観点から非効率的である。

そのような時に使えるのが ActiveRecord::Batches のメソッド (in_batches, find_in_batches, find_each) である。

これらのメソッドを使えば、大量レコードをいくつかの単位(デフォルトで1000件)に分割して処理を行うことができる。

in_batches

in_batches メソッドでは、分割単位ごとに絞り込まれた ActiveRecord::Relation オブジェクトをブロックの引数として処理を行う。

使い方として、おおきく3つを挙げる。

①分割したレコードごとに一括処理

User.in_batches do |users|
  users.update_all atrributes
end

②分割してレコードを読み込み、それを使い回すような処理

User.in_batches do |users|
  users = users.to_a

  OneService.call users
  AnotherService.call users
end

③分割してレコードを読み込み、その1件ずつに対する処理

User.in_batches do |users|
  users.each do |user|
    user.do_something!
  end
end

このうち②と③は、それぞれ find_in_batches, find_each で置き換えることができる。

find_in_batches

find_in_batches メソッドでは、ブロックが渡された場合、処理の中で in_batches が呼ばれるようになっている。 https://github.com/rails/rails/blob/v7.0.1/activerecord/lib/active_record/relation/batches.rb#L137-L139

      in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
        yield batch.to_a
      end

これを踏まえると②のコードは以下のように書き換えられる。

User.find_in_batches do |users|
  OneService.call users
  AnotherService.call users
end

find_each

find_each メソッドでは、ブロックが渡された場合、 処理の中で find_in_batches が呼ばれるようになっている。 https://github.com/rails/rails/blob/v7.0.1/activerecord/lib/active_record/relation/batches.rb#L70-L72

        find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
          records.each(&block)
        end

これを踏まえると③のコードは以下のように書き換えられる。

User.find_each do |user|
  user.do_something!
end

まとめ

ActiveRecord::Batches に3つののメソッドがあり、それらは find_eachfind_in_batchesin_batches のような関係性にあることがわかった。

Railsアプリケーションで大量レコードの処理を行う際はこれらのメソッドの違いを理解し、処理内容によってどのメソッドが適切か考えた上で使うようにする。

参考URL