Память не освобождается в работнике после завершения работы - PullRequest
0 голосов
/ 03 марта 2019

Сценарий:

У меня есть работа по запуску процесса (sidekiq) в производстве (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 не выглядит никогда не собираемым мусором, и я узнал об объектах RETAINED и о том, как работает память в рельсах.Хотя я еще не нашел способ применить это, чтобы решить мою проблему.

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

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

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

1 Ответ

0 голосов
/ 03 марта 2019

Попробуйте импортировать строки из 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(&: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 (гемы профилирования памяти), так и вне Ruby (работа и система).журналы), чтобы попытаться найти источник.

...