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

Commit 35750a3

Browse files
committed
Move RecoveryLockList into a hash table.
Standbys frequently need to release all locks held by a given xid. Instead of searching one big list linearly, let's create one list per xid and put them in a hash table, so we can find what we need in O(1) time. Earlier analysis and a prototype were done by David Rowley, though this isn't his patch. Back-patch all the way. Author: Thomas Munro Diagnosed-by: David Rowley, Andres Freund Reviewed-by: Andres Freund, Tom Lane, Robert Haas Discussion: https://postgr.es/m/CAEepm%3D1mL0KiQ2KJ4yuPpLGX94a4Ns_W6TL4EGRouxWibu56pA%40mail.gmail.com Discussion: https://postgr.es/m/CAKJS1f9vJ841HY%3DwonnLVbfkTWGYWdPN72VMxnArcGCjF3SywA%40mail.gmail.com
1 parent 7fdf56b commit 35750a3

File tree

1 file changed

+87
-77
lines changed

1 file changed

+87
-77
lines changed

src/backend/storage/ipc/standby.c

Lines changed: 87 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include "storage/procarray.h"
2929
#include "storage/sinvaladt.h"
3030
#include "storage/standby.h"
31+
#include "utils/hsearch.h"
32+
#include "utils/memutils.h"
3133
#include "utils/ps_status.h"
3234
#include "utils/timeout.h"
3335
#include "utils/timestamp.h"
@@ -37,14 +39,22 @@ int vacuum_defer_cleanup_age;
3739
int max_standby_archive_delay = 30 * 1000;
3840
int max_standby_streaming_delay = 30 * 1000;
3941

40-
static List *RecoveryLockList;
42+
static HTAB *RecoveryLockLists;
4143

4244
static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
4345
ProcSignalReason reason);
4446
static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
4547
static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
4648
static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
4749

50+
/*
51+
* Keep track of all the locks owned by a given transaction.
52+
*/
53+
typedef struct RecoveryLockListsEntry
54+
{
55+
TransactionId xid;
56+
List *locks;
57+
} RecoveryLockListsEntry;
4858

4959
/*
5060
* InitRecoveryTransactionEnvironment
@@ -62,6 +72,19 @@ void
6272
InitRecoveryTransactionEnvironment(void)
6373
{
6474
VirtualTransactionId vxid;
75+
HASHCTL hash_ctl;
76+
77+
/*
78+
* Initialize the hash table for tracking the list of locks held by each
79+
* transaction.
80+
*/
81+
memset(&hash_ctl, 0, sizeof(hash_ctl));
82+
hash_ctl.keysize = sizeof(TransactionId);
83+
hash_ctl.entrysize = sizeof(RecoveryLockListsEntry);
84+
RecoveryLockLists = hash_create("RecoveryLockLists",
85+
64,
86+
&hash_ctl,
87+
HASH_ELEM | HASH_BLOBS);
6588

6689
/*
6790
* Initialize shared invalidation management for Startup process, being
@@ -106,6 +129,10 @@ ShutdownRecoveryTransactionEnvironment(void)
106129
/* Release all locks the tracked transactions were holding */
107130
StandbyReleaseAllLocks();
108131

132+
/* Destroy the hash table of locks. */
133+
hash_destroy(RecoveryLockLists);
134+
RecoveryLockLists = NULL;
135+
109136
/* Cleanup our VirtualTransaction */
110137
VirtualXactLockTableCleanup();
111138
}
@@ -585,8 +612,8 @@ StandbyLockTimeoutHandler(void)
585612
* We only keep track of AccessExclusiveLocks, which are only ever held by
586613
* one transaction on one relation.
587614
*
588-
* We keep a single dynamically expandible list of locks in local memory,
589-
* RecoveryLockList, so we can keep track of the various entries made by
615+
* We keep a hash table of lists of locks in local memory keyed by xid,
616+
* RecoveryLockLists, so we can keep track of the various entries made by
590617
* the Startup process's virtual xid in the shared lock table.
591618
*
592619
* We record the lock against the top-level xid, rather than individual
@@ -604,8 +631,10 @@ StandbyLockTimeoutHandler(void)
604631
void
605632
StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
606633
{
634+
RecoveryLockListsEntry *entry;
607635
xl_standby_lock *newlock;
608636
LOCKTAG locktag;
637+
bool found;
609638

610639
/* Already processed? */
611640
if (!TransactionIdIsValid(xid) ||
@@ -619,58 +648,68 @@ StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
619648
/* dbOid is InvalidOid when we are locking a shared relation. */
620649
Assert(OidIsValid(relOid));
621650

651+
/* Create a new list for this xid, if we don't have one already. */
652+
entry = hash_search(RecoveryLockLists, &xid, HASH_ENTER, &found);
653+
if (!found)
654+
{
655+
entry->xid = xid;
656+
entry->locks = NIL;
657+
}
658+
622659
newlock = palloc(sizeof(xl_standby_lock));
623660
newlock->xid = xid;
624661
newlock->dbOid = dbOid;
625662
newlock->relOid = relOid;
626-
RecoveryLockList = lappend(RecoveryLockList, newlock);
663+
entry->locks = lappend(entry->locks, newlock);
627664

628665
SET_LOCKTAG_RELATION(locktag, newlock->dbOid, newlock->relOid);
629666

630667
LockAcquireExtended(&locktag, AccessExclusiveLock, true, false, false);
631668
}
632669

633670
static void
634-
StandbyReleaseLocks(TransactionId xid)
671+
StandbyReleaseLockList(List *locks)
635672
{
636-
ListCell *cell,
637-
*prev,
638-
*next;
639-
640-
/*
641-
* Release all matching locks and remove them from list
642-
*/
643-
prev = NULL;
644-
for (cell = list_head(RecoveryLockList); cell; cell = next)
673+
while (locks)
645674
{
646-
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
675+
xl_standby_lock *lock = (xl_standby_lock *) linitial(locks);
676+
LOCKTAG locktag;
677+
elog(trace_recovery(DEBUG4),
678+
"releasing recovery lock: xid %u db %u rel %u",
679+
lock->xid, lock->dbOid, lock->relOid);
680+
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
681+
if (!LockRelease(&locktag, AccessExclusiveLock, true))
682+
{
683+
elog(LOG,
684+
"RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
685+
lock->xid, lock->dbOid, lock->relOid);
686+
Assert(false);
687+
}
688+
pfree(lock);
689+
locks = list_delete_first(locks);
690+
}
691+
}
647692

648-
next = lnext(cell);
693+
static void
694+
StandbyReleaseLocks(TransactionId xid)
695+
{
696+
RecoveryLockListsEntry *entry;
649697

650-
if (!TransactionIdIsValid(xid) || lock->xid == xid)
698+
if (TransactionIdIsValid(xid))
699+
{
700+
if ((entry = hash_search(RecoveryLockLists, &xid, HASH_FIND, NULL)))
651701
{
652-
LOCKTAG locktag;
653-
654-
elog(trace_recovery(DEBUG4),
655-
"releasing recovery lock: xid %u db %u rel %u",
656-
lock->xid, lock->dbOid, lock->relOid);
657-
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
658-
if (!LockRelease(&locktag, AccessExclusiveLock, true))
659-
elog(LOG,
660-
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
661-
lock->xid, lock->dbOid, lock->relOid);
662-
663-
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
664-
pfree(lock);
702+
StandbyReleaseLockList(entry->locks);
703+
hash_search(RecoveryLockLists, entry, HASH_REMOVE, NULL);
665704
}
666-
else
667-
prev = cell;
668705
}
706+
else
707+
StandbyReleaseAllLocks();
669708
}
670709

671710
/*
672711
* Release locks for a transaction tree, starting at xid down, from
673-
* RecoveryLockList.
712+
* RecoveryLockLists.
674713
*
675714
* Called during WAL replay of COMMIT/ROLLBACK when in hot standby mode,
676715
* to remove any AccessExclusiveLocks requested by a transaction.
@@ -692,30 +731,16 @@ StandbyReleaseLockTree(TransactionId xid, int nsubxids, TransactionId *subxids)
692731
void
693732
StandbyReleaseAllLocks(void)
694733
{
695-
ListCell *cell,
696-
*prev,
697-
*next;
698-
LOCKTAG locktag;
734+
HASH_SEQ_STATUS status;
735+
RecoveryLockListsEntry *entry;
699736

700737
elog(trace_recovery(DEBUG2), "release all standby locks");
701738

702-
prev = NULL;
703-
for (cell = list_head(RecoveryLockList); cell; cell = next)
739+
hash_seq_init(&status, RecoveryLockLists);
740+
while ((entry = hash_seq_search(&status)))
704741
{
705-
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
706-
707-
next = lnext(cell);
708-
709-
elog(trace_recovery(DEBUG4),
710-
"releasing recovery lock: xid %u db %u rel %u",
711-
lock->xid, lock->dbOid, lock->relOid);
712-
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
713-
if (!LockRelease(&locktag, AccessExclusiveLock, true))
714-
elog(LOG,
715-
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
716-
lock->xid, lock->dbOid, lock->relOid);
717-
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
718-
pfree(lock);
742+
StandbyReleaseLockList(entry->locks);
743+
hash_search(RecoveryLockLists, entry, HASH_REMOVE, NULL);
719744
}
720745
}
721746

@@ -727,22 +752,17 @@ StandbyReleaseAllLocks(void)
727752
void
728753
StandbyReleaseOldLocks(int nxids, TransactionId *xids)
729754
{
730-
ListCell *cell,
731-
*prev,
732-
*next;
733-
LOCKTAG locktag;
755+
HASH_SEQ_STATUS status;
756+
RecoveryLockListsEntry *entry;
734757

735-
prev = NULL;
736-
for (cell = list_head(RecoveryLockList); cell; cell = next)
758+
hash_seq_init(&status, RecoveryLockLists);
759+
while ((entry = hash_seq_search(&status)))
737760
{
738-
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
739761
bool remove = false;
740762

741-
next = lnext(cell);
763+
Assert(TransactionIdIsValid(entry->xid));
742764

743-
Assert(TransactionIdIsValid(lock->xid));
744-
745-
if (StandbyTransactionIdIsPrepared(lock->xid))
765+
if (StandbyTransactionIdIsPrepared(entry->xid))
746766
remove = false;
747767
else
748768
{
@@ -751,7 +771,7 @@ StandbyReleaseOldLocks(int nxids, TransactionId *xids)
751771

752772
for (i = 0; i < nxids; i++)
753773
{
754-
if (lock->xid == xids[i])
774+
if (entry->xid == xids[i])
755775
{
756776
found = true;
757777
break;
@@ -767,19 +787,9 @@ StandbyReleaseOldLocks(int nxids, TransactionId *xids)
767787

768788
if (remove)
769789
{
770-
elog(trace_recovery(DEBUG4),
771-
"releasing recovery lock: xid %u db %u rel %u",
772-
lock->xid, lock->dbOid, lock->relOid);
773-
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
774-
if (!LockRelease(&locktag, AccessExclusiveLock, true))
775-
elog(LOG,
776-
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
777-
lock->xid, lock->dbOid, lock->relOid);
778-
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
779-
pfree(lock);
790+
StandbyReleaseLockList(entry->locks);
791+
hash_search(RecoveryLockLists, entry, HASH_REMOVE, NULL);
780792
}
781-
else
782-
prev = cell;
783793
}
784794
}
785795

0 commit comments

Comments
 (0)