Add a small cache of locks owned by a resource owner in ResourceOwner.

Back-patch 9.3-era commit eeb6f37d89fc60c6449ca12ef9e91491069369cb, to
improve the older branches' ability to cope with pg_dump dumping a large
number of tables.

I back-patched into 9.2 and 9.1, but not 9.0 as it would have required a
significant amount of refactoring, thus negating the argument that this
is by-now-well-tested code.

Jeff Janes, reviewed by Amit Kapila and Heikki Linnakangas.
This commit is contained in:
Tom Lane 2015-08-27 12:22:10 -04:00
parent 31934dd3dd
commit 9b1b9446f5
4 changed files with 207 additions and 57 deletions

View File

@ -257,6 +257,7 @@ static void RemoveLocalLock(LOCALLOCK *locallock);
static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static void LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable);
static void CleanUpLock(LOCK *lock, PROCLOCK *proclock,
@ -996,6 +997,13 @@ LockAcquireExtended(const LOCKTAG *locktag,
static void
RemoveLocalLock(LOCALLOCK *locallock)
{
int i;
for (i = locallock->numLockOwners - 1; i >= 0; i--)
{
if (locallock->lockOwners[i].owner != NULL)
ResourceOwnerForgetLock(locallock->lockOwners[i].owner, locallock);
}
pfree(locallock->lockOwners);
locallock->lockOwners = NULL;
if (!hash_search(LockMethodLocalHash,
@ -1241,6 +1249,8 @@ GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner)
lockOwners[i].owner = owner;
lockOwners[i].nLocks = 1;
locallock->numLockOwners++;
if (owner != NULL)
ResourceOwnerRememberLock(owner, locallock);
}
/*
@ -1498,6 +1508,8 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
Assert(lockOwners[i].nLocks > 0);
if (--lockOwners[i].nLocks == 0)
{
if (owner != NULL)
ResourceOwnerForgetLock(owner, locallock);
/* compact out unused slot */
locallock->numLockOwners--;
if (i < locallock->numLockOwners)
@ -1636,14 +1648,13 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
{
LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
/* If it's above array position 0, move it down to 0 */
for (i = locallock->numLockOwners - 1; i > 0; i--)
/* If session lock is above array position 0, move it down to 0 */
for (i = 0; i < locallock->numLockOwners; i++)
{
if (lockOwners[i].owner == NULL)
{
lockOwners[0] = lockOwners[i];
break;
}
else
ResourceOwnerForgetLock(lockOwners[i].owner, locallock);
}
if (locallock->numLockOwners > 0 &&
@ -1656,6 +1667,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
/* We aren't deleting this locallock, so done */
continue;
}
else
locallock->numLockOwners = 0;
}
/* Mark the proclock to show we need to release this lockmode */
@ -1785,18 +1798,31 @@ LockReleaseSession(LOCKMETHODID lockmethodid)
/*
* LockReleaseCurrentOwner
* Release all locks belonging to CurrentResourceOwner
*
* If the caller knows what those locks are, it can pass them as an array.
* That speeds up the call significantly, when a lot of locks are held.
* Otherwise, pass NULL for locallocks, and we'll traverse through our hash
* table to find them.
*/
void
LockReleaseCurrentOwner(void)
LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks)
{
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
hash_seq_init(&status, LockMethodLocalHash);
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
if (locallocks == NULL)
{
ReleaseLockIfHeld(locallock, false);
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
hash_seq_init(&status, LockMethodLocalHash);
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
ReleaseLockIfHeld(locallock, false);
}
else
{
int i;
for (i = nlocks - 1; i >= 0; i--)
ReleaseLockIfHeld(locallocks[i], false);
}
}
@ -1842,6 +1868,8 @@ ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock)
locallock->nLocks -= lockOwners[i].nLocks;
/* compact out unused slot */
locallock->numLockOwners--;
if (owner != NULL)
ResourceOwnerForgetLock(owner, locallock);
if (i < locallock->numLockOwners)
lockOwners[i] = lockOwners[locallock->numLockOwners];
}
@ -1864,59 +1892,85 @@ ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock)
/*
* LockReassignCurrentOwner
* Reassign all locks belonging to CurrentResourceOwner to belong
* to its parent resource owner
* to its parent resource owner.
*
* If the caller knows what those locks are, it can pass them as an array.
* That speeds up the call significantly, when a lot of locks are held
* (e.g pg_dump with a large schema). Otherwise, pass NULL for locallocks,
* and we'll traverse through our hash table to find them.
*/
void
LockReassignCurrentOwner(void)
LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks)
{
ResourceOwner parent = ResourceOwnerGetParent(CurrentResourceOwner);
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
LOCALLOCKOWNER *lockOwners;
Assert(parent != NULL);
hash_seq_init(&status, LockMethodLocalHash);
if (locallocks == NULL)
{
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
hash_seq_init(&status, LockMethodLocalHash);
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
LockReassignOwner(locallock, parent);
}
else
{
int i;
int ic = -1;
int ip = -1;
/*
* Scan to see if there are any locks belonging to current owner or
* its parent
*/
lockOwners = locallock->lockOwners;
for (i = locallock->numLockOwners - 1; i >= 0; i--)
{
if (lockOwners[i].owner == CurrentResourceOwner)
ic = i;
else if (lockOwners[i].owner == parent)
ip = i;
}
if (ic < 0)
continue; /* no current locks */
if (ip < 0)
{
/* Parent has no slot, so just give it child's slot */
lockOwners[ic].owner = parent;
}
else
{
/* Merge child's count with parent's */
lockOwners[ip].nLocks += lockOwners[ic].nLocks;
/* compact out unused slot */
locallock->numLockOwners--;
if (ic < locallock->numLockOwners)
lockOwners[ic] = lockOwners[locallock->numLockOwners];
}
for (i = nlocks - 1; i >= 0; i--)
LockReassignOwner(locallocks[i], parent);
}
}
/*
* Subroutine of LockReassignCurrentOwner. Reassigns a given lock belonging to
* CurrentResourceOwner to its parent.
*/
static void
LockReassignOwner(LOCALLOCK *locallock, ResourceOwner parent)
{
LOCALLOCKOWNER *lockOwners;
int i;
int ic = -1;
int ip = -1;
/*
* Scan to see if there are any locks belonging to current owner or its
* parent
*/
lockOwners = locallock->lockOwners;
for (i = locallock->numLockOwners - 1; i >= 0; i--)
{
if (lockOwners[i].owner == CurrentResourceOwner)
ic = i;
else if (lockOwners[i].owner == parent)
ip = i;
}
if (ic < 0)
return; /* no current locks */
if (ip < 0)
{
/* Parent has no slot, so just give it the child's slot */
lockOwners[ic].owner = parent;
ResourceOwnerRememberLock(parent, locallock);
}
else
{
/* Merge child's count with parent's */
lockOwners[ip].nLocks += lockOwners[ic].nLocks;
/* compact out unused slot */
locallock->numLockOwners--;
if (ic < locallock->numLockOwners)
lockOwners[ic] = lockOwners[locallock->numLockOwners];
}
ResourceOwnerForgetLock(CurrentResourceOwner, locallock);
}
/*
* GetLockConflicts

View File

@ -29,6 +29,23 @@
#include "utils/resowner.h"
#include "utils/snapmgr.h"
/*
* To speed up bulk releasing or reassigning locks from a resource owner to
* its parent, each resource owner has a small cache of locks it owns. The
* lock manager has the same information in its local lock hash table, and
* we fall back on that if cache overflows, but traversing the hash table
* is slower when there are a lot of locks belonging to other resource owners.
*
* MAX_RESOWNER_LOCKS is the size of the per-resource owner cache. It's
* chosen based on some testing with pg_dump with a large schema. When the
* tests were done (on 9.2), resource owners in a pg_dump run contained up
* to 9 locks, regardless of the schema size, except for the top resource
* owner which contained much more (overflowing the cache). 15 seems like a
* nice round number that's somewhat higher than what pg_dump needs. Note that
* making this number larger is not free - the bigger the cache, the slower
* it is to release locks (in retail), when a resource owner holds many locks.
*/
#define MAX_RESOWNER_LOCKS 15
/*
* ResourceOwner objects look like this
@ -45,6 +62,10 @@ typedef struct ResourceOwnerData
Buffer *buffers; /* dynamically allocated array */
int maxbuffers; /* currently allocated array size */
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
int nlocks; /* number of owned locks */
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
/* We have built-in support for remembering catcache references */
int ncatrefs; /* number of owned catcache pins */
HeapTuple *catrefs; /* dynamically allocated array */
@ -274,11 +295,30 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* subtransaction, we do NOT release its locks yet, but transfer
* them to the parent.
*/
LOCALLOCK **locks;
int nlocks;
Assert(owner->parent != NULL);
if (isCommit)
LockReassignCurrentOwner();
/*
* Pass the list of locks owned by this resource owner to the lock
* manager, unless it has overflowed.
*/
if (owner->nlocks > MAX_RESOWNER_LOCKS)
{
locks = NULL;
nlocks = 0;
}
else
LockReleaseCurrentOwner();
{
locks = owner->locks;
nlocks = owner->nlocks;
}
if (isCommit)
LockReassignCurrentOwner(locks, nlocks);
else
LockReleaseCurrentOwner(locks, nlocks);
}
}
else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
@ -359,6 +399,7 @@ ResourceOwnerDelete(ResourceOwner owner)
/* And it better not own any resources, either */
Assert(owner->nbuffers == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
Assert(owner->ncatrefs == 0);
Assert(owner->ncatlistrefs == 0);
Assert(owner->nrelrefs == 0);
@ -590,6 +631,56 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
}
}
/*
* Remember that a Local Lock is owned by a ResourceOwner
*
* This is different from the other Remember functions in that the list of
* locks is only a lossy cache. It can hold up to MAX_RESOWNER_LOCKS entries,
* and when it overflows, we stop tracking locks. The point of only remembering
* only up to MAX_RESOWNER_LOCKS entries is that if a lot of locks are held,
* ResourceOwnerForgetLock doesn't need to scan through a large array to find
* the entry.
*/
void
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
{
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have already overflowed */
if (owner->nlocks < MAX_RESOWNER_LOCKS)
owner->locks[owner->nlocks] = locallock;
else
{
/* overflowed */
}
owner->nlocks++;
}
/*
* Forget that a Local Lock is owned by a ResourceOwner
*/
void
ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
{
int i;
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have overflowed */
Assert(owner->nlocks > 0);
for (i = owner->nlocks - 1; i >= 0; i--)
{
if (locallock == owner->locks[i])
{
owner->locks[i] = owner->locks[owner->nlocks - 1];
owner->nlocks--;
return;
}
}
elog(ERROR, "lock reference %p is not owned by resource owner %s",
locallock, owner->name);
}
/*
* Make sure there is room for at least one more entry in a ResourceOwner's
* catcache reference array.

View File

@ -488,8 +488,8 @@ extern bool LockRelease(const LOCKTAG *locktag,
LOCKMODE lockmode, bool sessionLock);
extern void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks);
extern void LockReleaseSession(LOCKMETHODID lockmethodid);
extern void LockReleaseCurrentOwner(void);
extern void LockReassignCurrentOwner(void);
extern void LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks);
extern void LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks);
extern bool LockHasWaiters(const LOCKTAG *locktag,
LOCKMODE lockmode, bool sessionLock);
extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag,

View File

@ -21,6 +21,7 @@
#include "storage/buf.h"
#include "storage/fd.h"
#include "storage/lock.h"
#include "utils/catcache.h"
#include "utils/plancache.h"
#include "utils/snapshot.h"
@ -90,6 +91,10 @@ extern void ResourceOwnerEnlargeBuffers(ResourceOwner owner);
extern void ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer);
extern void ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer);
/* support for local lock management */
extern void ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock);
extern void ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock);
/* support for catcache refcount management */
extern void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner);
extern void ResourceOwnerRememberCatCacheRef(ResourceOwner owner,