Error de caída de Rails + Postgres: otros usuarios están accediendo a la base de datos


91

Tengo una aplicación de rieles que se ejecuta en Postgres.

Tengo dos servidores: uno para pruebas y otro para producción.

Muy a menudo necesito clonar la base de datos de producción en el servidor de prueba.

El comando que estoy ejecutando a través de Vlad es:

rake RAILS_ENV='test_server' db:drop db:create

El problema que tengo es que recibo el siguiente error:

ActiveRecord::StatementInvalid: PGError: ERROR: database <database_name> is being accessed by other users DROP DATABASE IF EXISTS <database_name>

Esto sucede si alguien ha accedido a la aplicación a través de la web recientemente (postgres mantiene una "sesión" abierta)

¿Hay alguna forma de que pueda terminar las sesiones en la base de datos de postgres?

Gracias.

Editar

Puedo eliminar la base de datos usando la interfaz de phppgadmin pero no con la tarea rake.

¿Cómo puedo replicar la caída de phppgadmin con una tarea de rake?


Asegúrese de no tener conexiones a la base de datos o no la dejará. Consulte más sobre esto aquí .
Nesha Zoric

Respuestas:


82

Si cancela las conexiones postgresql en ejecución para su aplicación, puede ejecutar db: drop sin problemas. Entonces, ¿cómo eliminar esas conexiones? Utilizo la siguiente tarea de rastrillo:

# lib/tasks/kill_postgres_connections.rake
task :kill_postgres_connections => :environment do
  db_name = "#{File.basename(Rails.root)}_#{Rails.env}"
  sh = <<EOF
ps xa \
  | grep postgres: \
  | grep #{db_name} \
  | grep -v grep \
  | awk '{print $1}' \
  | xargs kill
EOF
  puts `#{sh}`
end

task "db:drop" => :kill_postgres_connections

Eliminar las conexiones de debajo de los rieles a veces hará que la próxima vez que intente cargar una página se eche a vomitar, pero volver a cargarla restablecerá la conexión.


2
Tuve que agregar sudo a xargs y cambiar el nombre de la base de datos, pero funciona. TY
lzap

1
Lo mismo para mí ... cambiado a "sudo xargs kill" y db_name codificado de forma rígida a "my-development-database-name"
Kevin Dewalt

7
task "db:drop" => :kill_postgres_connectionsCreo que esta línea debería eliminarse, es peligroso extender el comportamiento de la tarea del sistema, desde mi punto de vista.
misa.im

En lugar de codificar el nombre de su base de datos, utilice lo siguiente:db_name = Rails.configuration.database_configuration[Rails.env]['database']
tala

41

La forma más fácil y actualizada es: 1. Use ps -ef | grep postgrespara encontrar la conexión # 2.sudo kill -9 "# of the connection

Nota: Puede haber un PID idéntico. Matar a uno mata a todos.


¿Qué número en el resultado representa el PID? Veo 3 columnas sin etiqueta con números que parecen PID.
BradGreens

3
@BradGreens segunda columna (estoy usando Mac Terminal)
s2t2

No se encuentra nada con ps, pero sigue apareciendo el error en db: drop.
JosephK

17

Aquí hay una forma rápida de eliminar todas las conexiones a su base de datos de postgres.

sudo kill -9 `ps -u postgres -o pid` 

Advertencia: esto matará cualquier proceso en ejecución que el postgresusuario haya abierto, así que asegúrese de querer hacer esto primero.


11
En mi sistema lo uso sudo kill -9 `ps -u postgres -o pid=` en su lugar, por lo que no se imprimirá un encabezado PIDps , por lo que no se le pasa un argumento de cadena kill, por lo que no se generará un error. Buen consejo en cualquier caso.
deivid

1
Sigo recibiendo votos a favor y en contra, lo que resulta en una calificación cercana a cero. Parece ser una "solución rápida" controvertida. Permítanme dejar constancia de que hice una advertencia de que es peligroso. :)
Jamon Holmgren

3
Use esto para iniciar postgresql nuevamente si está en Ubuntu:sudo service postgresql start
Viernes

9

Cuando usamos el método de "matar procesos" de arriba, el db: drop fallaba (si: kill_postgres_connections era un requisito previo). Creo que fue porque la conexión que usaba el comando rake estaba siendo eliminada. En cambio, estamos usando un comando sql para eliminar la conexión. Esto funciona como un requisito previo para db: drop, evita el riesgo de matar procesos a través de un comando bastante complejo y debería funcionar en cualquier sistema operativo (gentoo requiere una sintaxis diferente para kill).

cmd = %(psql -c "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE procpid <> pg_backend_pid();" -d '#{db_name}')

Aquí hay una tarea de rake que lee el nombre de la base de datos de database.yml y ejecuta un comando mejorado (en mi humilde opinión). También agrega db: kill_postgres_connections como requisito previo a db: drop. Incluye una advertencia que grita después de actualizar rieles, lo que indica que es posible que este parche ya no sea necesario.

ver: https://gist.github.com/4455341 , referencias incluidas


8

Utilizo la siguiente tarea de rake para anular el drop_databasemétodo Rails .

lib/database.rake

require 'active_record/connection_adapters/postgresql_adapter'
module ActiveRecord
  module ConnectionAdapters
    class PostgreSQLAdapter < AbstractAdapter
      def drop_database(name)
        raise "Nah, I won't drop the production database" if Rails.env.production?
        execute <<-SQL
          UPDATE pg_catalog.pg_database
          SET datallowconn=false WHERE datname='#{name}'
        SQL

        execute <<-SQL
          SELECT pg_terminate_backend(pg_stat_activity.pid)
          FROM pg_stat_activity
          WHERE pg_stat_activity.datname = '#{name}';
        SQL
        execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
      end
    end
  end
end

¿Alguna vez terminaste leyendo esa advertencia de producción? Solo curiosidad: P
Vinicius Brasil

6

Compruebe si su consola o servidor de rails se está ejecutando en otra pestaña y luego

Detenga el servidor de rieles y la consola.

entonces corre

 rake db:drop

5

Deje que su aplicación cierre la conexión cuando esté lista. PostgreSQL no mantiene abiertas las conexiones, es la aplicación la que mantiene la conexión.


1
Puedo eliminar la base de datos usando la interfaz de phppgadmin pero no con la tarea de rake. ¿Cómo puedo replicar la caída de phppgadmin con una tarea de rastrillo?
Fjuan

Lo siento, no puedo ayudarte en eso, no tengo experiencia con rake. Pero el error indica que otro usuario todavía está usando la base de datos. Es por eso que no puede eliminar la base de datos, no por rake ni por PhpPgAdmin, imposible. Del manual, DROP DATABASE: no se puede ejecutar mientras usted o cualquier otra persona esté conectada a la base de datos de destino.
Frank Heikens

3

Es probable que Rails se conecte a la base de datos para eliminarlo, pero cuando inicia sesión a través de phppgadmin, está iniciando sesión a través de la base de datos template1 o postgres, por lo que no se ve afectado por ella.


¿Cómo puedo obligar a los rieles a soltar la base de datos? ¿Debo definir mi propia acción de rake usando comandos SQL de postgres?
Fjuan

3

Esto funcionó para mí (rieles 6): rake db:drop:_unsafe

Creo que teníamos algo en nuestro código base que inició una conexión de base de datos antes de que la tarea de rastrillo intentara eliminarlo.


2

Escribí una gema llamada pgreset que eliminará automáticamente las conexiones a la base de datos en cuestión cuando ejecute rake db: drop (o db: reset, etc.). Todo lo que tiene que hacer es agregarlo a su Gemfile y este problema debería desaparecer. En el momento de escribir este artículo, funciona con Rails 4 y versiones posteriores y se ha probado en Postgres 9.x. El código fuente está disponible en github para cualquier persona interesada.

gem 'pgreset'

Nada más que su gema lo haría: la conexión no existía (búsqueda de ps grep, etc.), pero rails pensó que sí. ¡¡Muchas gracias!!
JosephK

1

Simplemente puede parchear el código ActiveRecord que hace la caída.

Para rieles 3.x:

# lib/tasks/databases.rake
def drop_database(config)
  raise 'Only for Postgres...' unless config['adapter'] == 'postgresql'
  Rake::Task['environment'].invoke
  ActiveRecord::Base.connection.select_all "select pg_terminate_backend(pg_stat_activity.pid) from pg_stat_activity where datname='#{config['database']}' AND state='idle';"
  ActiveRecord::Base.establish_connection config.merge('database' => 'postgres', 'schema_search_path' => 'public')
  ActiveRecord::Base.connection.drop_database config['database']
end

Para Rails 4.x:

# config/initializers/postgresql_database_tasks.rb
module ActiveRecord
  module Tasks
    class PostgreSQLDatabaseTasks
      def drop
        establish_master_connection
        connection.select_all "select pg_terminate_backend(pg_stat_activity.pid) from pg_stat_activity where datname='#{configuration['database']}' AND state='idle';"
        connection.drop_database configuration['database']
      end
    end
  end
end

(de: http://www.krautcomputing.com/blog/2014/01/10/how-to-drop-your-postgres-database-with-rails-4/ )


1

Tuve este mismo problema cuando trabajaba con una aplicación Rails 5.2 y una base de datos PostgreSQL en producción.

Así es como lo resolví :

Primero, cierre la sesión de todas las conexiones al servidor de la base de datos en el Cliente PGAdmin, si corresponde.

Detenga cada sesión usando la base de datos del terminal.

sudo kill -9 `ps -u postgres -o pid=`

Inicie el servidor PostgreSQL, ya que la operación de eliminación anterior detuvo el servidor PostgreSQL.

sudo systemctl start postgresql

Coloque la base de datos en el entorno de producción agregando los argumentos de producción.

rails db:drop RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1

Eso es todo.

espero que esto ayude


0

Solo asegúrese de haber salido de la consola de rieles en cualquier ventana de terminal abierta y salido del servidor de rieles ... este es uno de los errores más comunes que cometen las personas


0

Tuve un error similar que decía que 1 usuario estaba usando la base de datos, ¡me di cuenta de que era YO! Apagué mi servidor de rieles y luego hice el comando rake: drop y ¡funcionó!


0

Después de reiniciar el servidor o la computadora, vuelva a intentarlo.

Podría ser la solución más sencilla.


0

Solución

Guión bash

ENV=development

# restart postgresql
brew services restart postgresql

# get name of the db from rails app
RAILS_CONSOLE_COMMAND="bundle exec rails c -e $ENV"
DB_NAME=$(echo 'ActiveRecord::Base.connection_config[:database]' | $RAILS_CONSOLE_COMMAND | tail -2 | tr -d '\"')

# delete all connections to $DB_NAME
for pid in $(ps -ef | grep $DB_NAME | awk {'print$2'})
do
   kill -9 $pid
done

# drop db
DISABLE_DATABASE_ENVIRONMENT_CHECK=1 RAILS_ENV=$ENV bundle exec rails db:drop:_unsafe
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.