web-dev-qa-db-fra.com

Capistrano avec PostgreSQL, erreur: la base de données est en cours d'accès par d'autres utilisateurs

J'ai une application Rails qui utilise PostgreSQL comme backend avec un environnement cert qui essaie d'imiter la production, sauf qu'il doit avoir la base de données réinitialisée périodiquement pour le contrôle qualité.

Lorsque j'essaie d'exécuter db:reset à partir d'une tâche Capistrano pendant le déploiement, j'obtiens l'erreur:

ERROR: database "database_name" is being accessed by other users

et la base de données ne peut pas être supprimée dans le cadre de la tâche de réinitialisation, ce qui entraîne l'échec du déploiement. Existe-t-il un moyen de réinitialiser les connexions de base de données à partir de Capistrano afin de pouvoir supprimer correctement la table? Piping le SQL vers psql à partir d'une tâche Capistrano pourrait fonctionner mais je me demandais s'il y avait une meilleure façon de procéder.

30
Graham Conzett

J'ai combiné réponse de dbenhur avec cette tâche Capistrano pour obtenir le résultat dont j'avais besoin fonctionne comme un charme:

desc 'kill pgsql users so database can be dropped'
task :kill_postgres_connections do
  run 'echo "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname=\'database_name\';" | psql -U postgres'
end

Cela suppose que l'auth_method pour les postgres de l'utilisateur est défini sur 'trust' dans pg_hba.conf

Ensuite, vous pouvez simplement l'appeler dans votre tâche de déploiement après update_code et avant migrate

after 'deploy:update_code', 'kill_postgres_connections'
5
Graham Conzett

Avec PostgreSQL, vous pouvez émettre l'instruction suivante pour renvoyer les pids backend de toutes les connexions ouvertes autres que celle-ci:

SELECT pid FROM pg_stat_activity where pid <> pg_backend_pid();

Ensuite, vous pouvez envoyer une demande de résiliation à chacun de ces backends avec

SELECT pg_terminate_backend($1);

Lier les pids renvoyés par la première instruction à chaque exécutable pg_terminate_backend.

Si les autres connexions n'utilisent pas le même utilisateur que vous, vous devrez vous connecter en tant que superutilisateur pour réussir l'émission des terminaisons.

MISE À JOUR: Incorporation des commentaires et expression en tant que tâche Capistrano:

desc "Force disconnect of open backends and drop database"
task :force_close_and_drop_db do
  dbname = 'your_database_name'
  run "psql -U postgres",
      :data => <<-"PSQL"
         REVOKE CONNECT ON DATABASE #{dbname} FROM public;
         ALTER DATABASE #{dbname} CONNECTION LIMIT 0;
         SELECT pg_terminate_backend(pid)
           FROM pg_stat_activity
           WHERE pid <> pg_backend_pid()
           AND datname='#{dbname}';
         DROP DATABASE #{dbname};
      PSQL
end
55
dbenhur

Vous pouvez simplement monkeypatch le code ActiveRecord qui effectue la suppression.

Pour Rails 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

Pour 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

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

1
Manuel Meurer