Skip to content

Commit c4ccbcc

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 79b5b10 commit c4ccbcc

File tree

1 file changed

+88
-77
lines changed

1 file changed

+88
-77
lines changed

src/backend/storage/ipc/standby.c

Lines changed: 88 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include "storage/procarray.h"
2828
#include "storage/sinvaladt.h"
2929
#include "storage/standby.h"
30+
#include "utils/hsearch.h"
31+
#include "utils/memutils.h"
3032
#include "utils/ps_status.h"
3133
#include "utils/timeout.h"
3234
#include "utils/timestamp.h"
@@ -36,7 +38,7 @@ int vacuum_defer_cleanup_age;
3638
int max_standby_archive_delay = 30 * 1000;
3739
int max_standby_streaming_delay = 30 * 1000;
3840

39-
static List *RecoveryLockList;
41+
static HTAB *RecoveryLockLists;
4042

4143
static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
4244
ProcSignalReason reason);
@@ -45,6 +47,14 @@ 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,20 @@ 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+
hash_ctl.hash = tag_hash;
85+
RecoveryLockLists = hash_create("RecoveryLockLists",
86+
64,
87+
&hash_ctl,
88+
HASH_ELEM | HASH_FUNCTION);
6589

6690
/*
6791
* Initialize shared invalidation management for Startup process, being
@@ -106,6 +130,10 @@ ShutdownRecoveryTransactionEnvironment(void)
106130
/* Release all locks the tracked transactions were holding */
107131
StandbyReleaseAllLocks();
108132

133+
/* Destroy the hash table of locks. */
134+
hash_destroy(RecoveryLockLists);
135+
RecoveryLockLists = NULL;
136+
109137
/* Cleanup our VirtualTransaction */
110138
VirtualXactLockTableCleanup();
111139
}
@@ -548,8 +576,8 @@ StandbyTimeoutHandler(void)
548576
* We only keep track of AccessExclusiveLocks, which are only ever held by
549577
* one transaction on one relation, and don't worry about lock queuing.
550578
*
551-
* We keep a single dynamically expandible list of locks in local memory,
552-
* RecoveryLockList, so we can keep track of the various entries made by
579+
* We keep a hash table of lists of locks in local memory keyed by xid,
580+
* RecoveryLockLists, so we can keep track of the various entries made by
553581
* the Startup process's virtual xid in the shared lock table.
554582
*
555583
* We record the lock against the top-level xid, rather than individual
@@ -567,8 +595,10 @@ StandbyTimeoutHandler(void)
567595
void
568596
StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
569597
{
598+
RecoveryLockListsEntry *entry;
570599
xl_standby_lock *newlock;
571600
LOCKTAG locktag;
601+
bool found;
572602

573603
/* Already processed? */
574604
if (!TransactionIdIsValid(xid) ||
@@ -582,11 +612,19 @@ StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
582612
/* dbOid is InvalidOid when we are locking a shared relation. */
583613
Assert(OidIsValid(relOid));
584614

615+
/* Create a new list for this xid, if we don't have one already. */
616+
entry = hash_search(RecoveryLockLists, &xid, HASH_ENTER, &found);
617+
if (!found)
618+
{
619+
entry->xid = xid;
620+
entry->locks = NIL;
621+
}
622+
585623
newlock = palloc(sizeof(xl_standby_lock));
586624
newlock->xid = xid;
587625
newlock->dbOid = dbOid;
588626
newlock->relOid = relOid;
589-
RecoveryLockList = lappend(RecoveryLockList, newlock);
627+
entry->locks = lappend(entry->locks, newlock);
590628

591629
/*
592630
* Attempt to acquire the lock as requested, if not resolve conflict
@@ -599,46 +637,48 @@ StandbyAcquireAccessExclusiveLock(TransactionId xid, Oid dbOid, Oid relOid)
599637
}
600638

601639
static void
602-
StandbyReleaseLocks(TransactionId xid)
640+
StandbyReleaseLockList(List *locks)
603641
{
604-
ListCell *cell,
605-
*prev,
606-
*next;
607-
608-
/*
609-
* Release all matching locks and remove them from list
610-
*/
611-
prev = NULL;
612-
for (cell = list_head(RecoveryLockList); cell; cell = next)
642+
while (locks)
613643
{
614-
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
644+
xl_standby_lock *lock = (xl_standby_lock *) linitial(locks);
645+
LOCKTAG locktag;
646+
elog(trace_recovery(DEBUG4),
647+
"releasing recovery lock: xid %u db %u rel %u",
648+
lock->xid, lock->dbOid, lock->relOid);
649+
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
650+
if (!LockRelease(&locktag, AccessExclusiveLock, true))
651+
{
652+
elog(LOG,
653+
"RecoveryLockLists contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
654+
lock->xid, lock->dbOid, lock->relOid);
655+
Assert(false);
656+
}
657+
pfree(lock);
658+
locks = list_delete_first(locks);
659+
}
660+
}
615661

616-
next = lnext(cell);
662+
static void
663+
StandbyReleaseLocks(TransactionId xid)
664+
{
665+
RecoveryLockListsEntry *entry;
617666

618-
if (!TransactionIdIsValid(xid) || lock->xid == xid)
667+
if (TransactionIdIsValid(xid))
668+
{
669+
if ((entry = hash_search(RecoveryLockLists, &xid, HASH_FIND, NULL)))
619670
{
620-
LOCKTAG locktag;
621-
622-
elog(trace_recovery(DEBUG4),
623-
"releasing recovery lock: xid %u db %u rel %u",
624-
lock->xid, lock->dbOid, lock->relOid);
625-
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
626-
if (!LockRelease(&locktag, AccessExclusiveLock, true))
627-
elog(LOG,
628-
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
629-
lock->xid, lock->dbOid, lock->relOid);
630-
631-
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
632-
pfree(lock);
671+
StandbyReleaseLockList(entry->locks);
672+
hash_search(RecoveryLockLists, entry, HASH_REMOVE, NULL);
633673
}
634-
else
635-
prev = cell;
636674
}
675+
else
676+
StandbyReleaseAllLocks();
637677
}
638678

639679
/*
640680
* Release locks for a transaction tree, starting at xid down, from
641-
* RecoveryLockList.
681+
* RecoveryLockLists.
642682
*
643683
* Called during WAL replay of COMMIT/ROLLBACK when in hot standby mode,
644684
* to remove any AccessExclusiveLocks requested by a transaction.
@@ -660,30 +700,16 @@ StandbyReleaseLockTree(TransactionId xid, int nsubxids, TransactionId *subxids)
660700
void
661701
StandbyReleaseAllLocks(void)
662702
{
663-
ListCell *cell,
664-
*prev,
665-
*next;
666-
LOCKTAG locktag;
703+
HASH_SEQ_STATUS status;
704+
RecoveryLockListsEntry *entry;
667705

668706
elog(trace_recovery(DEBUG2), "release all standby locks");
669707

670-
prev = NULL;
671-
for (cell = list_head(RecoveryLockList); cell; cell = next)
708+
hash_seq_init(&status, RecoveryLockLists);
709+
while ((entry = hash_seq_search(&status)))
672710
{
673-
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
674-
675-
next = lnext(cell);
676-
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-
elog(LOG,
683-
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
684-
lock->xid, lock->dbOid, lock->relOid);
685-
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
686-
pfree(lock);
711+
StandbyReleaseLockList(entry->locks);
712+
hash_search(RecoveryLockLists, entry, HASH_REMOVE, NULL);
687713
}
688714
}
689715

@@ -695,22 +721,17 @@ StandbyReleaseAllLocks(void)
695721
void
696722
StandbyReleaseOldLocks(int nxids, TransactionId *xids)
697723
{
698-
ListCell *cell,
699-
*prev,
700-
*next;
701-
LOCKTAG locktag;
724+
HASH_SEQ_STATUS status;
725+
RecoveryLockListsEntry *entry;
702726

703-
prev = NULL;
704-
for (cell = list_head(RecoveryLockList); cell; cell = next)
727+
hash_seq_init(&status, RecoveryLockLists);
728+
while ((entry = hash_seq_search(&status)))
705729
{
706-
xl_standby_lock *lock = (xl_standby_lock *) lfirst(cell);
707730
bool remove = false;
708731

709-
next = lnext(cell);
732+
Assert(TransactionIdIsValid(entry->xid));
710733

711-
Assert(TransactionIdIsValid(lock->xid));
712-
713-
if (StandbyTransactionIdIsPrepared(lock->xid))
734+
if (StandbyTransactionIdIsPrepared(entry->xid))
714735
remove = false;
715736
else
716737
{
@@ -719,7 +740,7 @@ StandbyReleaseOldLocks(int nxids, TransactionId *xids)
719740

720741
for (i = 0; i < nxids; i++)
721742
{
722-
if (lock->xid == xids[i])
743+
if (entry->xid == xids[i])
723744
{
724745
found = true;
725746
break;
@@ -735,19 +756,9 @@ StandbyReleaseOldLocks(int nxids, TransactionId *xids)
735756

736757
if (remove)
737758
{
738-
elog(trace_recovery(DEBUG4),
739-
"releasing recovery lock: xid %u db %u rel %u",
740-
lock->xid, lock->dbOid, lock->relOid);
741-
SET_LOCKTAG_RELATION(locktag, lock->dbOid, lock->relOid);
742-
if (!LockRelease(&locktag, AccessExclusiveLock, true))
743-
elog(LOG,
744-
"RecoveryLockList contains entry for lock no longer recorded by lock manager: xid %u database %u relation %u",
745-
lock->xid, lock->dbOid, lock->relOid);
746-
RecoveryLockList = list_delete_cell(RecoveryLockList, cell, prev);
747-
pfree(lock);
759+
StandbyReleaseLockList(entry->locks);
760+
hash_search(RecoveryLockLists, entry, HASH_REMOVE, NULL);
748761
}
749-
else
750-
prev = cell;
751762
}
752763
}
753764

0 commit comments

Comments
 (0)