Re: Add CHECK_FOR_INTERRUPTS in polling loop code path in XactLockTableWait - Mailing list pgsql-hackers

From Fujii Masao
Subject Re: Add CHECK_FOR_INTERRUPTS in polling loop code path in XactLockTableWait
Date
Msg-id 422ea29f-2051-441c-aa5d-5eea04a81b95@oss.nttdata.com
Whole thread Raw
Responses Re: Add CHECK_FOR_INTERRUPTS in polling loop code path in XactLockTableWait
List pgsql-hackers

On 2025/05/24 5:41, Kevin K Biju wrote:
> Hi,
> 
> While creating a logical replication slot, we wait for older transactions to complete to reach a "consistent point",
whichcan take a while on busy databases. If we're creating a slot on a primary instance, it's pretty clear that we're
waitingon a transaction:
 
> 
> 
> postgres=# SELECT pid,wait_event_type,wait_event,state,query FROM pg_stat_activity WHERE pid=804;
>   pid | wait_event_type |  wait_event   | state  |                             query
> -----+-----------------+---------------+--------+----------------------------------------------------------------
>   804 | Lock            | transactionid | active | SELECT pg_create_logical_replication_slot('wow1', 'pgoutput');
> (1 row)
> postgres=# SELECT locktype,transactionid,pid,mode FROM pg_locks WHERE pid=804 AND granted='f';
>     locktype    | transactionid | pid |   mode
> ---------------+---------------+-----+-----------
>   transactionid |          6077 | 804 | ShareLock
> 
> However, creating a slot on a hot standby behaves very differently when blocked on a transaction. Querying
pg_stat_activitydoesn't give us any indication on what the issue is:
 
> 
> 
> postgres=# SELECT pg_is_in_recovery();
>   pg_is_in_recovery
> -------------------
>   t
> (1 row)
> postgres=# SELECT pid,wait_event_type,wait_event,state,query FROM pg_stat_activity WHERE pid=5074;
>   pid  | wait_event_type | wait_event | state  |                             query
> ------+-----------------+------------+--------+----------------------------------------------------------------
>   5074 |                 |            | active | SELECT pg_create_logical_replication_slot('wow1', 'pgoutput');
> 
> And more importantly, a backend in this state cannot be terminated via the usual methods and sometimes requires a
serverrestart.
 
> 
> 
> postgres=# SELECT pg_cancel_backend(5074), pg_terminate_backend(5074);
>   pg_cancel_backend | pg_terminate_backend
> -------------------+----------------------
>   t                 | t
> (1 row)
> postgres=# SELECT pid,wait_event_type,wait_event,state,query FROM pg_stat_activity WHERE pid=5074;
>   pid  | wait_event_type | wait_event | state  |                             query
> ------+-----------------+------------+--------+----------------------------------------------------------------
>   5074 |                 |            | active | SELECT pg_create_logical_replication_slot('wow1', 'pgoutput');
> 
> I've taken a look at the code that "awaits" on transactions and the function of interest is lmgr.c/XactLockTableWait.
Ona primary, it is able to acquire a ShareLock on the xid and the lock manager does a good job on making this wait
efficientas well as visible externally. However, on a standby, it cannot wait on the xid since it is not running the
transaction.However, it knows the transaction is still running from KnownAssignedXids, and then ends up on this
codepath:
> 
> *
> * Some uses of this function don't involve tuple visibility -- such
> * as when building snapshots for logical decoding.  It is possible to
> * see a transaction in ProcArray before it registers itself in the
> * locktable.  The topmost transaction in that case is the same xid,
> * so we try again after a short sleep.  (Don't sleep the first time
> * through, to avoid slowing down the normal case.)
> */
> if (!first)
> pg_usleep(1000L);
> 
> The attached comment suggests that this sleep was only meant to be hit a few times while we wait for the lock to
appearso we can wait on it. However, in the hot standby case, this ends up becoming a polling loop since the lock will
neverappear. There is no interrupt processing in this loop either and so the only way out is to wait for the
transactionon the primary to complete.
 

I agree with your analysis. It makes sense to me.


> Attached is a patch that adds CHECK_FOR_INTERRUPTS before the sleep in XactLockTableWait
and ConditionalXactLockTableWait.This allows backends waiting on a transaction during slot creation on a standby to be
interrupted.

+1

> It would also be nice if there was a way for users to tell what we're waiting on (maybe a different wait event?) but
I'dlike input on that.
 

Just an idea: how about calling pgstat_report_wait_start() and _end() around
pg_usleep(), similar to what WaitExceedsMaxStandbyDelay() does?
Since this is more of an improvement than a bug fix, I think
it would be better to make it as a separate patch from the CFI addition.


Also, would waiting only 1ms per loop cycle be too aggressive? If so, maybe
we could increase the sleep time gradually during recovery (but not beyond
1 second), again similar to WaitExceedsMaxStandbyDelay().

Regards,

-- 
Fujii Masao
NTT DATA Japan Corporation




pgsql-hackers by date:

Previous
From: Srinath Reddy Sadipiralla
Date:
Subject: Custom GUCs and typos
Next
From: jian he
Date:
Subject: CREATE OR REPLACE FUNCTION now validates it's dependents