Утечка памяти Ruby on Rails при прохождении через большое количество записей; find_each не помогает
У меня есть приложение Rails, которое обрабатывает большое (миллионное) количество записей в базе данных mysql. Как только он начинает работать, его использование памяти быстро растет со скоростью 50 МБ в секунду. С помощью инструментов, таких как oink, я смог сузить корневую причину до одного цикла, который проходит через все записи в большой таблице в базе данных.
Я понимаю, что если я использую что-то вроде Person.all.each, все записи будут загружены в память. Однако, если я переключусь на find_each, я все еще вижу ту же проблему с памятью. Чтобы дополнительно изолировать проблему, я создал следующий тестовый контроллер, который ничего не делает, кроме прокрутки записей. Я полагаю, что find_each сохраняет только небольшое количество объектов в памяти каждый раз, но использование памяти растет линейно по мере ее выполнения.
class TestController < ApplicationController
def memory_test
Person.find_each do |person|
end
end
Я подозреваю, что это связано с кэшированием результатов запроса ActiveRecord. Но я проверил настройки своей среды, и у меня есть все связанные с кешированием параметры, установленные как false в разработке (я использую настройки по умолчанию, созданные рельсами). Я сделал поиск в Интернете, но не смог найти решение.
Я использую rails 3.1.0 rc1 и ruby 1.9.2
Спасибо!
Ответы
Ответ 1
Я смог понять это сам. Есть два места для изменения.
Сначала отключите IdentityMap. В config/application.rb
config.active_record.identity_map = false
Во-вторых, используйте uncached для завершения цикла
class MemoryTestController < ApplicationController
def go
ActiveRecord::Base.uncached do
Person.find_each do |person|
# whatever operation
end
end
end
end
Теперь мое использование памяти находится под контролем. Надеюсь, это поможет другим людям.
Ответ 2
Как хорошо, как ActiveRecord, это не лучший инструмент для всех проблем. Я рекомендую отказаться от своего адаптера базы данных и выполнить работу на этом уровне.
Ответ 3
find_each
вызывает find_in_batches
с размером партии 1000 под капотом.
Все записи в пакете будут созданы и сохранены в памяти до тех пор, пока пакет обрабатывается.
Если ваши записи большие или если они потребляют много памяти через коллекции прокси (например, has_many кэширует все свои элементы в любое время, когда вы его используете), вы также можете попробовать меньший размер партии:
Person.find_each batch_size: 100 do |person|
# whatever operation
end
Вы также можете периодически запускать вызов GC.start
вручную (например, каждые 300 элементов)