]> 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 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,