inplace155-backbranch-inval-v1_16.patch

text/plain

Filename: inplace155-backbranch-inval-v1_16.patch
Type: text/plain
Part: 1
Message: Re: Inval reliability, especially for inplace updates

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v1
File+
src/backend/access/transam/xlog.c 25 0
src/backend/storage/ipc/sinvaladt.c 41 0
src/include/storage/sinvaladt.h 1 0
commit 1bfa06e (HEAD, zzy_test-commit-REL_16_STABLE)
Author:     Noah Misch <noah@leadboat.com>
AuthorDate: Wed Oct 23 15:27:13 2024 -0700
Commit:     Noah Misch <noah@leadboat.com>
CommitDate: Wed Oct 23 15:31:54 2024 -0700

    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.
    
    Reviewed by FIXME.
    
    Discussion: https://postgr.es/m/20240618152349.7f.nmisch@google.com
    
    Conflicts:
    	src/backend/access/transam/xlog.c
---
 src/backend/access/transam/xlog.c   | 25 ++++++++++++++++++++++
 src/backend/storage/ipc/sinvaladt.c | 41 +++++++++++++++++++++++++++++++++++++
 src/include/storage/sinvaladt.h     |  1 +
 3 files changed, 67 insertions(+)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a19ba71..46faf40 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -94,6 +94,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/reinit.h"
+#include "storage/sinvaladt.h"
 #include "storage/smgr.h"
 #include "storage/spin.h"
 #include "storage/sync.h"
@@ -5660,6 +5661,30 @@ StartupXLOG(void)
 	XLogCtl->LogwrtRqst.Flush = EndOfLog;
 
 	/*
+	 * Invalidate all sinval-managed caches before READ WRITE transactions
+	 * begin.  The xl_heap_inplace WAL record doesn't store sufficient data
+	 * for invalidations.  The commit record, if any, has the invalidations.
+	 * However, the inplace update is permanent, whether or not we reach a
+	 * commit record.  Fortunately, read-only transactions tolerate caches not
+	 * reflecting the latest inplace updates.  Read-only transactions
+	 * experience the notable inplace updates as follows:
+	 *
+	 * - relhasindex=true affects readers only after the CREATE INDEX
+	 * transaction commit makes an index fully available to them.
+	 *
+	 * - datconnlimit=DATCONNLIMIT_INVALID_DB affects readers only at
+	 * InitPostgres() time, and that read does not use a cache.
+	 *
+	 * - relfrozenxid, datfrozenxid, relminmxid, and datminmxid have no effect
+	 * on readers.
+	 *
+	 * Hence, hot standby queries (all READ ONLY) function correctly without
+	 * the missing invalidations.  This avoided changing the WAL format in
+	 * back branches.
+	 */
+	SIResetAll();
+
+	/*
 	 * Preallocate additional log files, if wanted.
 	 */
 	PreallocXlogFiles(EndOfLog, newTLI);
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index 3d97c75..099c292 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -762,6 +762,47 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 	}
 }
 
+/*
+ * SIResetAll
+ *		Mark all active backends as "reset"
+ *
+ * Use this when we don't know what needs to be invalidated.  It's a
+ * cluster-wide InvalidateSystemCaches().  This was a back-branch-only remedy
+ * to avoid a WAL format change.
+ *
+ * The implementation is like SICleanupQueue(false, MAXNUMMESSAGES + 1), with
+ * one addition.  SICleanupQueue() assumes minFree << MAXNUMMESSAGES, so it
+ * assumes hasMessages==true for any backend it resets.  We're resetting even
+ * fully-caught-up backends, so we set hasMessages.
+ */
+void
+SIResetAll(void)
+{
+	SISeg	   *segP = shmInvalBuffer;
+	int			i;
+
+	LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
+	LWLockAcquire(SInvalReadLock, LW_EXCLUSIVE);
+
+	for (i = 0; i < segP->lastBackend; i++)
+	{
+		ProcState  *stateP = &segP->procState[i];
+
+		if (stateP->procPid == 0 || stateP->sendOnly)
+			continue;
+
+		/* Consuming the reset will update "nextMsgNum" and "signaled". */
+		stateP->resetState = true;
+		stateP->hasMessages = true;
+	}
+
+	segP->minMsgNum = segP->maxMsgNum;
+	segP->nextThreshold = CLEANUP_MIN;
+
+	LWLockRelease(SInvalReadLock);
+	LWLockRelease(SInvalWriteLock);
+}
+
 
 /*
  * GetNextLocalTransactionId --- allocate a new LocalTransactionId
diff --git a/src/include/storage/sinvaladt.h b/src/include/storage/sinvaladt.h
index db38819..1f9eaf2 100644
--- a/src/include/storage/sinvaladt.h
+++ b/src/include/storage/sinvaladt.h
@@ -39,6 +39,7 @@ extern void BackendIdGetTransactionIds(int backendID, TransactionId *xid,
 extern void SIInsertDataEntries(const SharedInvalidationMessage *data, int n);
 extern int	SIGetDataEntries(SharedInvalidationMessage *data, int datasize);
 extern void SICleanupQueue(bool callerHasWriteLock, int minFree);
+extern void SIResetAll(void);
 
 extern LocalTransactionId GetNextLocalTransactionId(void);