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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy