0
0

find_each と find_in_batches と in_batches の違い

Posted at

find_each

Railsドキュメント
github

説明

分割してレコードを取得して1件ずつ処理
デフォルトでは1000件ずつ処理 大きなデータをもつモデルなどを処理する時に使う
内部的には、find_in_batchesを使っており、戻り値の配列に対してeachを実行することで1レコードずつ返している
そのため、使い方はeachと全く同じ

ソースコード

    def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc, &block)
      if block_given?
        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
      else
        enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
          relation = self
          apply_limits(relation, start, finish, order).size
        end
      end
    end

find_in_batches

Railsドキュメント
github

説明

分割してレコードを取得して処理
デフォルトで1000件ずつ処理
配列として返ってくる
内部的には、in_batchesを使っており、戻り値を配列に変換したものを呼び出し側のブロック引数として渡している

ソースコード

    def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
      relation = self
      unless block_given?
        return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
          total = apply_limits(relation, start, finish, order).size
          (total - 1).div(batch_size) + 1
        end
      end

      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
    end

in_batches

Railsドキュメント
github

説明

分割してレコードを取得して処理
デフォルトで1000件ずつ処理
呼び出し側のブロック引数として、バッチサイズ単位のActiveRecord::Relationオブジェクトを返す

ソースコード

    def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
      relation = self
      unless block_given?
        return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
      end

      unless [:asc, :desc].include?(order)
        raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
      end

      if arel.orders.present?
        act_on_ignored_order(error_on_ignore)
      end

      batch_limit = of
      if limit_value
        remaining   = limit_value
        batch_limit = remaining if remaining < batch_limit
      end

      relation = relation.reorder(batch_order(order)).limit(batch_limit)
      relation = apply_limits(relation, start, finish, order)
      relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
      batch_relation = relation

      loop do
        if load
          records = batch_relation.records
          ids = records.map(&:id)
          yielded_relation = where(primary_key => ids)
          yielded_relation.load_records(records)
        else
          ids = batch_relation.pluck(primary_key)
          yielded_relation = where(primary_key => ids)
        end

        break if ids.empty?

        primary_key_offset = ids.last
        raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset

        yield yielded_relation

        break if ids.length < batch_limit

        if limit_value
          remaining -= ids.length

          if remaining == 0
            # Saves a useless iteration when the limit is a multiple of the
            # batch size.
            break
          elsif remaining < batch_limit
            relation = relation.limit(remaining)
          end
        end

        batch_relation = relation.where(
          predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
        )
      end
    end

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0