Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8103822

Browse files
anarazelmichaelpq
authored andcommitted
lwlock: Fix quadratic behavior with very long wait lists
Until now LWLockDequeueSelf() sequentially searched the list of waiters to seeif the current proc is still is on the list of waiters, or has already beenremoved. In extreme workloads, where the wait lists are very long, this leadsto a quadratic behavior. #backends iterating over a list #backendslong. Additionally, the likelihood of needing to call LWLockDequeueSelf() inthe first place also increases with the increased length of the wait queue, asit becomes more likely that a lock is released while waiting for the wait listlock, which is held for longer during lock release.Due to the exponential back-off in perform_spin_delay() this is surprisinglyhard to detect. We should make that easier, e.g. by adding a wait event aroundthe pg_usleep() - but that's a separate patch.The fix is simple - track whether a proc is currently waiting in the wait listor already removed but waiting to be woken up in PGPROC->lwWaiting.In some workloads with a lot of clients contending for a small number oflwlocks (e.g. WALWriteLock), the fix can substantially increase throughput.This has been originally fixed for 16~ witha4adc31 without abackpatch, and we have heard complaints from users impacted by thisquadratic 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.deDiscussion:https://postgr.es/m/CALj2ACXktNbG=K8Xi7PSqbofTZozavhaxjatVc14iYaLu4Maag@mail.gmail.comBackpatch-through: 12
1 parentdb0d238 commit8103822

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
staticvoid
10281037
LWLockDequeueSelf(LWLock*lock)
10291038
{
1030-
boolfound= false;
1031-
proclist_mutable_iteriter;
1039+
boolon_waitlist;
10321040

10331041
#ifdefLWLOCK_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
intextraWaits=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
structPGPROC;
2626

27+
/* what state of the wait process is a backend in */
28+
typedefenumLWLockWaitState
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
boolrecoveryConflictPending;
135135

136136
/* Info about LWLock the process is currently waiting for, if any. */
137-
boollwWaiting;/*true if waiting for an LW lock */
137+
uint8lwWaiting;/*see LWLockWaitState */
138138
uint8lwWaitMode;/* lwlock mode being waited for */
139139
proclist_nodelwWaitLink;/* position in LW lock wait list */
140140

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp