Now-Next

SaaS architecture

database is being accessed by other users in PostgreSQL

PostgreSQL's database is being accessed by other users, measured on 12, 13, 15 and 17: the 5-second wait, WITH (FORCE), its refusals and the reconnect race.

Chris van Eijk, Now-Next · · 7 min read

ERROR: database "app" is being accessed by other users (SQLSTATE 55006) means you tried to drop, rename or copy a database while another session was connected to it. PostgreSQL will not remove a database out from under a connection, so the command fails and nothing changes. The DETAIL line says how many sessions are in the way; finding them is a query on pg_stat_activity, and getting rid of them is either DROP DATABASE … WITH (FORCE) or terminating them yourself.

We measured on PostgreSQL 17.11; the plain FORCE drop, the prepared-transaction refusal and the permission refusals also on 13.23, the 5-second wait also on 12.22, 13.23 and 15.19, and CREATE DATABASE … STRATEGY on 15.19, all in containers.

Why does DROP DATABASE say other users are connected?

Because one is — any session, idle or not. An idle psql window was enough:

drop database app;
-- ERROR:  55006: database "app" is being accessed by other users
-- DETAIL:  There is 1 other session using the database.
-- (with three sessions: There are 3 other sessions using the database.)

The error is not immediate. DROP DATABASE checks 50 times at 100 ms intervals whether the sessions have gone (CountOtherDBBackends in the PostgreSQL source: “50 tries with 100ms sleep between tries makes 5 sec total wait”); every ‘being accessed’ failure we measured came after 5.04 s (5,035 to 5,041 ms, on 12, 13, 15 and 17). During that wait new connections to the database queue behind it: one we opened 1 s after the drop got in only after 4.04 s, when the drop had failed. In that window it waits for the sessions to leave: with a session that ended its pg_sleep(2) 0.3 s after the drop started, the drop simply succeeded after 1.77 s.

Connected to the database you are dropping, you get a different message straight away: cannot drop the currently open database (also 55006), with or without FORCE. ALTER DATABASE app RENAME TO app2 gave the same “being accessed by other users” error after the same 5.04 s, and from inside the database current database cannot be renamed (0A000). A non-owner got must be owner of database app (42501) before any session check.

The same message comes with a prefix when you copy a database: CREATE DATABASE copy TEMPLATE src with one session on src returned source database "src" is being accessed by other users, also after 5.04 s. A plain CREATE DATABASE copies template1, so a single open session on template1 blocked every CREATE DATABASE on the server with source database "template1" is being accessed by other users; TEMPLATE template0 went through. STRATEGY wal_log and STRATEGY file_copy failed identically on 15 and 17.

Who is connected to the database?

select pid, usename, application_name, client_addr, state, backend_type
from pg_stat_activity
where datname = 'app' and pid <> pg_backend_pid();
--  pid | usename  | application_name | client_addr |        state        |  backend_type
-- 1325 | postgres | holder           | 172.17.0.1  | active              | client backend
-- 1326 | postgres | worker           | 172.17.0.1  | idle in transaction | client backend
-- 1327 | postgres | psql             | 172.17.0.1  | idle                | client backend

All three counted, whatever their state: the drop failed with There are 3 other sessions. An idle connection in an application’s pool, or a database GUI that still has the database open, is a session like any other and shows up as a row here — application_name and client_addr tell you whose. A session idle in transaction holds more than a connection; that is measured in idle in transaction in PostgreSQL.

Not everything in the list blocks. We made autovacuum work slowly on a table in app; pg_stat_activity showed it as backend_type = autovacuum worker running autovacuum: VACUUM ANALYZE public.big, and a plain DROP DATABASE (no FORCE) still succeeded in 0.18 s on 17.11: DROP DATABASE sends autovacuum workers in the database SIGTERM itself before it waits. The server log had FATAL: terminating autovacuum process due to administrator command.

How do you drop a database with active connections?

On PostgreSQL 13 and later: DROP DATABASE app WITH (FORCE). It took 0.17 s on 13.23 and 17.11. A session that was running a query, and an idle one at its next statement, both got:

-- FATAL:  57P01: terminating connection due to administrator command
-- server closed the connection unexpectedly

On 12.22 the option does not exist: ERROR: 42601: syntax error at or near "with".

FORCE does not always work, and when it refuses it terminates no one: in a mixed test (the owner’s own session plus a superuser session) the error came back and both sessions were still connected afterwards.

What is in the wayMessage with WITH (FORCE), PostgreSQL 17What to do
any client session (idle, active, idle in transaction)none, dropped in 0.17 s; the session gets FATAL 57P01—
an autovacuum workernone, also without FORCE—
a prepared transactiondatabase "app" is being used by prepared transactions / DETAIL: There is 1 prepared transaction using the database. (without FORCE: the usual message after the same 5.04 s, same DETAIL; with FORCE the refusal comes at once, on 13 and 17, and the other session stays connected)COMMIT PREPARED or ROLLBACK PREPARED from inside the database
an active logical replication slot (a pg_recvlogical streaming)database "app" is used by an active logical replication slot / DETAIL: There is 1 active slot., after 35 msstop the consumer; an inactive slot was dropped together with the database
a superuser’s session, dropped by a non-superuser owner42501 permission denied to terminate process / DETAIL: Only roles with the SUPERUSER attribute may terminate processes of roles with the SUPERUSER attribute. (13: must be a superuser to terminate superuser process)have a superuser drop it
another non-superuser role’s session, dropped by the owner42501 permission denied to terminate process / DETAIL: Only roles with privileges of the role whose process is being terminated or with privileges of the "pg_signal_backend" role may terminate this process. (13: must be a member of the role whose process is being terminated or member of pg_signal_backend, no DETAIL)grant pg_signal_backend to the owner: then it worked

Note the inactive slot: pg_replication_slots went from one slot to zero without a message.

Why does pg_terminate_backend not stop the error?

Because whatever opened the connection opens it again. We ran two clients in a loop that reconnected 50 ms after losing their connection, the way a pool or a worker with retries does, and terminated them with:

select pg_terminate_backend(pid)
from pg_stat_activity
where datname = 'app' and pid <> pg_backend_pid();
Sequence, two reconnecting clientsResult in 5 runs
terminate and DROP DATABASE in one psql call5 dropped, in 0.08 s
terminate, then the drop 0.5 s later5 failed after 5.6 s: There are 2 other sessions using the database.
ALTER DATABASE app ALLOW_CONNECTIONS false, terminate, drop 0.5 s later5 dropped
REVOKE CONNECT ON DATABASE app FROM PUBLIC, terminate, drop 0.5 s later5 dropped
WITH (FORCE) (13+)5 dropped, in 0.17 s

With ALLOW_CONNECTIONS false they got FATAL: database "app" is not currently accepting connections instead, and after REVOKE CONNECT, FATAL: permission denied for database "app". Of the two, ALLOW_CONNECTIONS false is the one that also stopped a superuser; after the REVOKE, our superuser connected without trouble. If you then decide not to drop, set it back to true. WITH (FORCE) did not lose the race in our runs, and that is not luck: DROP DATABASE locks the database before it terminates anyone, so a client that reconnects waits on that lock. But a pool that reconnects to a database that no longer exists just fails differently: FATAL: database "app" does not exist. Stop the application first. Reconnect loops that pile up connections have their own error: remaining connection slots are reserved.

How 55006 relates to the other codes in its class is in our overview of PostgreSQL error codes.

What we did not measure

Subscriptions on the target database, which the documentation also names as a reason for FORCE to refuse. Behaviour behind PgBouncer, where the pooler’s server connections are the sessions you see. How specific frameworks, test runners or GUI tools manage their connections; we only measured psql clients. Streaming (physical) replicas.

Sources: PostgreSQL 17 documentation on DROP DATABASE and template databases, read on 22 October 2026, and the PostgreSQL 17 source (procarray.c, CountOtherDBBackends and TerminateOtherDBBackends). All measurements are our own, on PostgreSQL 17.11, 15.19, 13.23 and 12.22 in containers, on 22 October 2026.

Let's talk

What are we building?

One conversation is enough to know whether we fit. Tell us what you have in mind — we will tell you how fast it can happen, and whether we are the right people for it.

Start the conversation