модуль ActiveRecord::Batches
Константы
- ORDER_IGNORE_MESSAGE
Открытые методы экземпляров
# File activerecord/lib/active_record/relation/batches.rb, line 67
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
records.each { |record| yield record }
end
else
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
relation = self
apply_limits(relation, start, finish).size
end
end
end Итерация по коллекции записей из базы данных (например, с использованием метода ActiveRecord::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- Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, когда в отношении присутствует порядок.
Ограничения соблюдаются, и если они присутствуют, размер пакета не обязателен: он может быть меньше, равен или больше ограничения.
Параметры 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
ПРИМЕЧАНИЕ: Указать порядок невозможно. Он автоматически устанавливается по возрастанию первичного ключа («id ASC»), чтобы обеспечить порядок обработки пакетов. Это также означает, что этот метод работает только при наличии первичного ключа, который можно упорядочить (например, целое число или строка).
ПРИМЕЧАНИЕ: По своей природе, пакетная обработка подвержена проблемам гонок, если другие процессы изменяют базу данных.
# File activerecord/lib/active_record/relation/batches.rb, line 126
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
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) do
total = apply_limits(relation, start, finish).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) 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- Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, когда в отношении присутствует порядок.
Ограничения соблюдаются, и если они присутствуют, размер пакета не обязателен: он может быть меньше, равен или больше ограничения.
Параметры 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
ПРИМЕЧАНИЕ: Указать порядок невозможно. Он автоматически устанавливается по возрастанию первичного ключа («id ASC»), чтобы обеспечить порядок обработки пакетов. Это также означает, что этот метод работает только при наличии первичного ключа, который можно упорядочить (например, целое число или строка).
ПРИМЕЧАНИЕ: По своей природе, пакетная обработка подвержена проблемам гонок, если другие процессы изменяют базу данных.
# File activerecord/lib/active_record/relation/batches.rb, line 201
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
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).limit(batch_limit)
relation = apply_limits(relation, start, finish)
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(
bind_attribute(primary_key, primary_key_offset) { |attr, bind| attr.gt(bind) }
)
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- Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, когда в отношении присутствует порядок.
Ограничения соблюдаются, и если они присутствуют, размер пакета не обязателен: он может быть меньше, равен или больше ограничения.
Параметры 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!)
ПРИМЕЧАНИЕ: Указать порядок невозможно. Он автоматически устанавливается по возрастанию первичного ключа («id ASC»), чтобы обеспечить согласованность порядка обработки пакетов. Поэтому первичный ключ должен быть упорядочиваемым, например, целое число или строка.
ПРИМЕЧАНИЕ: По своей природе, пакетная обработка подвержена проблемам гонок, если другие процессы изменяют базу данных.
© 2004–2019 David Heinemeier Hansson
Licensed under the MIT License.