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

Commit eeb6f37

Browse files
committed
Add a small cache of locks owned by a resource owner in ResourceOwner.
This speeds up reassigning locks to the parent owner, when the transaction holds a lot of locks, but only a few of them belong to the current resource owner. This is particularly helps pg_dump when dumping a large number of objects. The cache can hold up to 15 locks in each resource owner. After that, the cache is marked as overflowed, and we fall back to the old method of scanning the whole local lock table. The tradeoff here is that the cache has to be scanned whenever a lock is released, so if the cache is too large, lock release becomes more expensive. 15 seems enough to cover pg_dump, and doesn't have much impact on lock release. Jeff Janes, reviewed by Amit Kapila and Heikki Linnakangas.
1 parent dfd9c11 commit eeb6f37

File tree

4 files changed

+205
-53
lines changed

4 files changed

+205
-53
lines changed

src/backend/storage/lmgr/lock.c

Lines changed: 105 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
345345
static void FinishStrongLockAcquire(void);
346346
static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
347347
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
348+
static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent);
348349
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
349350
PROCLOCK *proclock, LockMethod lockMethodTable);
350351
static void CleanUpLock(LOCK *lock, PROCLOCK *proclock,
@@ -1098,8 +1099,16 @@ SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
10981099
static void
10991100
RemoveLocalLock(LOCALLOCK *locallock)
11001101
{
1102+
int i;
1103+
1104+
for (i = locallock->numLockOwners - 1; i >= 0; i--)
1105+
{
1106+
if (locallock->lockOwners[i].owner != NULL)
1107+
ResourceOwnerForgetLock(locallock->lockOwners[i].owner, locallock);
1108+
}
11011109
pfree(locallock->lockOwners);
11021110
locallock->lockOwners = NULL;
1111+
11031112
if (locallock->holdsStrongLockCount)
11041113
{
11051114
uint32 fasthashcode;
@@ -1112,6 +1121,7 @@ RemoveLocalLock(LOCALLOCK *locallock)
11121121
locallock->holdsStrongLockCount = FALSE;
11131122
SpinLockRelease(&FastPathStrongRelationLocks->mutex);
11141123
}
1124+
11151125
if (!hash_search(LockMethodLocalHash,
11161126
(void *) &(locallock->tag),
11171127
HASH_REMOVE, NULL))
@@ -1355,6 +1365,8 @@ GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner)
13551365
lockOwners[i].owner = owner;
13561366
lockOwners[i].nLocks = 1;
13571367
locallock->numLockOwners++;
1368+
if (owner != NULL)
1369+
ResourceOwnerRememberLock(owner, locallock);
13581370
}
13591371

13601372
/*
@@ -1670,6 +1682,8 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
16701682
Assert(lockOwners[i].nLocks > 0);
16711683
if (--lockOwners[i].nLocks == 0)
16721684
{
1685+
if (owner != NULL)
1686+
ResourceOwnerForgetLock(owner, locallock);
16731687
/* compact out unused slot */
16741688
locallock->numLockOwners--;
16751689
if (i < locallock->numLockOwners)
@@ -1862,14 +1876,13 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
18621876
{
18631877
LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
18641878

1865-
/* If it's above array position 0, move it down to 0 */
1866-
for (i = locallock->numLockOwners - 1; i > 0; i--)
1879+
/* If session lock is above array position 0, move it down to 0 */
1880+
for (i = 0; i < locallock->numLockOwners ; i++)
18671881
{
18681882
if (lockOwners[i].owner == NULL)
1869-
{
18701883
lockOwners[0] = lockOwners[i];
1871-
break;
1872-
}
1884+
else
1885+
ResourceOwnerForgetLock(lockOwners[i].owner, locallock);
18731886
}
18741887

18751888
if (locallock->numLockOwners > 0 &&
@@ -1882,6 +1895,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
18821895
/* We aren't deleting this locallock, so done */
18831896
continue;
18841897
}
1898+
else
1899+
locallock->numLockOwners = 0;
18851900
}
18861901

18871902
/*
@@ -2067,18 +2082,31 @@ LockReleaseSession(LOCKMETHODID lockmethodid)
20672082
/*
20682083
* LockReleaseCurrentOwner
20692084
* Release all locks belonging to CurrentResourceOwner
2085+
*
2086+
* If the caller knows what those locks are, it can pass them as an array.
2087+
* That speeds up the call significantly, when a lot of locks are held.
2088+
* Otherwise, pass NULL for locallocks, and we'll traverse through our hash
2089+
* table to find them.
20702090
*/
20712091
void
2072-
LockReleaseCurrentOwner(void)
2092+
LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks)
20732093
{
2074-
HASH_SEQ_STATUS status;
2075-
LOCALLOCK *locallock;
2094+
if (locallocks == NULL)
2095+
{
2096+
HASH_SEQ_STATUS status;
2097+
LOCALLOCK *locallock;
20762098

2077-
hash_seq_init(&status, LockMethodLocalHash);
2099+
hash_seq_init(&status, LockMethodLocalHash);
20782100

2079-
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
2101+
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
2102+
ReleaseLockIfHeld(locallock, false);
2103+
}
2104+
else
20802105
{
2081-
ReleaseLockIfHeld(locallock, false);
2106+
int i;
2107+
2108+
for (i = nlocks - 1; i >= 0; i--)
2109+
ReleaseLockIfHeld(locallocks[i], false);
20822110
}
20832111
}
20842112

@@ -2124,6 +2152,8 @@ ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock)
21242152
locallock->nLocks -= lockOwners[i].nLocks;
21252153
/* compact out unused slot */
21262154
locallock->numLockOwners--;
2155+
if (owner != NULL)
2156+
ResourceOwnerForgetLock(owner, locallock);
21272157
if (i < locallock->numLockOwners)
21282158
lockOwners[i] = lockOwners[locallock->numLockOwners];
21292159
}
@@ -2146,57 +2176,83 @@ ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock)
21462176
/*
21472177
* LockReassignCurrentOwner
21482178
* Reassign all locks belonging to CurrentResourceOwner to belong
2149-
* to its parent resource owner
2179+
* to its parent resource owner.
2180+
*
2181+
* If the caller knows what those locks are, it can pass them as an array.
2182+
* That speeds up the call significantly, when a lot of locks are held
2183+
* (e.g pg_dump with a large schema). Otherwise, pass NULL for locallocks,
2184+
* and we'll traverse through our hash table to find them.
21502185
*/
21512186
void
2152-
LockReassignCurrentOwner(void)
2187+
LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks)
21532188
{
21542189
ResourceOwner parent = ResourceOwnerGetParent(CurrentResourceOwner);
2155-
HASH_SEQ_STATUS status;
2156-
LOCALLOCK *locallock;
2157-
LOCALLOCKOWNER *lockOwners;
21582190

21592191
Assert(parent != NULL);
21602192

2161-
hash_seq_init(&status, LockMethodLocalHash);
2193+
if (locallocks == NULL)
2194+
{
2195+
HASH_SEQ_STATUS status;
2196+
LOCALLOCK *locallock;
21622197

2163-
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
2198+
hash_seq_init(&status, LockMethodLocalHash);
2199+
2200+
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
2201+
LockReassignOwner(locallock, parent);
2202+
}
2203+
else
21642204
{
2165-
int i;
2166-
int ic = -1;
2167-
int ip = -1;
2205+
int i;
21682206

2169-
/*
2170-
* Scan to see if there are any locks belonging to current owner or
2171-
* its parent
2172-
*/
2173-
lockOwners = locallock->lockOwners;
2174-
for (i = locallock->numLockOwners - 1; i >= 0; i--)
2175-
{
2176-
if (lockOwners[i].owner == CurrentResourceOwner)
2177-
ic = i;
2178-
else if (lockOwners[i].owner == parent)
2179-
ip = i;
2180-
}
2207+
for (i = nlocks - 1; i >= 0; i--)
2208+
LockReassignOwner(locallocks[i], parent);
2209+
}
2210+
}
21812211

2182-
if (ic < 0)
2183-
continue; /* no current locks */
2212+
/*
2213+
* Subroutine of LockReassignCurrentOwner. Reassigns a given lock belonging to
2214+
* CurrentResourceOwner to its parent.
2215+
*/
2216+
static void
2217+
LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent)
2218+
{
2219+
LOCALLOCKOWNER *lockOwners;
2220+
int i;
2221+
int ic = -1;
2222+
int ip = -1;
21842223

2185-
if (ip < 0)
2186-
{
2187-
/* Parent has no slot, so just give it child's slot */
2188-
lockOwners[ic].owner = parent;
2189-
}
2190-
else
2191-
{
2192-
/* Merge child's count with parent's */
2193-
lockOwners[ip].nLocks += lockOwners[ic].nLocks;
2194-
/* compact out unused slot */
2195-
locallock->numLockOwners--;
2196-
if (ic < locallock->numLockOwners)
2197-
lockOwners[ic] = lockOwners[locallock->numLockOwners];
2198-
}
2224+
/*
2225+
* Scan to see if there are any locks belonging to current owner or
2226+
* its parent
2227+
*/
2228+
lockOwners = locallock->lockOwners;
2229+
for (i = locallock->numLockOwners - 1; i >= 0; i--)
2230+
{
2231+
if (lockOwners[i].owner == CurrentResourceOwner)
2232+
ic = i;
2233+
else if (lockOwners[i].owner == parent)
2234+
ip = i;
2235+
}
2236+
2237+
if (ic < 0)
2238+
return; /* no current locks */
2239+
2240+
if (ip < 0)
2241+
{
2242+
/* Parent has no slot, so just give it the child's slot */
2243+
lockOwners[ic].owner = parent;
2244+
ResourceOwnerRememberLock(parent, locallock);
2245+
}
2246+
else
2247+
{
2248+
/* Merge child's count with parent's */
2249+
lockOwners[ip].nLocks += lockOwners[ic].nLocks;
2250+
/* compact out unused slot */
2251+
locallock->numLockOwners--;
2252+
if (ic < locallock->numLockOwners)
2253+
lockOwners[ic] = lockOwners[locallock->numLockOwners];
21992254
}
2255+
ResourceOwnerForgetLock(CurrentResourceOwner, locallock);
22002256
}
22012257

22022258
/*

src/backend/utils/resowner/resowner.c

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@
2727
#include "utils/rel.h"
2828
#include "utils/snapmgr.h"
2929

30+
/*
31+
* To speed up bulk releasing or reassigning locks from a resource owner to
32+
* its parent, each resource owner has a small cache of locks it owns. The
33+
* lock manager has the same information in its local lock hash table, and
34+
* we fall back on that if cache overflows, but traversing the hash table
35+
* is slower when there are a lot of locks belonging to other resource owners.
36+
*
37+
* MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's
38+
* chosen based on some testing with pg_dump with a large schema. When the
39+
* tests were done (on 9.2), resource owners in a pg_dump run contained up
40+
* to 9 locks, regardless of the schema size, except for the top resource
41+
* owner which contained much more (overflowing the cache). 15 seems like a
42+
* nice round number that's somewhat higher than what pg_dump needs. Note that
43+
* making this number larger is not free - the bigger the cache, the slower
44+
* it is to release locks (in retail), when a resource owner holds many locks.
45+
*/
46+
#define MAX_RESOWNER_LOCKS 15
3047

3148
/*
3249
* ResourceOwner objects look like this
@@ -43,6 +60,10 @@ typedef struct ResourceOwnerData
4360
Buffer *buffers; /* dynamically allocated array */
4461
int maxbuffers; /* currently allocated array size */
4562

63+
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
64+
int nlocks; /* number of owned locks */
65+
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
66+
4667
/* We have built-in support for remembering catcache references */
4768
int ncatrefs; /* number of owned catcache pins */
4869
HeapTuple *catrefs; /* dynamically allocated array */
@@ -272,11 +293,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
272293
* subtransaction, we do NOT release its locks yet, but transfer
273294
* them to the parent.
274295
*/
296+
LOCALLOCK **locks;
297+
int nlocks;
298+
275299
Assert(owner->parent != NULL);
300+
301+
/*
302+
* Pass the list of locks owned by this resource owner to the lock
303+
* manager, unless it has overflowed.
304+
*/
305+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
306+
{
307+
locks = NULL;
308+
nlocks = 0;
309+
}
310+
else
311+
{
312+
locks = owner->locks;
313+
nlocks = owner->nlocks;
314+
}
315+
276316
if (isCommit)
277-
LockReassignCurrentOwner();
317+
LockReassignCurrentOwner(locks, nlocks);
278318
else
279-
LockReleaseCurrentOwner();
319+
LockReleaseCurrentOwner(locks, nlocks);
280320
}
281321
}
282322
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
@@ -357,6 +397,7 @@ ResourceOwnerDelete(ResourceOwner owner)
357397

358398
/* And it better not own any resources, either */
359399
Assert(owner->nbuffers == 0);
400+
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
360401
Assert(owner->ncatrefs == 0);
361402
Assert(owner->ncatlistrefs == 0);
362403
Assert(owner->nrelrefs == 0);
@@ -588,6 +629,56 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
588629
}
589630
}
590631

632+
/*
633+
* Remember that a Local Lock is owned by a ResourceOwner
634+
*
635+
* This is different from the other Remember functions in that the list of
636+
* locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries,
637+
* and when it overflows, we stop tracking locks. The point of only remembering
638+
* only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
639+
* ResourceOwnerForgetLock doesn't need to scan through a large array to find
640+
* the entry.
641+
*/
642+
void
643+
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK * locallock)
644+
{
645+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
646+
return; /* we have already overflowed */
647+
648+
if (owner->nlocks < MAX_RESOWNER_LOCKS)
649+
owner->locks[owner->nlocks] = locallock;
650+
else
651+
{
652+
/* overflowed */
653+
}
654+
owner->nlocks++;
655+
}
656+
657+
/*
658+
* Forget that a Local Lock is owned by a ResourceOwner
659+
*/
660+
void
661+
ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
662+
{
663+
int i;
664+
665+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
666+
return; /* we have overflowed */
667+
668+
Assert(owner->nlocks > 0);
669+
for (i = owner->nlocks - 1; i >= 0; i--)
670+
{
671+
if (locallock == owner->locks[i])
672+
{
673+
owner->locks[i] = owner->locks[owner->nlocks - 1];
674+
owner->nlocks--;
675+
return;
676+
}
677+
}
678+
elog(ERROR, "lock reference %p is not owned by resource owner %s",
679+
locallock, owner->name);
680+
}
681+
591682
/*
592683
* Make sure there is room for at least one more entry in a ResourceOwner's
593684
* catcache reference array.

src/include/storage/lock.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,8 @@ extern bool LockRelease(const LOCKTAG *locktag,
492492
LOCKMODE lockmode, bool sessionLock);
493493
extern void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks);
494494
extern void LockReleaseSession(LOCKMETHODID lockmethodid);
495-
extern void LockReleaseCurrentOwner(void);
496-
extern void LockReassignCurrentOwner(void);
495+
extern void LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks);
496+
extern void LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks);
497497
extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag,
498498
LOCKMODE lockmode);
499499
extern void AtPrepare_Locks(void);

0 commit comments

Comments
 (0)