Spec-Zone.ru › Ruby on Rails 8.1

module ActiveRecord::Batches

Пакетная обработка Active Record

Константы

DEFAULT_ORDER
ORDER_IGNORE_MESSAGE

Открытые методы экземпляра

find_each (start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block) Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 85
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block)
  if block_given?
    find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, 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, cursor: cursor, order: order) do
      relation = self
      cursor = Array(cursor)
      apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
    end
  end
end

Перебор коллекции записей из базы данных (например, с помощью метода Scoping::Named::ClassMethods.all) крайне неэффективен, поскольку при этом предпринимается попытка создать все объекты одновременно.

В таких случаях методы пакетной обработки позволяют работать с записями пакетами, значительно сокращая потребление памяти.

Метод find_each использует find_in_batches с размером пакета 1000 (или с размером, указанным параметром :batch_size).

Person.find_each do |person|
  person.do_awesome_stuff
end

Person.where("age > 21").find_each do |person|
  person.party_all_night!
end

Если не передать блочный параметр методу find_each, он вернёт Enumerator, который можно объединять с другими методами:

Person.find_each.with_index do |person, index|
  person.award_trophy(index + 1)
end

Параметры

  • :batch_size — задаёт размер пакета. Значение по умолчанию — 1000.

  • :start — задаёт начальное значение столбца курсора, включая указанное значение.

  • :finish — задаёт конечное значение столбца курсора, включая указанное значение.

  • :error_on_ignore — переопределяет конфигурацию приложения и задаёт, следует ли выдавать ошибку, если в отношении задан порядок сортировки.

  • :cursor — задаёт столбец, используемый для пакетной обработки (это может быть имя столбца или массив имён столбцов). По умолчанию используется первичный ключ.

  • :order — задаёт порядок сортировки столбца курсора (может принимать значение :asc или :desc, либо массив, состоящий из :asc или :desc). По умолчанию используется :asc.

    class Order < ActiveRecord::Base
      self.primary_key = [:id_1, :id_2]
    end
    
    Order.find_each(order: [:asc, :desc])
    

    В приведённом выше коде id_1 сортируется по возрастанию, а id_2 — по убыванию.

Ограничения соблюдаются. Если ограничение задано, размер пакета не имеет значения: он может быть меньше, равен ограничению или превышать его.

Параметры start и finish особенно полезны, если с одной очередью обработки работают несколько исполнителей. Например, можно поручить исполнителю 1 обработку всех записей с идентификаторами от 1 до 9999, а исполнителю 2 — записей с идентификатором от 10000 и выше, задав для каждого исполнителя параметры :start и :finish.

# In worker 1, let's process until 9999 records.
Person.find_each(finish: 9_999) do |person|
  person.party_all_night!
end

# In worker 2, let's process from record 10_000 and onwards.
Person.find_each(start: 10_000) do |person|
  person.party_all_night!
end

ПРИМЕЧАНИЕ. Порядок сортировки может быть по возрастанию (:asc) или по убыванию (:desc). Для первичного ключа автоматически задаётся сортировка по возрастанию («id ASC»). Это также означает, что метод работает только в том случае, если столбец курсора поддерживает сортировку (например, является целым числом или строкой).

ПРИМЕЧАНИЕ. При использовании пользовательских столбцов для пакетной обработки среди них должен быть хотя бы один уникальный столбец (например, первичный ключ), который будет использоваться для разрешения совпадений. Кроме того, чтобы снизить вероятность гонок, все столбцы должны быть статическими (неизменяемыми после установки).

ПРИМЕЧАНИЕ. По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.

find_in_batches (start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER) { |to_a| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 161
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER)
  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, cursor: cursor, order: order) do
      cursor = Array(cursor)
      total = apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, 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, cursor: cursor, order: order) do |batch|
    yield batch.to_a
  end
end

Передаёт в блок каждый найденный пакет записей в виде массива.

Person.where("age > 21").find_in_batches do |group|
  sleep(50) # Make sure it doesn't get too crowded in there!
  group.each { |person| person.party_all_night! }
end

Если не передать блочный параметр методу find_in_batches, он вернёт Enumerator, который можно объединять с другими методами:

Person.find_in_batches.with_index do |group, batch|
  puts "Processing group ##{batch}"
  group.each(&:recover_from_last_night!)
end

Чтобы получать записи по одной, используйте вместо этого find_each.

Параметры

  • :batch_size — задаёт размер пакета. Значение по умолчанию — 1000.

  • :start — задаёт начальное значение столбца курсора, включая указанное значение.

  • :finish — задаёт конечное значение столбца курсора, включая указанное значение.

  • :error_on_ignore — переопределяет конфигурацию приложения и задаёт, следует ли выдавать ошибку, если в отношении задан порядок сортировки.

  • :cursor — задаёт столбец, используемый для пакетной обработки (это может быть имя столбца или массив имён столбцов). По умолчанию используется первичный ключ.

  • :order — задаёт порядок сортировки столбца курсора (может принимать значение :asc или :desc, либо массив, состоящий из :asc или :desc). По умолчанию используется :asc.

    class Order < ActiveRecord::Base
      self.primary_key = [:id_1, :id_2]
    end
    
    Order.find_in_batches(order: [:asc, :desc])
    

    В приведённом выше коде id_1 сортируется по возрастанию, а id_2 — по убыванию.

Ограничения соблюдаются. Если ограничение задано, размер пакета не имеет значения: он может быть меньше, равен ограничению или превышать его.

Параметры start и finish особенно полезны, если с одной очередью обработки работают несколько исполнителей. Например, можно поручить исполнителю 1 обработку всех записей с идентификаторами от 1 до 9999, а исполнителю 2 — записей с идентификатором от 10000 и выше, задав для каждого исполнителя параметры :start и :finish.

# Let's process from record 10_000 on.
Person.find_in_batches(start: 10_000) do |group|
  group.each { |person| person.party_all_night! }
end

ПРИМЕЧАНИЕ. Порядок сортировки может быть по возрастанию (:asc) или по убыванию (:desc). Для первичного ключа автоматически задаётся сортировка по возрастанию («id ASC»). Это также означает, что метод работает только в том случае, если столбец курсора поддерживает сортировку (например, является целым числом или строкой).

ПРИМЕЧАНИЕ. При использовании пользовательских столбцов для пакетной обработки среди них должен быть хотя бы один уникальный столбец (например, первичный ключ), который будет использоваться для разрешения совпадений. Кроме того, чтобы снизить вероятность гонок, все столбцы должны быть статическими (неизменяемыми после установки).

ПРИМЕЧАНИЕ. По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.

in_batches (of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block) Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 259
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block)
  cursor = Array(cursor).map(&:to_s)
  ensure_valid_options_for_batching!(cursor, start, finish, order)

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

  unless block
    return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges)
  end

  batch_limit = of

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

  if self.loaded?
    batch_on_loaded_relation(
      relation: self,
      start: start,
      finish: finish,
      cursor: cursor,
      order: order,
      batch_limit: batch_limit,
      &block
    )
  else
    batch_on_unloaded_relation(
      relation: self,
      start: start,
      finish: finish,
      load: load,
      cursor: cursor,
      order: order,
      use_ranges: use_ranges,
      remaining: remaining,
      batch_limit: batch_limit,
      &block
    )
  end
end

Передаёт объекты ActiveRecord::Relation, позволяющие работать с пакетом записей.

Person.where("age > 21").in_batches do |relation|
  relation.delete_all
  sleep(10) # Throttle the delete queries
end

Если не передать блочный параметр методу in_batches, он вернёт перечисляемый объект BatchEnumerator.

Person.in_batches.each_with_index do |relation, batch_index|
  puts "Processing relation ##{batch_index}"
  relation.delete_all
end

Примеры вызова методов для возвращённого объекта BatchEnumerator:

Person.in_batches.delete_all
Person.in_batches.update_all(awesome: true)
Person.in_batches.each_record(&:party_all_night!)

Параметры

  • :of — задаёт размер пакета. Значение по умолчанию — 1000.

  • :load — задаёт, следует ли загружать отношение. Значение по умолчанию — false.

  • :start — задаёт начальное значение столбца курсора, включая указанное значение.

  • :finish — задаёт конечное значение столбца курсора, включая указанное значение.

  • :error_on_ignore — переопределяет конфигурацию приложения и задаёт, следует ли выдавать ошибку, если в отношении задан порядок сортировки.

  • :cursor — задаёт столбец, используемый для пакетной обработки (это может быть имя столбца или массив имён столбцов). По умолчанию используется первичный ключ.

  • :order — задаёт порядок сортировки столбца курсора (может принимать значение :asc или :desc, либо массив, состоящий из :asc или :desc). По умолчанию используется :asc.

    class Order < ActiveRecord::Base
      self.primary_key = [:id_1, :id_2]
    end
    
    Order.in_batches(order: [:asc, :desc])
    

    В приведённом выше коде id_1 сортируется по возрастанию, а id_2 — по убыванию.

  • :use_ranges — задаёт, следует ли использовать итерацию по диапазонам (id >= x AND id <= y). Это может в несколько раз ускорить перебор всей таблицы или почти всех её записей. По умолчанию такой способ итерации используется только при переборе всей таблицы. Это поведение можно отключить, передав false. Если вы перебираете таблицу, и единственное условие — например, archived_at: nil (а архивирована лишь небольшая часть записей), имеет смысл включить этот способ.

Ограничения соблюдаются. Если ограничение задано, размер пакета не имеет значения: он может быть меньше ограничения, равен ему или превышать его.

Параметры start и finish особенно полезны, если с одной очередью обработки работают несколько исполнителей. Например, можно поручить исполнителю 1 обработку всех записей с идентификаторами от 1 до 9999, а исполнителю 2 — записей с идентификатором от 10000 и выше, задав для каждого исполнителя параметры :start и :finish.

# Let's process from record 10_000 on.
Person.in_batches(start: 10_000).update_all(awesome: true)

Пример вызова метода where для запроса к отношению:

Person.in_batches.each do |relation|
  relation.update_all('age = age + 1')
  relation.where('age > 21').update_all(should_party: true)
  relation.where('age <= 21').delete_all
end

ПРИМЕЧАНИЕ. Если вы собираетесь перебирать записи по одной, вызовите each_record для возвращённого BatchEnumerator:

Person.in_batches.each_record(&:party_all_night!)

ПРИМЕЧАНИЕ. Порядок сортировки может быть по возрастанию (:asc) или по убыванию (:desc). Для первичного ключа автоматически задаётся сортировка по возрастанию («id ASC»). Это также означает, что метод работает только в том случае, если столбец курсора поддерживает сортировку (например, является целым числом или строкой).

ПРИМЕЧАНИЕ. При использовании пользовательских столбцов для пакетной обработки среди них должен быть хотя бы один уникальный столбец (например, первичный ключ), который будет использоваться для разрешения совпадений. Кроме того, чтобы снизить вероятность гонок, все столбцы должны быть статическими (неизменяемыми после установки).

ПРИМЕЧАНИЕ. По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API