Skip to main content
RunBook Academy

PostgreSQLIX · Locks, Blocking and DeadlocksLocks

Cancel or terminate

Intermediate⏱ ~30 minpsql

What you'll learn

  • Choose between cancel and terminate from the target session state
  • Predict what each verb releases and what it leaves behind
  • Decide whether killing a given session is safe before doing it
  • Apply timeouts so that manual intervention is rarely needed

Prerequisites

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

Two functions, and the choice between them is made under time pressure with incomplete information. The commonly repeated summary — “cancel stops the query, terminate kills the connection” — is true and does not tell you what you need to know, which is what each one releases.

That was measured rather than assumed, and one of the results contradicts the folklore.

Case 1: the session is running a statement

pg_cancel_backend(pid) sends a signal asking the backend to stop what it is doing. The statement ends with an error.

Service impact possiblecancelling a session that is mid-statement inside a transaction
$ psql -U postgres -At -c "SELECT pg_cancel_backend(1876)"
-- client output of the cancelled session:
BEGIN
UPDATE 1
ERROR:  canceling statement due to user request

-- its state five seconds later:
pid  |             state             | backend_xid | xmin_age
------+-------------------------------+-------------+----------
1876 | idle in transaction (aborted) |             |

-- locks it still holds on the table:
(0 rows)

Read the last two blocks carefully, because this is the part that is usually stated wrongly.

The cancel aborted the transaction. backend_xid is NULL, backend_xmin is NULL, and no relation locks remain. The session is in idle in transaction (aborted) — it exists, it holds a connection slot, and it holds nothing else.

Verified again with a session holding a REPEATABLE READ snapshot:

Read-only / Safethe same test against a held snapshot
$ psql -U postgres -c "SELECT pid, state, age(backend_xmin) AS xmin_age FROM pg_stat_activity WHERE pid=1913"
before cancel:  1913 | active                        | xmin_age 0
after cancel:   1913 | idle in transaction (aborted) | xmin_age NULL

So cancelling an active session does release its hold on the vacuum horizon. If the goal is to free locks or unblock vacuum, and the session is running something, cancel is sufficient.

Case 2: the session is idle in transaction

This is the case that matters, because it is the one causing the damage in lesson VII-05.

Service impact possiblecancelling a session that is idle in transaction
$ psql -U postgres -At -c "SELECT pg_cancel_backend(1950)"
before:                        1950 | idle in transaction | xmin_age 0
pg_cancel_backend returns:     t
after cancel:                  1950 | idle in transaction | xmin_age 0

The cancel did nothing. It returned true, because the signal was delivered successfully — but there was no statement to cancel, so the session is unchanged: same state, same snapshot, same locks.

pg_terminate_backend is the only verb that works here.

Destructiveterminating the same session
$ psql -U postgres -At -c "SELECT pg_terminate_backend(1950)"
-- returns: t

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

-- afterwards:
SELECT count(*) FROM pg_stat_activity WHERE pid=1950;  -->  0

Deciding whether it is safe

Before either verb, three questions.

What is it? Read application_name, usename, client_addr and query. A session from the analytics host running a long aggregate is a different decision from one belonging to the payment service.

SELECT pid, usename, application_name, client_addr, state,
       now() - xact_start   AS xact_age,
       now() - state_change AS in_state_for,
       backend_type,
       left(query, 100)     AS query
  FROM pg_stat_activity
 WHERE pid = 12345;

What will be lost? An active session in a transaction loses everything since BEGIN. If it is a four-hour data migration, that is four hours. If it is idle in transaction, it is doing nothing and losing nothing matters less.

Will it come back? Many clients reconnect and retry immediately. If the session is a runaway query from a service that will re-issue it, you have bought seconds. Fix the source, or the loop continues.

Bulk termination

Occasionally you need to clear a class of sessions rather than one.

-- everything idle in transaction for over ten minutes
SELECT pid, application_name, now() - state_change AS idle_for,
       pg_terminate_backend(pid)
  FROM pg_stat_activity
 WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')
   AND now() - state_change > interval '10 minutes'
   AND backend_type = 'client backend'
   AND pid <> pg_backend_pid();

Three guards in that query are not optional: backend_type excludes background processes, pid <> pg_backend_pid() stops it terminating itself partway through, and the state filter restricts it to sessions that are doing nothing.

Run the SELECT without the pg_terminate_backend call first. Look at what it returns. Then add the call.

What to take from this

  • Check state before choosing the verb. It determines which one works.
  • Cancel on an active session aborts the transaction and releases its locks and snapshot.
  • Cancel on an idle in transaction session returns true and does nothing. Terminate is the only option there.
  • Never kill -9 a backend; the postmaster restarts the cluster.
  • Anti-wraparound vacuum, concurrent index builds and backups deserve thought before termination.
  • Doing this by hand regularly means a timeout is missing.

Cross-course references

  • Linux for Production Sysadmins — Part VI (Processes) covers the signal semantics underneath pg_cancel_backend and pg_terminate_backend, and why kill -9 is a different act entirely.
  • Observability for Production Sysadmins — Part CIX (Incident investigation workflows) covers recording what was cancelled and why, which is the part most often skipped under pressure.

Quiz

Knowledge check · 6 questions

  1. Q1. A session is idle in transaction and has been holding the vacuum horizon for two hours. An operator runs pg_cancel_backend against it and the function returns true. What has changed?

  2. Q2. Why should kill -9 never be used against a PostgreSQL backend process?

  3. Q3. An operator needs to cancel and terminate other users' sessions during incidents but should not be a superuser. What is the correct grant?

  4. Q4. Which sessions deserve deliberate thought before being terminated? Select all that apply.

  5. Q5. Cancelling an active session that holds a REPEATABLE READ snapshot releases that snapshot, so vacuum is no longer held back by it.

  6. Q6. You have identified the root of a blocking chain. Describe how you would decide what to do with it.

Passing score: 75%. Answers are checked in this browser.