Add a small cache of locks owned by a resource owner in ResourceOwner.
authorTom Lane <[email protected]>
Thu, 27 Aug 2015 16:22:10 +0000 (12:22 -0400)
committerTom Lane <[email protected]>
Thu, 27 Aug 2015 16:22:10 +0000 (12:22 -0400)
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.

src/backend/storage/lmgr/lock.c
src/backend/utils/resowner/resowner.c
src/include/storage/lock.h
src/include/utils/resowner.h

index cbc5ee1be35d59701867eb0281655870d271459f..65726196d00082e20e29762dd5b68f03b0bfbc68 100644 (file)
@@ -344,6 +344,7 @@ static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
 static void FinishStrongLockAcquire(void);
 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,
@@ -1205,8 +1206,16 @@ SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
 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 (locallock->holdsStrongLockCount)
    {
        uint32      fasthashcode;
@@ -1219,6 +1228,7 @@ RemoveLocalLock(LOCALLOCK *locallock)
        locallock->holdsStrongLockCount = FALSE;
        SpinLockRelease(&FastPathStrongRelationLocks->mutex);
    }
+
    if (!hash_search(LockMethodLocalHash,
                     (void *) &(locallock->tag),
                     HASH_REMOVE, NULL))
@@ -1462,6 +1472,8 @@ GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner)
    lockOwners[i].owner = owner;
    lockOwners[i].nLocks = 1;
    locallock->numLockOwners++;
+   if (owner != NULL)
+       ResourceOwnerRememberLock(owner, locallock);
 }
 
 /*
@@ -1778,6 +1790,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)
@@ -1974,14 +1988,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 &&
@@ -1994,6 +2007,8 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
                /* We aren't deleting this locallock, so done */
                continue;
            }
+           else
+               locallock->numLockOwners = 0;
        }
 
        /*
@@ -2200,18 +2215,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;
+   if (locallocks == NULL)
+   {
+       HASH_SEQ_STATUS status;
+       LOCALLOCK  *locallock;
 
-   hash_seq_init(&status, LockMethodLocalHash);
+       hash_seq_init(&status, LockMethodLocalHash);
 
-   while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+       while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
+           ReleaseLockIfHeld(locallock, false);
+   }
+   else
    {
-       ReleaseLockIfHeld(locallock, false);
+       int         i;
+
+       for (i = nlocks - 1; i >= 0; i--)
+           ReleaseLockIfHeld(locallocks[i], false);
    }
 }
 
@@ -2257,6 +2285,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];
            }
@@ -2279,57 +2309,83 @@ 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;
-       }
+       for (i = nlocks - 1; i >= 0; i--)
+           LockReassignOwner(locallocks[i], parent);
+   }
+}
 
-       if (ic < 0)
-           continue;           /* no current locks */
+/*
+ * 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;
 
-       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];
-       }
+   /*
+    * 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);
 }
 
 /*
index 50690bc38ea9126541814e518700cf2a93717341..abbef1bac3b75edcb2f45632a9856d6bc3419142 100644 (file)
 #include "utils/rel.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
@@ -43,6 +60,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 */
@@ -272,11 +293,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);
+
+           /*
+            * 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
+           {
+               locks = owner->locks;
+               nlocks = owner->nlocks;
+           }
+
            if (isCommit)
-               LockReassignCurrentOwner();
+               LockReassignCurrentOwner(locks, nlocks);
            else
-               LockReleaseCurrentOwner();
+               LockReleaseCurrentOwner(locks, nlocks);
        }
    }
    else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
@@ -357,6 +397,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);
@@ -588,6 +629,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.
index 78c3074ad27e1d5067f0c159cec1e8d229329bfa..d1ad32ad25859da8e216978cec1ec92dc2553401 100644 (file)
@@ -506,8 +506,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,
index 47eb0ac05e50af7231e3f9c1a97cfd96c09fb2c2..336d250fbbea1b544572136c56825c61204bd12f 100644 (file)
@@ -20,6 +20,7 @@
 #define RESOWNER_H
 
 #include "storage/fd.h"
+#include "storage/lock.h"
 #include "utils/catcache.h"
 #include "utils/plancache.h"
 #include "utils/snapshot.h"
@@ -89,6 +90,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,