Re: [PG19] Promotion hangs behind a backend blocked on a stuck client

From: Manu <manuelreyesbravo(at)gmail(dot)com>
To: shihao zhong <zhong950419(at)gmail(dot)com>
Cc: Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com>, Fujii Masao <masao(dot)fujii(at)gmail(dot)com>, "Hayato Kuroda (Fujitsu)" <kuroda(dot)hayato(at)fujitsu(dot)com>, Andres Freund <andres(at)anarazel(dot)de>, Thomas Munro <thomas(dot)munro(at)gmail(dot)com>, Noah Misch <noah(at)leadboat(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [PG19] Promotion hangs behind a backend blocked on a stuck client
Date: 2026-10-20 04:39:54
Message-ID: 179247119467.2410654.17218914322784616346@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Shihao,

> At the end of recovery, a stale cache can only be in a process started
> before StartupLogicalDecodingStatus(), like the checkpointer. Later ones
> read it at startup, and a replayed enable waits for everyone.

I checked that directly for the failover-slot case (slot present before the
base backup, synced by slotsync, no status change replayed): with gdb I read
the process-local XLogLogicalInfo of every standby child just before
pg_promote(), over 8 runs. The only stale ones were the checkpointer, the
background writer, the two io workers and the startup process; every client
backend (including the one blocking promotion), the walreceiver and the
slotsync worker already had it set. So at end of recovery the barrier only
has to reach those auxiliaries, and the backend that blocks promotion is
already up to date. (I did not check whether any of those auxiliaries writes
WAL that needs the updated status.)

> It does not help when replaying the enable record has to wait, or
> when the end of recovery itself turns the status on. For master I
> agree (2) is better, and it helps DROP DATABASE too.

To check that (2) really clears these cases, I wrote a small,
measurement-only prototype of Sawada-san's idea on master (0497318e442):
in WaitForProcSignalBarrier(), after a fixed grace, a client backend or
walsender that still has not absorbed the barrier gets SIGTERM, as
pg_terminate_backend() does. It is not a patch proposal; the direction is
Sawada-san's and yours, and the diff is yours to use or ignore. (I built
your v5-0001 on the same base to compare the numbers below.)

Each case had one backend stuck in COPY TO STDOUT to a client that
stopped reading, a 2 s grace, and 3 runs:

- promotion, logical decoding enabled since startup: master hung, the
prototype promoted in ~2.02 s, v5 in 7-22 ms
- promotion after an enable record was replayed: the same
- replay of the enable record with the backend already stuck: master
and v5 stayed stopped for the whole 15 s, the prototype caught up in
~2.03 s
- pg_enable_data_checksums() on a primary: master was still
"inprogress-on" after 15 s, the prototype reached "on" in ~2.1 s
- DROP DATABASE with the backend connected to another database: master
hung for 20 s, the prototype dropped it in ~2.01 s

The stuck backend always ended with "terminating connection due to
administrator command". With a 5 s grace the times became ~5.0-5.1 s,
and the termination never came before the grace. As a negative control
I ran five idle sessions, one in pg_sleep() and one slow reader
(256 KB/s) through promotion, checksum enabling and DROP DATABASE: all
9 runs finished in 10-111 ms, and none of the 63 sessions was
terminated.

Two findings that may matter for the design:

- The recovery-conflict signal, as it is, does not reach such a backend.
The same prototype sending SignalRecoveryConflict() instead of SIGTERM
released none of them (0/12 across promotion, replay, checksums and
DROP DATABASE), since ProcessClientWriteInterrupt() acts only on
ProcDiePending. The same is true today for real conflicts: with
max_standby_streaming_delay = 1s, a snapshot conflict against that
backend kept replay stopped for the whole 15 s (3/3); the startup
process named it as the conflicting process, and confl_snapshot
stayed 0.

- For DROP DATABASE, a stuck backend connected to the target database
is not a barrier wait: a plain DROP fails after 5 s with "is being
accessed by other users", and WITH (FORCE) already works on master
(~115 ms). The barrier case is a backend on another database, as
Sawada-san said.

I can share the scripts, the diff and the per-run numbers if that is useful.

Regards,
Manu

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Tom Lane 2026-10-20 04:46:43 Re: [PATCH] initdb: map Windows' new Canadian time zone names
Previous Message Amit Kapila 2026-10-20 04:32:24 Re: [PG19] Sequence sync stops when the subscription owner can't SET ROLE to a sequence owner