Unicorn Memory Usage заполняет почти всю оперативную память

Снимок процесса New Relic

Тут по сути 3 проблемы:

1) Похоже, что Unicorn постоянно заполняет всю оперативную память, из-за чего я вручную удаляю воркеры.

2) Единорог по какой-то причине порождает дополнительных рабочих, хотя я указал фиксированное количество рабочих (7 из них). Это частично вызывает накопление оперативной памяти, что также заставляет меня вручную удалять рабочие процессы.

3) Развертывание с нулевым временем простоя в моем случае ненадежно. Иногда он подхватывает изменения, иногда я получаю тайм-ауты шлюза. Каждое развертывание становится очень стрессовой ситуацией.

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

Итак, это нормально? Есть ли у других людей, использующих Unicorn, ту же проблему, когда оперативная память просто неконтролируемо растет?

А также, где рабочие количество созданных рабочих не соответствует количеству определенных рабочих?

Другой альтернативой является убийца рабочих-единорогов, которую я бы попробовал после прочтения Unicorn Eating Memory.

Небольшое обновление:

введите здесь описание изображения

Так что дошло до того, что New Relic сообщила мне, что память почти 95%. Поэтому мне пришлось убить рабочего. Интересно, что убийство этого работника значительно уменьшило объем памяти, как видно из графика ниже.

Что случилось с этим?

Для справки, вот мои unicorn.rb и unicorn_init.sh. Хотелось бы, чтобы кто-нибудь сказал мне, что где-то там ошибка.

unicorn.rb

root = "/home/deployer/apps/myapp/current"
working_directory root
pid "#{root}/tmp/pids/unicorn.pid"
stderr_path "#{root}/log/unicorn.stderr.log"
stdout_path "#{root}/log/unicorn.log"

listen "/tmp/unicorn.myapp.sock"
worker_processes 7
timeout 30

preload_app true

before_exec do |_|
  ENV["BUNDLE_GEMFILE"] = '/home/deployer/apps/myapp/current/Gemfile'
end

before_fork do |server, worker|
  # Disconnect since the database connection will not carry over
  if defined? ActiveRecord::Base
    ActiveRecord::Base.connection.disconnect!
  end

  old_pid = "#{root}/tmp/pids/unicorn.pid.oldbin`"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
  sleep 1
end

after_fork do |server, worker|
  # Start up the database connection again in the worker
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end

  Redis.current.quit
  Rails.cache.reconnect
end

unicorn_init.sh

#!/bin/sh
set -e

# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/home/deployer/apps/myapp/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="cd $APP_ROOT; BUNDLE_GEMFILE=/home/deployer/apps/myapp/current/Gemfile bundle exec unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
AS_USER=deployer
set -u
OLD_PIN="$PID.oldbin"

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
  test -s $OLD_PIN && kill -$1 `cat $OLD_PIN`
}

run () {
  if [ "$(id -un)" = "$AS_USER" ]; then
    eval $1
  else
    su -c "$1" - $AS_USER
  fi
}

case "$1" in
start)
  sig 0 && echo >&2 "Already running" && exit 0
  run "$CMD"
  ;;
stop)
  sig QUIT && exit 0
  echo >&2 "Not running"
  ;;
force-stop)
  sig TERM && exit 0
  echo >&2 "Not running"
  ;;
restart|reload)
  sig USR2 && echo reloaded OK && exit 0
  echo >&2 "Couldn't reload, starting '$CMD' instead"
  run "$CMD"
  ;;
upgrade)
  if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
  then
    n=$TIMEOUT
    while test -s $OLD_PIN && test $n -ge 0
    do
      printf '.' && sleep 1 && n=$(( $n - 1 ))
    done
    echo

    if test $n -lt 0 && test -s $OLD_PIN
    then
      echo >&2 "$OLD_PIN still exists after $TIMEOUT seconds"
      exit 1
    fi
    exit 0
  fi
  echo >&2 "Couldn't upgrade, starting '$CMD' instead"
  run "$CMD"
  ;;
reopen-logs)
  sig USR1
  ;;
*)
  echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
  exit 1
  ;;
esac

person Benjamin Tan Wei Hao    schedule 10.08.2013    source источник
comment
как вы создали этот график?   -  person ant    schedule 17.01.2017


Ответы (2)


Похоже, у вас есть две проблемы: 1) у вас есть ошибки в координации изящного перезапуска, из-за которых старые рабочие единороги и старый мастер остаются; 2) Ваше приложение (не единорог) теряет память.

В первом случае, глядя на ваш код before_fork, кажется, что вы используете подход с ограничением памяти из пример конфигурации Однако у вас есть опечатка в имени файла .oldbin (лишняя обратная галочка в конце), что означает, что вы никогда не сигнализируете старому процессу, потому что вы не можете прочитать pid из несуществующего файл.

Для более позднего вам придется исследовать и сверлить. Найдите в своем приложении семантику кэширования, которая со временем накапливает данные; внимательно изучите все случаи использования глобальных переменных, class-vars и class-instance-vars, которые могут сохранять ссылки на данные от запроса к запросу. Запустите несколько профилей памяти, чтобы охарактеризовать использование памяти. Вы можете смягчить утечку памяти, убивая рабочих, когда они превышают некоторый верхний предел; unicorn-worker-killer упрощает эту задачу.

person dbenhur    schedule 10.08.2013
comment
Спасибо! Сразу исправлю и отчитаюсь. Цените время. - person Benjamin Tan Wei Hao; 10.08.2013
comment
@dbenhur, не могли бы вы объяснить немного подробнее, что такое изящный перезапуск и этот подход с ограничением памяти? - person tulio84z; 05.05.2014
comment
@tulio84z Re: изящный перезапуск, прочитайте Процедура замены работающего исполняемого файла единорога в документах. Что касается коммерческого использования unicorn_worker_killer, ознакомьтесь с лицензией. - person dbenhur; 21.05.2014
comment
@dbenhur Как это сделать: запустите несколько профилей памяти, чтобы охарактеризовать использование вашей памяти. - person DelPiero; 10.03.2020

Используйте unicorn-worker-killer, это упрощает уничтожение рабочих, потребляющих много ОЗУ :)

person Kazuki Ohta    schedule 15.08.2015