module ActiveRecord::Batches
Пакетная обработка Active Record
Константы
- DEFAULT_ORDER
- ORDER_IGNORE_MESSAGE
Открытые методы экземпляра
# 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»). Это также означает, что метод работает только в том случае, если столбец курсора поддерживает сортировку (например, является целым числом или строкой).
ПРИМЕЧАНИЕ. При использовании пользовательских столбцов для пакетной обработки среди них должен быть хотя бы один уникальный столбец (например, первичный ключ), который будет использоваться для разрешения совпадений. Кроме того, чтобы снизить вероятность гонок, все столбцы должны быть статическими (неизменяемыми после установки).
ПРИМЕЧАНИЕ. По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.
# 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»). Это также означает, что метод работает только в том случае, если столбец курсора поддерживает сортировку (например, является целым числом или строкой).
ПРИМЕЧАНИЕ. При использовании пользовательских столбцов для пакетной обработки среди них должен быть хотя бы один уникальный столбец (например, первичный ключ), который будет использоваться для разрешения совпадений. Кроме того, чтобы снизить вероятность гонок, все столбцы должны быть статическими (неизменяемыми после установки).
ПРИМЕЧАНИЕ. По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.
# 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.