Страницы

Поиск по вопросам

воскресенье, 8 марта 2020 г.

rails puma постоянный рост используемой памяти

#ruby_on_rails #веб_сервер


Имеется небольшое приложение на rails(4.2.6), в качестве веб-сервера используется
nginx+puma(3.4.0). Сервер Debian 8.1 64bit. 
Заметил одну странную вещь, с каждым запросом, используемое процессами puma количество
памяти растет, и никогда не снижается(во время отсутствия каких либо запросов). Это
приводит к тому, что  ОЗУ на сервере (512 мб) заканчивается, и останавливается служба
postgres(9.4). Пробовал разное количество воркеров, нитей, работа в кластерном режиме
и в обычном - результат всегда один и тот же. После нескольких тяжелых запросов ОЗУ
заканчивается.
Неужели так и должно быть? Какие есть пути устранения такой проблемы? По информации,
которую я нашел, Puma - это один из лучших выборов для избежания проблемы медленных
клиентов и долгих запросов.
Текущий конфиг такой:

#!/usr/bin/env puma

...

threads 2,4

...
workers 1

preload_app!

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "/home/.../current/Gemfile"
end

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end

    


Ответы

Ответ 1



Описанное в ответе реально не совсем правда. GC может освобождать память, но темпы освобождения могут быть меньше темпов роста. Вне синтетических тестов обычно так и есть. Ответ подлежит переработке с учётом этого факта. Да, это из-за особенностей работы GC в самом Ruby. Он держит собственный пул памяти, увеличивая его при необходимости, но никогда не уменьшая. И тому есть причина. Объекты, "подметённые" сборщиком мусора, освобождают память с точки зрения интерпретатора, но не с точки зрения ОС. Посему, только тот факт, что память занята процессом, не означает, что в ней действительно есть что-то ценное для программы, скорее всего, это просто "запас", в котором интерпретатор будет размещать новые объекты сам, не дёргая аллокатор ОС. Соответственно, в каждый момент времени процесс будет занимать максимум того, что ему было нужно за всё время его жизни. Размер пула будет с небольшим запасом равен пиковому потреблению памяти. Беда с большими объектами в том, что они требуют большие последовательные области памяти. И если такой блок в пуле не находится, то... пул ещё увеличивается на такую величину, чтобы большой объект влез! Эту проблему можно было б частично победить, используя сборщик мусора с "уплотнением" (compaction), когда GC в процессе работы "перекладывает" мелкие объекты поближе друг к другу, тем самым образуя более крупные последовательные области свободной памяти. Это же могло бы позволить отдавать крупные "хвосты" незанятой памяти обратно в ОС. Но это здорово усложняет работу С-шных расширений, которые запоминают, где был объект, непосредственно по адресу в памяти. Нельзя просто сказать им "я вон тот объект подвинул, имей в виду". Сделать можно много чего. Пол-гигабайта на целое рельсовое приложение и хранилища данных это... крайне немного. Есть смысл добавить, если не физической ОЗУ, то хотя бы подкачки. Подкрутить garbage collector на более осторожное расширение пула и частые срабатывания. Это вряд ли поможет, но может немножко отсрочить неизбежное. Это довольно обширная и опасная тема, требующая тщательного стресс-тестирования на каждое изменение. (Самый богатый на приключения) Реально снизить пиковое потребление памяти, отдавая большие ответы по кусочкам, чтобы GC успевал подчищать то, что уже отослано клиенту. Есть ActionController::Live, с помощью которого можно писать ответ в поток по кусочкам, не загружая все исходные данные для него в память.

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

Отправить комментарий