Rails + PostgreSQL でデータベースを作り直す

PostgreSQLでは、他のセッションが同じdatabaseに接続しているときは DROP DATABASE できないようになっている。

my_database=# DROP DATABASE my_database;
ERROR:  database "my_database" is being accessed by other users
DETAIL:  There is 1 other session using the database.

そのため、Rails で以下のようにデータベースを作り直すときにしばしば問題になる。

$ rails db:drop db:setup
PG::ObjectInUse: ERROR:  database "my_database" is being accessed by other users
DETAIL:  There is 1 other session using the database.
Couldn't drop database 'my_database'
rails aborted!
ActiveRecord::StatementInvalid: PG::ObjectInUse: ERROR:  database "my_database" is being accessed by other users (ActiveRecord::StatementInvalid)
DETAIL:  There is 1 other session using the database.


Caused by:
PG::ObjectInUse: ERROR:  database "my_database" is being accessed by other users (PG::ObjectInUse)
DETAIL:  There is 1 other session using the database.

Tasks: TOP => db:drop:_unsafe
(See full trace by running task with --trace)

これを解決するためには、他のセッションを切断してから再度 DROP DATABASE すればいいが、以下のSQLを実行することでも切断することができる。

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'my_database' AND pid <> pg_backend_pid();
my_database=# SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'my_database' AND pid <> pg_backend_pid();
 pg_terminate_backend
----------------------
 t
(1 row)

そのため、以下のように他セッションの接続を切断する Rake task を作成し実行すれば他のセッションを気にすることなく作成しなおすことができるようになる。

lib/tasks/db/connection.rake:

namespace :db do
  namespace :connection do
    desc 'Close all DB connections'
    task :close => :environment do
      db_name = ActiveRecord::Base.connection.current_database
      ActiveRecord::Base.connection.execute(ActiveRecord::Base.sanitize_sql_array([<<~SQL, db_name]))
        SELECT pg_terminate_backend(pid)
        FROM pg_stat_activity
        WHERE datname = '%s' AND pid <> pg_backend_pid();
      SQL
    end
  end
end
$ rails db:connection:close db:drop db:setup
Dropped database 'my_database'
Created database 'my_database'

環境

参考