Память не освобождается в worker после завершения задания

#ruby-on-rails #ruby #memory-management #sidekiq #activerecord-import

#ruby-on-rails #ruby #управление памятью #sidekiq #activerecord-импорт

Вопрос:

Сценарий:

У меня есть задание, выполняющее процесс (sidekiq) в production (heroku). Процесс импортирует данные (CSV) из S3 в модель БД с помощью activerecord-import gem. Этот драгоценный камень помогает выполнять массовую вставку данных. Таким образом, dbRows переменная устанавливает значительный объем памяти для всех ActiveRecord объектов, сохраненных при повторении строк CSV (все хорошо). После импорта данных (в: db_model.import dbRows ) dbRows данные очищаются (должны быть!) и обрабатывается следующий объект.

Например: (сценарий упрощен для лучшего понимания)

 def import
      ....
      s3_objects.contents.each do |obj|
          @cli.get_object({..., key: obj.key}, target: file) 
          dbRows = []
          csv = CSV.new(file, headers: false)
          while line = csv.shift
              # >> here dbRows grows and grows and never is freed!
              dbRows << db_model.new(
                field1: field1,
                field2: field2,
                fieldN: fieldN
              )
          end
          db_model.import dbRows
          dbRows = nil   # try 1 to freed array
          GC.start   # try 2 to freed memory
      end
      ....
end
  

Проблема:

Объем памяти задания увеличивается во время выполнения процесса, но после выполнения задания объем памяти не уменьшается. Это остается навсегда!

При отладке я обнаружил, что dbRows похоже, что мусор никогда не собирается, и я узнал о СОХРАНЕННЫХ объектах в rails и о том, как работает память в rails. Хотя я еще не нашел способа применить это для решения моей проблемы.

Я хотел бы, чтобы после завершения задания все ссылки, установленные в dbRows, были GC, а рабочая память освобождалась.

приветствуется любая помощь.

ОБНОВЛЕНИЕ: я читал о weakref , но я не знаю, будет ли это полезно. есть какие-нибудь идеи?

Комментарии:

1.Пожалуйста, загуглите раздувание памяти ruby; на эту тему есть десятки сообщений в блогах. mikeperham.com/2018/04/25/taming-rails-memory-bloat speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html это два.

2. Привет @MikePerham, я установил heroku-buildpack-jemalloc , но не изменил обработку памяти. в любом случае спасибо.

3. heroku config: установите MALLOC_ARENA_MAX=2, это уменьшит объем памяти.

4. если ничего не работает, попробуйте github.com/resque/resque . его реализация устраняет эту известную проблему sidekiq. возможно, это будет соответствовать вашим требованиям.

Ответ №1:

Попробуйте импортировать строки из CSV пакетами, например, импортировать строки в DB по 1000 строк за раз, чтобы вы не сохраняли предыдущие строки, и GC мог их собирать. В любом случае это полезно для базы данных (и для загрузки с s3, если вы передаете CSV объект ввода-вывода из S3.

 s3_io_object = s3_client.get_object(*s3_obj_params).body
csv = CSV.new(s3_io_object, headers: true, header_converters: :symbol)
csv.each_slice(1_000) do |row_batch|
  db_model.import ALLOWED_FIELDS, row_batch.map(amp;:to_h), validate: false
end
  

Обратите внимание, что я также не создаю экземпляры AR-моделей для экономии памяти, а только передаю хэши и сообщаю activerecord-import validate: false .

Кроме того, откуда берется file ссылка? Кажется, что он долговечен.

Из вашего примера это не очевидно, но возможно ли, чтобы ссылки на объекты все еще хранились глобально библиотекой или расширением в вашей среде?

Иногда эти вещи очень трудно отследить, поскольку любой вызываемый код из любого места (включая код внешней библиотеки) может выполнять что-то вроде:

Динамически определяющие константы, поскольку они никогда не получают GC’d

 Any::Module::Or:Class.const_set('NewConstantName', :foo)
  

или добавление данных ко всему, на что ссылается / принадлежит константе

 SomeConstant::Referenceable::Globally.array << foo # array will only get bigger and contents will never be GC'd
  

В противном случае лучшее, что вы можете сделать, это использовать некоторые инструменты профилирования памяти, либо внутри Ruby (профилирование памяти gems), либо вне Ruby (журналы заданий и системные журналы), чтобы попытаться найти источник.

Комментарии:

1. ПРИВЕТ @Kache, спасибо за ваш ответ. На самом деле я точно знаю строку, которая увеличивает память: когда dbRows << db_model.new(...) . Читаем здесь: sitepoint.com/ruby-uses-memory Я думаю, что это связано с тем, как обрабатываются сохраненные объекты в rails. Но я не знаю, как это обойти, поскольку мне нужны dbRows для объемной обработки данных в более позднем состоянии: в db_model.import dbRows

2. Однако в вашем примере вы не используете константу, так что это на нее не похоже. Как насчет того, чтобы попробовать импортировать пакетами, чтобы посмотреть, поможет ли это? Я добавлю к своему ответу.