Использование памяти единорога заполняет почти все ОЗУ

New Relic Process snapshot

Здесь есть по существу 3 проблемы:

1) Единорог, похоже, неуклонно заполняет всю ОЗУ, заставляя меня вручную удалять работников.

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

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

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

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

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

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

Маленькое обновление:

enter image description here

Итак, дошло до того, что 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

Ответы

Ответ 1

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

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

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

Ответ 2

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