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

Как бороться с утечками памяти Ruby 2.1.2?

У меня есть рабочий процесс, который порождает до 50 потоков и выполняет некоторые асинхронные операции (большинство из которых - HTTP-вызовы). Когда я запускаю процесс, он начинается с примерно 35 МБ используемой памяти и быстро растет до 250 МБ. С этого момента он растет еще больше, и проблема в том, что память никогда не перестает расти (хотя нарастающая фаза уменьшается со временем). Через несколько дней процесс просто перерастает доступную память и сбой.

Я провел много анализа и профилирования и не могу найти, что не так. Процессная память постоянно растет, хотя размер кучи почти постоянный. Я собрал вывод GC.stat в электронную таблицу, которую вы можете получить здесь:

https://docs.google.com/spreadsheets/d/17TohDNXQ_MXM31CeAmR2ptHFYfvOeF3dB6WCBkBS_Bc/edit?usp=sharing

Хотя кажется, что память процесса окончательно стабилизировалась на уровне 415 МБ, она будет продолжать расти в течение следующих нескольких дней, пока не достигнет предела 512 МБ и не сработает.

Я также пробовал отслеживать объекты с помощью objectpace, но сумма памяти отслеживаемых объектов никогда не пересекается с 70-80 МБ, что идеально согласуется с отчетами GC. Где остальные 300MB + (и растущие) потрачены.. я понятия не имею.

Как бороться с этими проблемами? Есть ли какие-либо инструменты, которые могли бы дать мне более четкое представление о том, как используется память?

ОБНОВЛЕНИЕ: Драгоценные камни и ОС

Я использую следующие камни:

gem "require_all", "~> 1.3"
gem "thread", "~> 0.1"
gem "equalizer", "~> 0.0.9"
gem "digest-murmurhash", "~> 0.3", require: "digest/murmurhash"
gem "google-api-client", "~> 0.7", require: "google/api_client"
gem "aws-sdk", "~> 1.44"

Приложение развернуто на герою, хотя утечка памяти заметна при ее локальном использовании в Mac OS X 10.9.4.

ОБНОВЛЕНИЕ: Утечки

Я обновил stringbuffer и проанализировал все, что предложил @mtm, и теперь нет утечек памяти, идентифицированных с помощью инструмента leak, без увеличения размера кучи ruby ​​со временем, и тем не менее, память процесса все еще растет. Первоначально я думал, что в какой-то момент она перестала расти, но через несколько часов она переросла предел и процесс разбился.

4b9b3361

Ответ 1

Из ваших журналов GC кажется, что проблема не в утечке ссылки на объект ruby, поскольку значение heap_live_slot существенно не увеличивается. Это предполагает, что проблема заключается в одном из:

  1. Данные хранятся вне кучи (строки, массивы и т.д.)
  2. Утечка в геме, который использует нативный код
  3. Утечка в самом интерпретаторе Ruby (наименее вероятно)

Интересно отметить, что проблема проявляется как в OSX, так и в Heroku (Ubuntu Linux).

Данные объекта и "куча"

Сборка мусора в Ruby 2.1 использует указанную "кучу" только для объектов, которые содержат небольшое количество данных. Когда данные, содержащиеся в объекте, превышают определенный предел, данные перемещаются и выделяются в область за пределами кучи. Вы можете получить общий размер каждого типа данных с помощью ObjectSpace:

require 'objspace'
ObjectSpace.count_objects_size({})

Сбор этого вместе с вашей статистикой GC может указывать, где память выделяется за пределами кучи. Если вы найдете определенный тип, скажите :T_ARRAY увеличивается намного больше, чем другие, которые вам могут понадобиться для поиска массива, к :T_ARRAY навсегда.

Вы можете использовать pry-byebug чтобы зайти в консоль, чтобы перемещаться вокруг определенных объектов или даже смотреть на все объекты из корня:

ObjectSpace.memsize_of(some_object)
ObjectSpace.reachable_objects_from_root

Немного подробнее о блоге разработчиков ruby, а также об этом ответе SO. Мне нравится их идея профилирования JRuby/VisualVM.

Тестирование локальных драгоценных камней

Используйте bundle для установки ваших драгоценных камней в локальный путь:

bundle install --path=.gems/

Затем вы можете найти те, которые включают нативный код:

find .gems/ -name "*.c"

Что дает вам: (в моем порядке подозрительности)

  • дайджест-StringBuffer-0.0.2
  • дайджест-murmurhash-0.3.0
  • nokogiri-1.6.3.1
  • JSON-1.8.1

OSX имеет полезный инструмент разработки, называемый leaks который может сказать вам, если он обнаруживает несвязанную память в работающем процессе. Не очень полезно для определения, откуда берется память в Ruby, но поможет определить, когда это происходит.

Сначала нужно проверить digest-stringbuffer. Возьмите пример из файла Readme и добавьте в GC логирование с помощью gc_tracer.

require "digest/stringbuffer"
require "gc_tracer"
GC::Tracer.start_logging "gclog.txt"
module Digest
  class Prime31 < StringBuffer
    def initialize
      @prime = 31
    end

    def finish
      result = 0
      buffer.unpack("C*").each do |c|
        result += (c * @prime)
      end
      [result & 0xffffffff].pack("N")
    end
  end
end

И заставить его работать много

while true do
  a=[]
  500.times do |i|
    a.push Digest::Prime31.hexdigest( "abc" * (1000 + i) )
  end
  sleep 1
end

Запустите пример:

bundle exec ruby ./stringbuffertest.rb &
pid=$!

Отслеживайте объем резидентной и виртуальной памяти процесса ruby, а также количество обнаруженных leaks:

while true; do
  ps=$(ps -o rss,vsz -p $pid | tail +2)
  leaks=$(leaks $pid | grep -c Leak)
  echo "$(date) m[$ps] l[$leaks]"
  sleep 15
done

И похоже, что мы уже нашли что-то:

Tue 26 Aug 2014 18:22:36 BST m[104776  2538288] l[8229]
Tue 26 Aug 2014 18:22:51 BST m[110524  2547504] l[13657]
Tue 26 Aug 2014 18:23:07 BST m[113716  2547504] l[19656]
Tue 26 Aug 2014 18:23:22 BST m[113924  2547504] l[25454]
Tue 26 Aug 2014 18:23:38 BST m[113988  2547504] l[30722]

Резидентная память увеличивается, и инструмент утечек находит все больше и больше ссылочной памяти. Подтвердите размер кучи GC, и количество объектов выглядит стабильным

tail -f gclog.txt | awk '{ print $1, $3, $4, $7, $13 }
1581853040832 468 183 39171 3247996
1581859846164 468 183 33190 3247996
1584677954974 469 183 39088 3254580
1584678531598 469 183 39088 3254580
1584687986226 469 183 33824 3254580
1587512759786 470 183 39643 3261058
1587513449256 470 183 39643 3261058
1587521726010 470 183 34470 3261058

Тогда сообщите о проблеме.

Моему неопытному глазу C кажется, что они выделяют и указатель, и буфер, но только очищают буфер.

Глядя на digest-murmurhash, кажется, что он предоставляет только функции, которые полагаются на StringBuffer, поэтому утечка может быть в порядке после исправления stringbuffer.

Когда они исправят это, протестируйте снова и переходите к следующему драгоценному камню. Вероятно, лучше использовать фрагменты кода из вашей реализации для каждого теста gem, а не общий пример.

Тестирование МРТ

Первым шагом было бы доказать проблему на нескольких машинах под одной и той же МРТ, чтобы исключить что-либо локальное, что вы уже сделали.

Затем попробуйте ту же версию Ruby на другой ОС, что вы тоже сделали.

Попробуйте код на JRuby или Rubinius, если это возможно. Возникает ли такая же проблема?

Попробуйте использовать тот же код на 2.0 или 1.9, если это возможно, посмотрите, существует ли та же проблема.

Попробуйте версию для разработчиков из github и посмотрите, имеет ли это какое-то значение.

Если ничего не становится очевидным, отправьте сообщение об ошибке в Ruby, подробно описав проблему и все, что вы устранили. Подождите, пока разработчик выручит и предоставит все, что им нужно. Скорее всего, они захотят воспроизвести проблему, поэтому, если вы сможете получить самый краткий/минимальный пример решения проблемы. Это часто поможет вам определить причину проблемы.

Ответ 3

Я являюсь автором камней дайджест /murmurhash и digest/stringbuffer.

В самом деле, похоже, что есть утечки в дайджесте /stringbuffer.

Я исправлю это позже.

Можете ли вы объяснить больше кода?

Я рекомендую использовать такие методы, как это.

Digest::MurmurHash1.hexdigest(some_data)

Может быть, это не утечка, поскольку методы singleton не используют digest/stringbuffer.