Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8103822

Browse files
anarazelmichaelpq
authored andcommitted
lwlock: Fix quadratic behavior with very long wait lists
Until now LWLockDequeueSelf() sequentially searched the list of waiters to see if the current proc is still is on the list of waiters, or has already been removed. In extreme workloads, where the wait lists are very long, this leads to a quadratic behavior. #backends iterating over a list #backends long. Additionally, the likelihood of needing to call LWLockDequeueSelf() in the first place also increases with the increased length of the wait queue, as it becomes more likely that a lock is released while waiting for the wait list lock, which is held for longer during lock release. Due to the exponential back-off in perform_spin_delay() this is surprisingly hard to detect. We should make that easier, e.g. by adding a wait event around the pg_usleep() - but that's a separate patch. The fix is simple - track whether a proc is currently waiting in the wait list or already removed but waiting to be woken up in PGPROC->lwWaiting. In some workloads with a lot of clients contending for a small number of lwlocks (e.g. WALWriteLock), the fix can substantially increase throughput. This has been originally fixed for 16~ with a4adc31 without a backpatch, and we have heard complaints from users impacted by this quadratic behavior in older versions as well. Author: Andres Freund <andres@anarazel.de> Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> Discussion: https://postgr.es/m/20221027165914.2hofzp4cvutj6gin@awork3.anarazel.de Discussion: https://postgr.es/m/CALj2ACXktNbG=K8Xi7PSqbofTZozavhaxjatVc14iYaLu4Maag@mail.gmail.com Backpatch-through: 12
1 parent db0d238 commit 8103822

File tree

5 files changed

+42
-27
lines changed

5 files changed

+42
-27
lines changed

src/backend/access/transam/twophase.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ MarkAsPreparingGuts(GlobalTransaction gxact, TransactionId xid, const char *gid,
485485
proc->roleId = owner;
486486
proc->tempNamespaceId = InvalidOid;
487487
proc->isBackgroundWorker = false;
488-
proc->lwWaiting = false;
488+
proc->lwWaiting = LW_WS_NOT_WAITING;
489489
proc->lwWaitMode = 0;
490490
proc->waitLock = NULL;
491491
proc->waitProcLock = NULL;

src/backend/storage/lmgr/lwlock.c

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,15 @@ LWLockWakeup(LWLock *lock)
913913
wokeup_somebody = true;
914914
}
915915

916+
/*
917+
* Signal that the process isn't on the wait list anymore. This allows
918+
* LWLockDequeueSelf() to remove itself of the waitlist with a
919+
* proclist_delete(), rather than having to check if it has been
920+
* removed from the list.
921+
*/
922+
Assert(waiter->lwWaiting == LW_WS_WAITING);
923+
waiter->lwWaiting = LW_WS_PENDING_WAKEUP;
924+
916925
/*
917926
* Once we've woken up an exclusive lock, there's no point in waking
918927
* up anybody else.
@@ -970,7 +979,7 @@ LWLockWakeup(LWLock *lock)
970979
* another lock.
971980
*/
972981
pg_write_barrier();
973-
waiter->lwWaiting = false;
982+
waiter->lwWaiting = LW_WS_NOT_WAITING;
974983
PGSemaphoreUnlock(waiter->sem);
975984
}
976985
}
@@ -991,15 +1000,15 @@ LWLockQueueSelf(LWLock *lock, LWLockMode mode)
9911000
if (MyProc == NULL)
9921001
elog(PANIC, "cannot wait without a PGPROC structure");
9931002

994-
if (MyProc->lwWaiting)
1003+
if (MyProc->lwWaiting != LW_WS_NOT_WAITING)
9951004
elog(PANIC, "queueing for lock while waiting on another one");
9961005

9971006
LWLockWaitListLock(lock);
9981007

9991008
/* setting the flag is protected by the spinlock */
10001009
pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_HAS_WAITERS);
10011010

1002-
MyProc->lwWaiting = true;
1011+
MyProc->lwWaiting = LW_WS_WAITING;
10031012
MyProc->lwWaitMode = mode;
10041013

10051014
/* LW_WAIT_UNTIL_FREE waiters are always at the front of the queue */
@@ -1027,8 +1036,7 @@ LWLockQueueSelf(LWLock *lock, LWLockMode mode)
10271036
static void
10281037
LWLockDequeueSelf(LWLock *lock)
10291038
{
1030-
bool found = false;
1031-
proclist_mutable_iter iter;
1039+
bool on_waitlist;
10321040

10331041
#ifdef LWLOCK_STATS
10341042
lwlock_stats *lwstats;
@@ -1041,18 +1049,13 @@ LWLockDequeueSelf(LWLock *lock)
10411049
LWLockWaitListLock(lock);
10421050

10431051
/*
1044-
* Can't just remove ourselves from the list, but we need to iterate over
1045-
* all entries as somebody else could have dequeued us.
1052+
* Remove ourselves from the waitlist, unless we've already been
1053+
* removed. The removal happens with the wait list lock held, so there's
1054+
* no race in this check.
10461055
*/
1047-
proclist_foreach_modify(iter, &lock->waiters, lwWaitLink)
1048-
{
1049-
if (iter.cur == MyProc->pgprocno)
1050-
{
1051-
found = true;
1052-
proclist_delete(&lock->waiters, iter.cur, lwWaitLink);
1053-
break;
1054-
}
1055-
}
1056+
on_waitlist = MyProc->lwWaiting == LW_WS_WAITING;
1057+
if (on_waitlist)
1058+
proclist_delete(&lock->waiters, MyProc->pgprocno, lwWaitLink);
10561059

10571060
if (proclist_is_empty(&lock->waiters) &&
10581061
(pg_atomic_read_u32(&lock->state) & LW_FLAG_HAS_WAITERS) != 0)
@@ -1064,8 +1067,8 @@ LWLockDequeueSelf(LWLock *lock)
10641067
LWLockWaitListUnlock(lock);
10651068

10661069
/* clear waiting state again, nice for debugging */
1067-
if (found)
1068-
MyProc->lwWaiting = false;
1070+
if (on_waitlist)
1071+
MyProc->lwWaiting = LW_WS_NOT_WAITING;
10691072
else
10701073
{
10711074
int extraWaits = 0;
@@ -1089,7 +1092,7 @@ LWLockDequeueSelf(LWLock *lock)
10891092
for (;;)
10901093
{
10911094
PGSemaphoreLock(MyProc->sem);
1092-
if (!MyProc->lwWaiting)
1095+
if (MyProc->lwWaiting == LW_WS_NOT_WAITING)
10931096
break;
10941097
extraWaits++;
10951098
}
@@ -1239,7 +1242,7 @@ LWLockAcquire(LWLock *lock, LWLockMode mode)
12391242
for (;;)
12401243
{
12411244
PGSemaphoreLock(proc->sem);
1242-
if (!proc->lwWaiting)
1245+
if (proc->lwWaiting == LW_WS_NOT_WAITING)
12431246
break;
12441247
extraWaits++;
12451248
}
@@ -1399,7 +1402,7 @@ LWLockAcquireOrWait(LWLock *lock, LWLockMode mode)
13991402
for (;;)
14001403
{
14011404
PGSemaphoreLock(proc->sem);
1402-
if (!proc->lwWaiting)
1405+
if (proc->lwWaiting == LW_WS_NOT_WAITING)
14031406
break;
14041407
extraWaits++;
14051408
}
@@ -1611,7 +1614,7 @@ LWLockWaitForVar(LWLock *lock, uint64 *valptr, uint64 oldval, uint64 *newval)
16111614
for (;;)
16121615
{
16131616
PGSemaphoreLock(proc->sem);
1614-
if (!proc->lwWaiting)
1617+
if (proc->lwWaiting == LW_WS_NOT_WAITING)
16151618
break;
16161619
extraWaits++;
16171620
}
@@ -1690,6 +1693,10 @@ LWLockUpdateVar(LWLock *lock, uint64 *valptr, uint64 val)
16901693

16911694
proclist_delete(&lock->waiters, iter.cur, lwWaitLink);
16921695
proclist_push_tail(&wakeup, iter.cur, lwWaitLink);
1696+
1697+
/* see LWLockWakeup() */
1698+
Assert(waiter->lwWaiting == LW_WS_WAITING);
1699+
waiter->lwWaiting = LW_WS_PENDING_WAKEUP;
16931700
}
16941701

16951702
/* We are done updating shared state of the lock itself. */
@@ -1705,7 +1712,7 @@ LWLockUpdateVar(LWLock *lock, uint64 *valptr, uint64 val)
17051712
proclist_delete(&wakeup, iter.cur, lwWaitLink);
17061713
/* check comment in LWLockWakeup() about this barrier */
17071714
pg_write_barrier();
1708-
waiter->lwWaiting = false;
1715+
waiter->lwWaiting = LW_WS_NOT_WAITING;
17091716
PGSemaphoreUnlock(waiter->sem);
17101717
}
17111718
}

src/backend/storage/lmgr/proc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ InitProcess(void)
403403
/* NB -- autovac launcher intentionally does not set IS_AUTOVACUUM */
404404
if (IsAutoVacuumWorkerProcess())
405405
MyPgXact->vacuumFlags |= PROC_IS_AUTOVACUUM;
406-
MyProc->lwWaiting = false;
406+
MyProc->lwWaiting = LW_WS_NOT_WAITING;
407407
MyProc->lwWaitMode = 0;
408408
MyProc->waitLock = NULL;
409409
MyProc->waitProcLock = NULL;
@@ -583,7 +583,7 @@ InitAuxiliaryProcess(void)
583583
MyPgXact->delayChkpt = false;
584584
MyProc->delayChkptEnd = false;
585585
MyPgXact->vacuumFlags = 0;
586-
MyProc->lwWaiting = false;
586+
MyProc->lwWaiting = LW_WS_NOT_WAITING;
587587
MyProc->lwWaitMode = 0;
588588
MyProc->waitLock = NULL;
589589
MyProc->waitProcLock = NULL;

src/include/storage/lwlock.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424

2525
struct PGPROC;
2626

27+
/* what state of the wait process is a backend in */
28+
typedef enum LWLockWaitState
29+
{
30+
LW_WS_NOT_WAITING, /* not currently waiting / woken up */
31+
LW_WS_WAITING, /* currently waiting */
32+
LW_WS_PENDING_WAKEUP /* removed from waitlist, but not yet signalled */
33+
} LWLockWaitState;
34+
2735
/*
2836
* Code outside of lwlock.c should not manipulate the contents of this
2937
* structure directly, but we have to declare it here to allow LWLocks to be

src/include/storage/proc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ struct PGPROC
134134
bool recoveryConflictPending;
135135

136136
/* Info about LWLock the process is currently waiting for, if any. */
137-
bool lwWaiting; /* true if waiting for an LW lock */
137+
uint8 lwWaiting; /* see LWLockWaitState */
138138
uint8 lwWaitMode; /* lwlock mode being waited for */
139139
proclist_node lwWaitLink; /* position in LW lock wait list */
140140

0 commit comments

Comments
 (0)