]> granicus.if.org Git - postgresql/commitdiff
Add a small cache of locks owned by a resource owner in ResourceOwner.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 27 Aug 2015 16:22:10 +0000 (12:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
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 eb3e5bdf25381416fcc4ea2597ca006974fb887e..5269b2d10ead09bc4d1386fac5c01f5a5df6ce78 100644 (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;
+       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);
        }
 }
 
@@ -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,57 +1892,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 f8507d8f856849f2b1d760f7da0a380c1df45bfb..ff6a908b4e462997bb7273a839bb4a408b63eadb 100644 (file)
 #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);
+
+                       /*
+                        * 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)
@@ -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.
index 7be4b03fdbc37be0a44cb1d8957b7eec87900bb7..e15553d727eb8e20a6b8f95e3502ace3600cbf2a 100644 (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,
index bfde96e75d9dabb5b2b53a9232f25709966a8841..afd2bf9fae4f67cf3c2df7db29de1bc5f6d41a24 100644 (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,