2013-08-10 9 views
14

New Relic Process snapshotUnicorn Utilizzo memoria riempire quasi tutta la RAM

Ci sono essenzialmente 3: problemi qui

1) Unicorn sembra essere in costante riempire tutta la RAM, mi ha causato a rimuovere manualmente i lavoratori.

2) L'unicorno sembra generare ulteriori lavoratori per qualche motivo, anche se ho specificato un numero fisso di lavoratori (7 di loro). Ciò causa in parte l'accumulo di RAM, che mi causa anche la rimozione manuale dei lavoratori.

3) Nel mio caso, l'implementazione di zero tempi morti è inaffidabile. A volte capita le modifiche, a volte ricevo il timeout del gateway. Ogni schieramento diventa una situazione molto stressante.

Non mi piace molto usare Monit, perché uccide i lavoratori senza aspettare che i lavoratori finiscano di servire le loro richieste.

Quindi, è normale? Le altre persone che distribuiscono usando Unicorn hanno lo stesso problema in cui la RAM cresce in modo incontrollabile?

E anche dove i lavoratori il numero di lavoratori spawnati non corrisponde al numero di lavoratori definiti?

L'altra alternativa è l'assassino di unicorno, che stavo provando dopo aver letto Unicorn Eating Memory.

Aggiornamento Piccolo:

enter image description here

Così è venuto a un punto in cui Nuovo Relic mi stava dicendo la memoria era quasi il 95%. Quindi ho dovuto uccidere un lavoratore. È interessante notare come l'omicidio di quell'operaia abbia ridotto la memoria di molto, come si vede dal grafico sottostante.

Che succede?

Per riferimento, ecco il mio unicorn.rb e unicorn_init.sh. Mi piacerebbe che qualcuno mi dicesse che c'è un errore lì da qualche parte.

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 
+0

come hai generare questo grafico? – ant

risposta

9

Si sembrano avere due problemi: 1) Hai errori nel coordinamento di riavviare grazioso causando vecchi operai unicorno e il vecchio maestro aggrapparsi; 2) La tua app (non unicorno) sta perdendo memoria.

Per i primi, guardando il codice before_fork, sembra che si sta utilizzando l'approccio di memoria-vincolante da the example config Tuttavia, è un errore di battitura nel nome del file .oldbin (un back-tick estranea alla fine) che significa non segnali mai il vecchio processo perché non puoi leggere il pid da un file inesistente.

Per il dopo, si dovrà indagare e trapanare.Cerca nella tua app la semantica della cache che accumula i dati nel tempo; esaminare attentamente tutti gli usi di globals, class-vars e class-instance-vars che possono conservare i riferimenti dei dati dalla richiesta alla richiesta. Esegui alcuni profili di memoria per caratterizzare l'utilizzo della memoria. Puoi mitigare la perdita di memoria uccidendo i lavoratori quando crescono più grandi di un limite superiore; unicorn-worker-killer rende tutto questo facile.

+0

Grazie! Lo aggiusterò subito e riferirò. Apprezzo il tempo –

+0

Può questo killer unicorno-lavoratore essere usato in modo commerciale? – tulio84z

+0

@dbenhur potresti spiegare un po 'quale "riavvio graduale" e questo approccio vincolante della memoria? – tulio84z

3

Usa unicorn-worker-killer, questo rende più facile per uccidere i lavoratori che consumano un sacco di RAM :)