модуль 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, он вернет перечислитель для цепочки с другими методами:
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 обрабатывал записи с id от 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, он вернет перечислитель для цепочки с другими методами:
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 обрабатывал записи с id от 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 И id <= y). Это может сделать итерацию по всей или почти всей таблице значительно быстрее. По умолчанию этот стиль итерации используется только для итерации по всей таблице. Можно отключить это поведение, передавfalse. Если вы итерируете по таблице, и единственное условие, например,archived_at: nil(и только небольшая часть записей архивирована), имеет смысл выбрать этот подход.
Ограничения учитываются, и если они присутствуют, размер пакета не обязателен: он может быть меньше, равен или больше ограничения.
Параметры start и finish особенно полезны, если несколько рабочих процессов обрабатывают одну и ту же очередь. Можно настроить, чтобы рабочий процесс 1 обрабатывал записи с id от 1 до 9999, а рабочий процесс 2 — с 10000 и далее, установив параметр :start и :finish на каждом рабочем процессе.
# Let's process from record 10_000 on. Person.in_batches(start: 10_000).update_all(awesome: true)
Пример вызова метода запроса на отношении:
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.