#ruby-on-rails #activerecord #rails-migrations
#ruby-on-rails #activerecord #rails-миграции
Вопрос:
Запуск Rails 3 с PostgreSQL,
У меня миграция, обновляющая миллионы небольших записей.
Record.find_each do |r|
r.doing_incredibly_complex_stuff
r.save!
puts "#{r.id} updated"
end
Поскольку я думаю, что ActiveRecord переносит такие обновления в транзакцию, время «фиксации» очень велико, а занимаемая память ОГРОМНА, в то время как каждая запись была «напечатана» на экране в приведенном выше примере.
Итак, могу ли я запустить этот find_each вне транзакции — пока это вполне безопасно -, экономя много времени «фиксации» и памяти?
Что-то вроде ActiveRecord::Base.without_transaction do … ; end, я думаю 🙂
ИЛИ: я ошибаюсь, миграции не переносятся в транзакции, и время, которое я вижу, — это просто применение инструкций SQL update?
РЕДАКТИРОВАТЬ: кажется, что нет связи с транзакциями, вот трассировка стека, которую я получил, как только я прервал миграцию, когда все были напечатаны на экране, а объем оперативной памяти уменьшился с 500 МБ до ~ 30 МБ :
IRB::Abort: abort then interrupt!!
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activesupport-3.0.4/lib/active_support/whiny_nil.rb:46:in `call'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activesupport-3.0.4/lib/active_support/whiny_nil.rb:46:in `method_missing'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:978:in `flatten'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:978:in `block in select'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:977:in `map'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/connection_adapters/postgresql_adapter.rb:977:in `select'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/connection_adapters/abstract/database_statements.rb:7:in `select_all'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/connection_adapters/abstract/query_cache.rb:56:in `select_all'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/base.rb:467:in `find_by_sql'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/relation.rb:64:in `to_a'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/activerecord-3.0.4/lib/active_record/relation.rb:356:in `inspect'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/railties-3.0.4/lib/rails/commands/console.rb:44:in `start'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/railties-3.0.4/lib/rails/commands/console.rb:8:in `start'
from /Users/clement/.rvm/gems/ruby-1.9.2-p136@gemset/gems/railties-3.0.4/lib/rails/commands.rb:23:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
ПРАВКА (2): Вау. Оказалось, что это было очень долго, потому что find_each возвращает все элементы после итерации.
Я пытался :
Record.tap do |record_class|
record_class.find_each do |r|
r.doing_incredibly_complex_stuff
r.save!
puts "#{r.id} updated"
end
end
=> Record(id: integer, ...)
Таким образом, консоль была возвращена мгновенно, как и ожидалось. 🙂
Но тогда я все еще вижу странное поведение: оперативная память не освобождается. Вместо этого, как только я вышел из term, оперативная память по-прежнему загружается…
Может быть, мое решение с tap не удовлетворяет? Это все еще массовый выбор? Как бы мне избежать массового выбора после find_each?
Спасибо!
Комментарии:
1. Действительно ли ваши обновления базы данных такие простые, как
r.update_attribute(:some_column, 'some_value')
?2. Я бы обновил_all, но на самом деле это немного сложнее, поскольку у меня есть вызовы методов для каждого объекта. Я думаю, мне нужно изменить свой пример 🙂
3. взгляните на coffeepowered.net/2009/01/23/… Они предполагают, что activerecord без транзакций на самом деле занимает больше времени, чем с ними. Большой объем памяти и низкая скорость, вероятно, связаны с самим activerecord. Можете ли вы переписать миграцию с помощью raw SQL?
4. Спасибо за эту ссылку, я просматривал ее несколько часов назад — теперь мне интересно, мог бы я сделать что-нибудь попроще, используя AR в этом конкретном случае миграции и find_each.
5. Я мог бы понять, что попытка простой работы с AR не является хорошим подходом для такого рода вещей.
Ответ №1:
Ни ActiveRecord::Migration
, ни find_each
не делайте ничего, чтобы включить ваш код в транзакцию базы данных. r.save!
Будет заключен в отдельную транзакцию, которая охватывает все каскадные эффекты сохранения.
Как и в комментариях выше, использование update_all
или raw execute
будет быстрее для массового обновления. У меня нет способа определить, подходит ли это для того, что вы делаете. Кроме того, если у вас проблемы с памятью, вы должны иметь возможность настроить размер пакета find_each
и посмотреть, повлияет ли это. Если нет, возможно, вы где-то храните эти объекты.
Комментарии:
1. Что меня смущает, так это порядок, в котором все происходит: у меня есть все мои печатные результаты на экране, и после этого мой ноутбук, похоже, работает с большим трудом!
2. Это действительно странно. Если вы
Ctrl-C
сделаете это, пока он усердно работает, можете ли вы что-нибудь определить по трассировке?3. Спасибо, я сообщу об этом как можно скорее!
4. Привет, Остин, я только что опубликовал трассировку стека. Похоже, что в строке 978 адаптера AR Postgres выполняется выбор. Я не знаю, как исследовать больше!
5. Я почти уверен, что поведение
find_each
не является проблемой. Обертывание его в блок не изменит выполняемую им работу. Он возвращаетself
, который в любом случае будет классом. Однако, похоже, что он проверяет всю коллекцию. Возможно, вы могли бы опубликовать более реалистичную версию кода, который вы запускаете?
Ответ №2:
Возможно, вы могли бы структурировать метод так, чтобы добавить оператор last в качестве возвращаемого значения, а не возвращать возвращаемое значение find_each . Вы могли бы заменить последний «end» на
end ; nil