ActiveRecord не освобождает память - PullRequest
2 голосов
/ 30 мая 2020

У меня есть задание на экспорт, которое экспортирует большой объем данных из нашей MySQL БД. По мере роста данных я заметил, что для этого задания sidekiq требуется слишком много памяти. На сервере 32 ГБ, а после экспорта требуется 28 ГБ. Когда я останавливаю процесс sidekiq, использование памяти падает до 8 ГБ.

Я уже следовал руководству здесь https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting

  • предотвращение фрагментации памяти с помощью MALLOC_ARENA_MAX=2
  • очистить кеш запросов ActiveRecord::Base.connection.clear_query_cache

Я нахожусь на Ruby 2.6.5p114 и попытался изолировать проблему, создав новое приложение rails в производстве и используя мою БД в качестве бэкэнда:

gem install rails --version 5.2.4.3
rails new debug -d mysql

Я создал пустую модель, чтобы избежать пользовательских методов в моем коде, которые могут вызвать проблему:

class Variant < ApplicationRecord
end

Этот сценарий просто загружает 1 миллион объектов из БД и печатает использование памяти:

# memory.rb

def memory
  (`ps -o rss= -p #{Process.pid}`.to_i.to_f / 1024).to_s + " MB"
end

def load_variants
  puts "load_variants..."
  Variant.uncached do
    variants = Variant.limit(1_000_000).to_a
    puts "variant.count: #{variants.count}"
  end
end

puts memory
load_variants
puts memory

puts "GC.start..."
GC.start
puts memory

# second run
load_variants
puts memory

puts "GC.start..."
GC.start
puts memory

Это результат:

root@6e79d7a97d9c:/usr/src/debug# rails r memory.rb
76.93359375 MB
load_variants...
variant.count: 1000000
2436.3125 MB
GC.start...
2421.046875 MB
load_variants...
variant.count: 1000000
2436.3828125 MB
GC.start...
2436.3984375 MB
  1. он начинается с 76.93359375 MB
  2. после загрузки 1 млн объектов память увеличивается до 2436.3125 MB
  3. сборка мусора уменьшает память до 2421.046875 MB, , но я ожидал бы значительно большего падения!
  4. интересно , второй прогон только увеличивает память до 2436.3828125 MB
  5. последний GC.start как-то увеличивает памяти немного до 2436.3984375 MB

Итак, я хотел бы знать, как это могло быть? В ActiveRecord должно быть что-то, о чем я не знаю, и я хотел бы понять, как все это работает и почему память не освобождается.

После этого logi c объем памяти должен увеличиться на каждый запрос, который читает данные, но я предполагаю, что есть что-то другое при использовании в цикле запрос-ответ.

1 Ответ

3 голосов
/ 30 мая 2020

Загрузка больших объектов, которые распределены по всей памяти (вместо объектов типа String, которые находятся в последовательной памяти) в Ruby, как правило, имеет такой эффект, поскольку алгоритм Mark & ​​Sweep не может возвращать целые блоки память обратно в ОС. Вы получите аналогичный эффект, если начнете анализировать большие JSON файлы (например, 10 + МБ), поскольку полученный Ha sh, состоящий из множества других объектов, будет помещен в несколько блоков памяти вместе с другими объектами, которые все еще имеет активную ссылку и, следовательно, Ruby не может освободить этот блок.

...