fix-tuplecid-cmin-upstream.patch
application/octet-stream
Filename: fix-tuplecid-cmin-upstream.patch
Type: application/octet-stream
Part: 0
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
| File | + | − |
|---|---|---|
| src/backend/replication/logical/reorderbuffer.c | 53 | 6 |
| src/backend/replication/logical/snapbuild.c | 1 | 1 |
| src/include/replication/reorderbuffer.h | 4 | 1 |
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 11139a910b8..6c0125818cd 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -145,6 +145,7 @@ typedef struct ReorderBufferTupleCidEnt
CommandId cmin;
CommandId cmax;
CommandId combocid; /* just for debugging */
+ TransactionId subxid; /* subtransaction that wrote this entry */
} ReorderBufferTupleCidEnt;
/* Virtual file descriptor with file offset tracking */
@@ -1878,16 +1879,60 @@ ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
ent->cmin = change->data.tuplecid.cmin;
ent->cmax = change->data.tuplecid.cmax;
ent->combocid = change->data.tuplecid.combocid;
+ ent->subxid = change->data.tuplecid.subxid;
}
- else
+ else if (ent->cmin != change->data.tuplecid.cmin)
{
/*
- * Maybe we already saw this tuple before in this transaction, but
- * if so it must have the same cmin.
+ * The same TID appears with a different cmin. This happens when
+ * a subtransaction inserts a catalog tuple, then a ROLLBACK TO
+ * SAVEPOINT aborts that subtransaction, and a later operation
+ * reuses the same TID (after page pruning reclaims the dead
+ * tuple's slot). Both xl_heap_new_cid records remain in the WAL
+ * under the top-level xid.
+ *
+ * We resolve this by checking which entry's subtransaction
+ * aborted. The entry from the aborted subtransaction is stale
+ * and should be discarded.
*/
- Assert(ent->cmin == change->data.tuplecid.cmin);
-
+ if (TransactionIdDidAbort(ent->subxid))
+ {
+ /* Existing entry is from an aborted subtxn; replace it. */
+ ent->cmin = change->data.tuplecid.cmin;
+ ent->cmax = change->data.tuplecid.cmax;
+ ent->combocid = change->data.tuplecid.combocid;
+ ent->subxid = change->data.tuplecid.subxid;
+ }
+ else if (TransactionIdDidAbort(change->data.tuplecid.subxid))
+ {
+ /* New entry is from an aborted subtxn; skip it. */
+ }
+ else
+ {
+ /*
+ * Neither subtransaction aborted — this shouldn't happen.
+ * Keep the existing entry but log a warning.
+ */
+ elog(WARNING, "tuplecid cmin mismatch with no aborted "
+ "subtransaction: rel %u/%u/%u tid (%u,%u) "
+ "existing cmin %u (subxid %u) "
+ "new cmin %u (subxid %u)",
+ key.rlocator.spcOid,
+ key.rlocator.dbOid,
+ key.rlocator.relNumber,
+ ItemPointerGetBlockNumber(&key.tid),
+ ItemPointerGetOffsetNumber(&key.tid),
+ ent->cmin, ent->subxid,
+ change->data.tuplecid.cmin,
+ change->data.tuplecid.subxid);
+ }
+ }
+ else
+ {
/*
+ * Same cmin — this is the normal case where the same tuple is
+ * seen multiple times (e.g. insert then update). Update cmax.
+ *
* cmax may be initially invalid, but once set it can only grow,
* and never become invalid again.
*/
@@ -3440,7 +3485,8 @@ void
ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
XLogRecPtr lsn, RelFileLocator locator,
ItemPointerData tid, CommandId cmin,
- CommandId cmax, CommandId combocid)
+ CommandId cmax, CommandId combocid,
+ TransactionId subxid)
{
ReorderBufferChange *change = ReorderBufferAllocChange(rb);
ReorderBufferTXN *txn;
@@ -3452,6 +3498,7 @@ ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
change->data.tuplecid.cmin = cmin;
change->data.tuplecid.cmax = cmax;
change->data.tuplecid.combocid = combocid;
+ change->data.tuplecid.subxid = subxid;
change->lsn = lsn;
change->txn = txn;
change->action = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index adf18c397db..3c9eba3dbaa 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -700,7 +700,7 @@ SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
ReorderBufferAddNewTupleCids(builder->reorder, xlrec->top_xid, lsn,
xlrec->target_locator, xlrec->target_tid,
xlrec->cmin, xlrec->cmax,
- xlrec->combocid);
+ xlrec->combocid, xid);
/* figure out new command id */
if (xlrec->cmin != InvalidCommandId &&
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
index fa0745552f8..0072c7e5aa1 100644
--- a/src/include/replication/reorderbuffer.h
+++ b/src/include/replication/reorderbuffer.h
@@ -146,6 +146,7 @@ typedef struct ReorderBufferChange
CommandId cmin;
CommandId cmax;
CommandId combocid;
+ TransactionId subxid;
} tuplecid;
/* Invalidation. */
@@ -748,7 +749,9 @@ extern void ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
extern void ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
XLogRecPtr lsn, RelFileLocator locator,
ItemPointerData tid,
- CommandId cmin, CommandId cmax, CommandId combocid);
+ CommandId cmin, CommandId cmax,
+ CommandId combocid,
+ TransactionId subxid);
extern void ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
Size nmsgs, SharedInvalidationMessage *msgs);
extern void ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid,