Подтвердить что ты не робот

Утечка памяти 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

Спасибо!

4b9b3361

Ответ 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 элементов)