Kill Idle PostgreSQL Sessions with SQL
Kill Idle PostgreSQL Sessions with SQL
The connection count sits at max_connections, new clients are being refused, and pg_stat_activity is full of sessions that have run nothing for hours. Freeing the slots takes one statement, pg_terminate_backend(), but the choices around it decide whether the cleanup is safe: which sessions to select, which signal to send, who is allowed to send it, and what happens to the client on the other end.
Purpose and Overview
Every connected session holds a server process, memory, and a connection slot, whether or not it is doing any work. A session that is idle in the plain sense costs little beyond the slot. A session that is idle in transaction costs more, because its open transaction keeps its locks and prevents vacuum from removing recently dead rows. Cleaning up either kind is a routine DBA task when a connection pooler leaks sessions, an application forgets to close transactions, or an interactive client is left open.
The procedure here is the termination itself. Finding and measuring idle sessions, and setting server-side timeouts that close them automatically, are covered in identifying idle PostgreSQL connections, and the slow-query side is in monitoring slow running queries. For a broader look at everything a server is running before deciding what to stop, see monitoring running queries with pg_stat_activity.
Two functions do the work. pg_cancel_backend(pid) stops the current query and leaves the connection open. pg_terminate_backend(pid) ends the session. An idle session has no current query to cancel, so for idle sessions the terminate function is the one that frees the slot. The selection logic matters more than the call: pick the wrong filter and the statement kills healthy connections.
Sample Code
Preview first. This lists sessions that have been idle, or idle inside a transaction, for more than 15 minutes:
1SELECT
2 pid,
3 datname,
4 usename,
5 application_name,
6 client_addr,
7 state,
8 clock_timestamp() - state_change AS idle_for,
9 clock_timestamp() - xact_start AS xact_age,
10 left(query, 60) AS last_query
11FROM pg_stat_activity
12WHERE backend_type = 'client backend'
13 AND pid <> pg_backend_pid()
14 AND state IN ('idle', 'idle in transaction', 'idle in transaction (aborted)')
15 AND state_change < clock_timestamp() - interval '15 minutes'
16ORDER BY idle_for DESC;
Terminate the same set, on every database in the cluster:
1SELECT
2 pid,
3 usename,
4 application_name,
5 pg_terminate_backend(pid) AS signalled
6FROM pg_stat_activity
7WHERE backend_type = 'client backend'
8 AND pid <> pg_backend_pid()
9 AND state IN ('idle', 'idle in transaction', 'idle in transaction (aborted)')
10 AND state_change < clock_timestamp() - interval '15 minutes';
Terminate in the current database only, protecting admin tools and service accounts:
1SELECT
2 pid,
3 pg_terminate_backend(pid, 5000) AS terminated
4FROM pg_stat_activity
5WHERE backend_type = 'client backend'
6 AND pid <> pg_backend_pid()
7 AND datname = current_database()
8 AND usename NOT IN ('postgres')
9 AND application_name !~ '^(psql|pgAdmin)'
10 AND state IN ('idle', 'idle in transaction', 'idle in transaction (aborted)')
11 AND state_change < clock_timestamp() - interval '15 minutes';
For a query that is running rather than idle, cancel it first and leave the connection alone:
1SELECT pid, pg_cancel_backend(pid) AS cancelled
2FROM pg_stat_activity
3WHERE state = 'active'
4 AND pid <> pg_backend_pid()
5 AND query_start < clock_timestamp() - interval '30 minutes';
Notes: The idle-time test uses state_change, which needs PostgreSQL 9.2 or later. The backend_type filter needs PostgreSQL 10; on earlier releases remove it. The two-argument form pg_terminate_backend(pid, 5000) needs PostgreSQL 14; on earlier releases use the one-argument form. Replace 'postgres' and the application-name pattern with the accounts and tools to protect.
Code Breakdown
The selection is built from the same four conditions in every variant.
state_change, not query_start
query_start is the time the currently active query started, or, when the state is not active, the time the last query started. For an idle session that is the start of the last query, not the moment the session went idle. A session that ran a 40-minute report and finished one minute ago has an old query_start and a one-minute-old state_change, so a filter on query_start would terminate a session that has barely been idle. state_change is the time the state last changed, which for an idle session is when it finished its last command. The idle-time test belongs on state_change. query_start is the right column for the cancel variant above, because there the state is active and query_start is when the running query began.
State list
idle is a connection waiting for the next command. idle in transaction has an open transaction and is running nothing. idle in transaction (aborted) is the same except a statement in the transaction raised an error, and the session is waiting for a ROLLBACK. All three are in the list. A fourth value, disabled, means track_activities is off for that backend, so its other columns cannot be trusted and it is left out here.
Why there is no filter on the query text
The query column shows the currently executing query when the state is active, and the last query that ran in every other state. An idle session therefore almost always has non-empty query text, so a condition such as query = '' matches only sessions that never ran a statement. It does not mean "no work in progress", and it is not used here. The state column is the correct signal.
Self and system exclusions
pid <> pg_backend_pid() keeps the session from terminating itself. backend_type = 'client backend' keeps background processes such as the checkpointer and autovacuum workers out of the result, so the statement only ever targets user connections.
The signal functions
pg_cancel_backend and pg_terminate_backend send SIGINT and SIGTERM respectively to the backend with the given process ID, and both return a boolean. Without a timeout, pg_terminate_backend returns true as soon as the signal was sent, whether or not the process has actually exited. With a timeout in milliseconds greater than zero, it waits until the process has terminated or the time has passed. If the process ended, it returns true. On timeout it emits a warning and returns false. Returning pid next to the function result gives a record of what was signalled.
Key Permission and Safety Rules
Signalling another session is a privileged action, and a few rules decide who can do it.
Who may signal a session
A superuser can signal any session. A non-superuser can signal a session if the calling role is a member of the role that owns the target backend, or if the calling role has privileges of the predefined pg_signal_backend role. Only a superuser can signal a superuser's backend, so even pg_signal_backend does not extend to those sessions. pg_signal_backend was introduced in PostgreSQL 9.6.
Least privilege for a cleanup job
A cleanup job does not need a superuser. Granting pg_signal_backend to a dedicated maintenance role limits what the job can touch to non-superuser sessions, and the usename exclusion in the script protects the sessions that should stay.
What the client sees
Termination closes the connection from the server side. The client gets a connection error on its next use, and any open transaction in that session does not commit. Applications and poolers that do not expect a closed connection can fail or hold a dead connection in their pool, which is why the exclusion filters and the preview matter on managed pools.
Practical Applications
The same filter serves several situations, with a different safety margin each time.
Freeing connection slots under pressure
When new connections are being refused, run the preview, check that application_name and client_addr on the oldest rows belong to a pool that can reconnect, then run the terminate variant.
Clearing an idle-in-transaction blocker
A session in idle in transaction with an old xact_age can hold locks that block other sessions. Find it with the blocking query in the running-queries post, then terminate that one pid. Cancelling is not useful here because the session is not running a query.
Scheduled cleanup of stale pool connections
Wrap the terminate statement in a job that runs every few minutes with a longer idle threshold, and have it insert the returned pid, usename, and application_name into a log table. Repeated hits from the same application show which client is leaking sessions, which is the real fix.
Cancel first on running queries
For a runaway query, pg_cancel_backend ends the statement and keeps the connection, so a pooled application can carry on. Terminate only if the cancel does not take effect.
Version Compatibility
The idle-time filter needs state and state_change in pg_stat_activity, available since PostgreSQL 9.2. The backend_type column arrived in PostgreSQL 10. The predefined role pg_signal_backend arrived in PostgreSQL 9.6. The optional timeout argument to pg_terminate_backend was added in PostgreSQL 14.
PostgreSQL 14 also added the idle_session_timeout parameter, which closes sessions that have been idle outside a transaction for longer than the configured time. idle_in_transaction_session_timeout does the same for sessions idle inside a transaction. Both default to zero, which disables them. Pooling software and other middleware may not react well to an unexpected connection closure, so enable idle_session_timeout for particular users rather than for connections that come through a pooler. A server-side timeout is the durable fix; this script is the manual tool for the moment before one is in place.
Best Practices
- Preview with the same WHERE clause — run the
SELECTversion first and readapplication_name,client_addr, andidle_forbefore terminating anything. - Measure idle time with
state_change—query_startreports when the last query began, so it overstates idle time for any session whose last query was long. - Treat
idle in transactionseparately — terminating it discards uncommitted work, so use a longer threshold and log thepid, user, and application. - Protect admin and service sessions — exclude
usenameandapplication_namevalues you rely on before running against a shared cluster. - Use a narrow role — grant
pg_signal_backendto a dedicated cleanup role instead of running the job as a superuser. - Prefer server timeouts for steady state —
idle_in_transaction_session_timeoutandidle_session_timeoutclose sessions without a scheduled script.
References
- PostgreSQL: Server Signaling Functions —
pg_cancel_backendandpg_terminate_backend, the timeout argument, return values, and the permission rules. - PostgreSQL: The Cumulative Statistics System —
pg_stat_activitycolumns, includingstate,state_change,query_start, and the meaning ofqueryoutside theactivestate. - PostgreSQL: Client Connection Defaults —
idle_session_timeoutandidle_in_transaction_session_timeout. - PostgreSQL: Predefined Roles —
pg_signal_backendand what it does not permit. - depesz: Understanding pg_stat_activity — The difference between
query_startandstate_changefor idle backends. - HariSekhon/SQL-scripts: postgres_idle_sessions_kill.sql — The original script this post revises.
- HariSekhon/SQL-scripts LICENSE (MIT) — Copyright 2020 Hari Sekhon. The script in this post is used under the MIT License.
Posts in this series
- How Many Connections Can Your PostgreSQL Database Handle?
- PostgreSQL Backend Connections via pg_stat_database
- pg_blocking_pids — Find Blocking Queries in PostgreSQL
- List PostgreSQL Databases by Size with Access Check
- Assess PostgreSQL Database Sizes Quickly and Easily
- Unveiling Your PostgreSQL Server - A Diagnostic Powerhouse
- Keep Your PostgreSQL Database Clean, Identify Idle Connections
- Query the PostgreSQL Configuration
- pg_is_in_recovery — Monitor PostgreSQL Standby Status
- ALTER SEQUENCE RESTART WITH in PostgreSQL — Examples
- Monitor Running Queries in PostgreSQL using pg_stat_activity
- Monitor PostgreSQL Active Sessions with pg_stat_activity
- PostgreSQL Error Handling Settings via pg_settings
- PostgreSQL File Location Settings Query via pg_settings
- Inspect PostgreSQL Lock Management Settings with pg_settings
- PostgreSQL Logging Configuration Query via pg_settings
- Monitor PostgreSQL Memory Settings with pg_settings
- PostgreSQL Table Row Count Estimates with SQL
- List PostgreSQL Tables by Size with SQL
- PostgreSQL WAL Settings Query Guide
- log_parser_stats, log_planner_stats, log_executor_stats — PostgreSQL
- PostgreSQL SSL Settings Query Guide
- PostgreSQL Resource Settings Query Guide
- PostgreSQL Replication Settings Query Guide
- PostgreSQL Query Planning Settings Query Guide
- PostgreSQL Preset Options Settings Query Guide
- PostgreSQL Miscellaneous Settings Query Guide
- Count PostgreSQL Sessions by State with SQL
- Kill Idle PostgreSQL Sessions with SQL
- GRANT SELECT on All Tables in PostgreSQL — with Examples
- pg_stat_user_tables — Find Insert-Only Tables in PostgreSQL
- Detect Soft Delete Patterns in PostgreSQL
- List PostgreSQL Object Comments with SQL
- List Foreign Key Constraints in PostgreSQL
- List PostgreSQL Enum Types and Their Values with SQL
- List All Views in a PostgreSQL Database with SQL
- Find PostgreSQL Tables Without a Primary Key
- List PostgreSQL Partitioned Tables with SQL
- List All Schemas in Your PostgreSQL Database
- pg_stat_database — Query PostgreSQL Database Statistics
- List PostgreSQL Roles and Their Privileges
- Scrubbing Email PII in PostgreSQL for GDPR Compliance
- List Installed Extensions in PostgreSQL
- List PostgreSQL Collations with pg_collation and ICU
- PostgreSQL Replica Identity for Logical Replication
- Monitor PostgreSQL Vacuum Progress with pg_stat_progress_vacuum
- Monitor PostgreSQL Wait Events Using pg_stat_activity
- Monitor PostgreSQL Replication Lag with pg_stat_replication
- List PostgreSQL Wait Events with the pg_wait_events View
- PostgreSQL Column-Level Permissions Audit Query
- List All PostgreSQL Triggers with Their State
- timestamptz and tzdata: Avoid Shifted PostgreSQL Timestamps
- Inspect PostgreSQL Sequences with the pg_sequences View
- List PostgreSQL Functions with pg_proc
- Query PostgreSQL Tablespace Info with pg_tablespace
- Audit PostgreSQL Authentication with pg_hba_file_rules
- List PostgreSQL Materialized Views with pg_matviews
- Audit Row-Level Security Policies with pg_policies