Skip to content

Commit a4668c9

Browse files
committed
At end of recovery, reset all sinval-managed caches.
An inplace update's invalidation messages are part of its transaction's commit record. However, the update survives even if its transaction aborts or we stop recovery before replaying its transaction commit. After recovery, a backend that started in recovery could update the row without incorporating the inplace update. That could result in a table with an index, yet relhasindex=f. That is a source of index corruption. This bulk invalidation avoids the functional consequences. A future change can fix the !RecoveryInProgress() scenario without changing the WAL format. Back-patch to v17 - v12 (all supported versions). v18 will instead add invalidations to WAL. Discussion: https://postgr.es/m/20240618152349.7f.nmisch@google.com
1 parent e119076 commit a4668c9

File tree

3 files changed

+68
-0
lines changed

3 files changed

+68
-0
lines changed

src/backend/access/transam/xlog.c

+25
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
#include "storage/proc.h"
9393
#include "storage/procarray.h"
9494
#include "storage/reinit.h"
95+
#include "storage/sinvaladt.h"
9596
#include "storage/spin.h"
9697
#include "storage/sync.h"
9798
#include "utils/guc_hooks.h"
@@ -6037,6 +6038,30 @@ StartupXLOG(void)
60376038
XLogCtl->LogwrtRqst.Write = EndOfLog;
60386039
XLogCtl->LogwrtRqst.Flush = EndOfLog;
60396040

6041+
/*
6042+
* Invalidate all sinval-managed caches before READ WRITE transactions
6043+
* begin. The xl_heap_inplace WAL record doesn't store sufficient data
6044+
* for invalidations. The commit record, if any, has the invalidations.
6045+
* However, the inplace update is permanent, whether or not we reach a
6046+
* commit record. Fortunately, read-only transactions tolerate caches not
6047+
* reflecting the latest inplace updates. Read-only transactions
6048+
* experience the notable inplace updates as follows:
6049+
*
6050+
* - relhasindex=true affects readers only after the CREATE INDEX
6051+
* transaction commit makes an index fully available to them.
6052+
*
6053+
* - datconnlimit=DATCONNLIMIT_INVALID_DB affects readers only at
6054+
* InitPostgres() time, and that read does not use a cache.
6055+
*
6056+
* - relfrozenxid, datfrozenxid, relminmxid, and datminmxid have no effect
6057+
* on readers.
6058+
*
6059+
* Hence, hot standby queries (all READ ONLY) function correctly without
6060+
* the missing invalidations. This avoided changing the WAL format in
6061+
* back branches.
6062+
*/
6063+
SIResetAll();
6064+
60406065
/*
60416066
* Preallocate additional log files, if wanted.
60426067
*/

src/backend/storage/ipc/sinvaladt.c

+42
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,48 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
683683
}
684684
}
685685

686+
/*
687+
* SIResetAll
688+
* Mark all active backends as "reset"
689+
*
690+
* Use this when we don't know what needs to be invalidated. It's a
691+
* cluster-wide InvalidateSystemCaches(). This was a back-branch-only remedy
692+
* to avoid a WAL format change.
693+
*
694+
* The implementation is like SICleanupQueue(false, MAXNUMMESSAGES + 1), with
695+
* one addition. SICleanupQueue() assumes minFree << MAXNUMMESSAGES, so it
696+
* assumes hasMessages==true for any backend it resets. We're resetting even
697+
* fully-caught-up backends, so we set hasMessages.
698+
*/
699+
void
700+
SIResetAll(void)
701+
{
702+
SISeg *segP = shmInvalBuffer;
703+
int i;
704+
705+
LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
706+
LWLockAcquire(SInvalReadLock, LW_EXCLUSIVE);
707+
708+
for (i = 0; i < segP->numProcs; i++)
709+
{
710+
ProcState *stateP = &segP->procState[segP->pgprocnos[i]];
711+
712+
Assert(stateP->procPid != 0);
713+
if (stateP->sendOnly)
714+
continue;
715+
716+
/* Consuming the reset will update "nextMsgNum" and "signaled". */
717+
stateP->resetState = true;
718+
stateP->hasMessages = true;
719+
}
720+
721+
segP->minMsgNum = segP->maxMsgNum;
722+
segP->nextThreshold = CLEANUP_MIN;
723+
724+
LWLockRelease(SInvalReadLock);
725+
LWLockRelease(SInvalWriteLock);
726+
}
727+
686728

687729
/*
688730
* GetNextLocalTransactionId --- allocate a new LocalTransactionId

src/include/storage/sinvaladt.h

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ extern void SharedInvalBackendInit(bool sendOnly);
3535
extern void SIInsertDataEntries(const SharedInvalidationMessage *data, int n);
3636
extern int SIGetDataEntries(SharedInvalidationMessage *data, int datasize);
3737
extern void SICleanupQueue(bool callerHasWriteLock, int minFree);
38+
extern void SIResetAll(void);
3839

3940
extern LocalTransactionId GetNextLocalTransactionId(void);
4041

0 commit comments

Comments
 (0)