модуль ActiveRecord::Batches
Обработка записей Active Record по частям
Константы
- DEFAULT_ORDER
- СООБЩЕНИЕ_ИГНОРИРОВАНИЯ_СОРТИРОВКИ
Открытые методы экземпляров
# File activerecord/lib/active_record/relation/batches.rb, line 79
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &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, build_batch_orders(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— Переопределяет конфигурацию приложения для указания, следует ли генерировать ошибку, когда в запросе присутствует сортировка. -
: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 148
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, 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, order: order) do
total = apply_limits(relation, start, finish, build_batch_orders(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 Возвращает каждый пакет записей, найденных в соответствии с параметрами поиска, в виде массива.
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— Переопределяет конфигурацию приложения для указания, следует ли генерировать ошибку, когда в запросе присутствует сортировка. -
: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 239
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block)
unless Array(order).all? { |ord| [:asc, :desc].include?(ord) }
raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
end
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, 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,
order: order,
batch_limit: batch_limit,
&block
)
else
batch_on_unloaded_relation(
relation: self,
start: start,
finish: finish,
load: load,
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— Переопределяет конфигурацию приложения для указания, следует ли генерировать ошибку, когда в запросе присутствует сортировка. -
: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)
Пример вызова метода query на объекте отношения:
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.