Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3e36ff6
Document CSN Stage 1 design baseline
vbp1 Apr 20, 2026
f6c56b8
Add CSN infrastructure skeleton
vbp1 Apr 20, 2026
8cf96b8
Wire CSN transaction status into lifecycle paths
vbp1 Apr 20, 2026
288ea30
Add conservative CSN snapshot builder
vbp1 Apr 20, 2026
4fcc9d2
Wire CSN tuple visibility through HeapTupleSatisfiesMVCC
vbp1 Apr 20, 2026
8a0c6fc
Track CSN oldest-active xid through ProcArray lifecycle
vbp1 Apr 20, 2026
b78875e
Add CSN Stage 1 test coverage and stabilization
vbp1 Apr 20, 2026
84bf72b
Add Stage 2 CSN audit baseline
vbp1 Apr 20, 2026
4100001
Harden csnlog recovery lifecycle
vbp1 Apr 20, 2026
d49fbff
Harden CSN truncation and horizon retention
vbp1 Apr 21, 2026
b081742
Harden CSN subxid overflow snapshots
vbp1 Apr 22, 2026
d7cbea9
Strengthen CSN prepared transaction restart coverage
vbp1 Apr 22, 2026
c313ec6
Harden CSN snapshot transport contract
vbp1 Apr 22, 2026
f9e3054
Finalize Stage 2 CSN validation and status semantics
vbp1 Apr 22, 2026
e6b3ee5
Close Stage 2 2PC primary-only contract
vbp1 Apr 22, 2026
21d5d2b
Add Stage 3 commit fallback harness
vbp1 Apr 22, 2026
1e06bc1
Stage 3 C1: publish CSN-safe snapshot state early
vbp1 Apr 22, 2026
68b954d
Stage 3 D1: track xmin-only CSN holders earlier
vbp1 Apr 22, 2026
53d56a3
Stage 3 D1: cover snapshot reuse in CSN xmin bookkeeping
vbp1 Apr 22, 2026
3f831e3
Stage 3 D1: keep CSN xmin bookkeeping on reset
vbp1 Apr 22, 2026
664f7b0
Stage 3 E1: self-clear CSN marker after procarray exit
vbp1 Apr 22, 2026
fa80689
Stage 3 F1: make snapshot membership CSN-aware
vbp1 Apr 22, 2026
5316e7d
Stage 3 G1: remove ordinary ProcArrayEndTransaction path
vbp1 Apr 22, 2026
4c5fe3d
Expand H1 pre-switch ordinary path proof harness
vbp1 Apr 23, 2026
42599a8
Stabilize H1-E ordinary mirror proof harness
vbp1 Apr 23, 2026
a8da2c7
Make ordinary primary transaction end lock-free
vbp1 Apr 23, 2026
5f503a5
Close H1 Milestone A validation gate
vbp1 Apr 24, 2026
94cac2a
Fix CSN frontend build after rebase
vbp1 Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 278 additions & 5 deletions src/backend/access/heap/heapam_visibility.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
* that were in progress during a crash as aborted. We determine that
* transactions aborted/crashed through process of elimination instead.
*
* When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
* TransactionIdIsInProgress, but the logic is otherwise the same: do not
* check pg_xact until after deciding that the xact is no longer in progress.
* When using a legacy MVCC snapshot, we rely on XidInMVCCSnapshot rather
* than TransactionIdIsInProgress, but the logic is otherwise the same: do
* not check pg_xact until after deciding that the xact is no longer in
* progress. Stage 1 CSN snapshots take a separate path that asks transam for
* a centralized CSN status and compares committed xacts against
* snapshot_csn.
*
*
* Summary of visibility functions:
Expand Down Expand Up @@ -98,6 +101,16 @@ typedef enum SetHintBitsState
SHB_ENABLED,
} SetHintBitsState;

typedef enum HeapTupleCSNXidVisibility
{
HEAPTUPLE_CSN_XID_FALLBACK,
HEAPTUPLE_CSN_XID_ABORTED,
HEAPTUPLE_CSN_XID_IN_PROGRESS,
HEAPTUPLE_CSN_XID_COMMITTING,
HEAPTUPLE_CSN_XID_VISIBLE,
HEAPTUPLE_CSN_XID_IN_FUTURE
} HeapTupleCSNXidVisibility;

/*
* SetHintBitsExt()
*
Expand Down Expand Up @@ -202,6 +215,54 @@ SetHintBits(HeapTupleHeader tuple, Buffer buffer,
SetHintBitsExt(tuple, buffer, infomask, xid, NULL);
}

static inline bool
HeapTupleCSNCommittedVisible(CommitSeqNo xidcsn, Snapshot snapshot)
{
Assert(SnapshotUsesCSN(snapshot));
Assert(CommitSeqNoIsCommitted(xidcsn));

if (CommitSeqNoIsFrozen(xidcsn))
return true;

return CommitSeqNoPrecedes(xidcsn, snapshot->snapshot_csn);
}

static inline HeapTupleCSNXidVisibility
HeapTupleCSNGetXidVisibility(TransactionId xid, Snapshot snapshot)
{
CommitSeqNo xidcsn = InvalidCommitSeqNo;
TransactionCSNStatus xidstatus;

Assert(SnapshotUsesCSN(snapshot));

xidstatus = TransactionIdGetCSNStatus(xid, &xidcsn);

switch (xidstatus)
{
case TRANSACTION_CSN_STATUS_INVALID:

/*
* Stage 1 cannot safely invent a committed/aborted answer when
* transam reports that no general CSN status is available for
* this xid. Fall back to the legacy tuple-visibility path for the
* whole tuple instead of making a mixed-model guess here.
*/
return HEAPTUPLE_CSN_XID_FALLBACK;
case TRANSACTION_CSN_STATUS_IN_PROGRESS:
return HEAPTUPLE_CSN_XID_IN_PROGRESS;
case TRANSACTION_CSN_STATUS_COMMITTING:
return HEAPTUPLE_CSN_XID_COMMITTING;
case TRANSACTION_CSN_STATUS_ABORTED:
return HEAPTUPLE_CSN_XID_ABORTED;
case TRANSACTION_CSN_STATUS_COMMITTED:
if (HeapTupleCSNCommittedVisible(xidcsn, snapshot))
return HEAPTUPLE_CSN_XID_VISIBLE;
return HEAPTUPLE_CSN_XID_IN_FUTURE;
}

pg_unreachable();
}

/*
* HeapTupleSetHintBits --- exported version of SetHintBits()
*
Expand Down Expand Up @@ -936,8 +997,8 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
* and more contention on ProcArrayLock.
*/
static inline bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer, SetHintBitsState *state)
HeapTupleSatisfiesMVCCLegacy(HeapTuple htup, Snapshot snapshot,
Buffer buffer, SetHintBitsState *state)
{
HeapTupleHeader tuple = htup->t_data;

Expand Down Expand Up @@ -1095,6 +1156,218 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
return false;
}

static inline bool
HeapTupleSatisfiesMVCCCSN(HeapTuple htup, Snapshot snapshot,
Buffer buffer, SetHintBitsState *state)
{
HeapTupleHeader tuple = htup->t_data;

Assert(SnapshotUsesCSN(snapshot));

if (!HeapTupleHeaderXminCommitted(tuple))
{
HeapTupleCSNXidVisibility xminvisible;

if (HeapTupleHeaderXminInvalid(tuple))
return false;

if (!HeapTupleCleanMoved(tuple, buffer))
return false;
else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
{
if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
return false; /* inserted after scan started */

if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;

if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;

if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;

xmax = HeapTupleGetUpdateXid(tuple);

/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));

/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* updated after scan started */
else
return false; /* updated before scan started */
}

if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBitsExt(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId, state);
return true;
}

if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}

xminvisible = HeapTupleCSNGetXidVisibility(HeapTupleHeaderGetRawXmin(tuple),
snapshot);
switch (xminvisible)
{
case HEAPTUPLE_CSN_XID_FALLBACK:
return HeapTupleSatisfiesMVCCLegacy(htup, snapshot, buffer, state);
case HEAPTUPLE_CSN_XID_VISIBLE:
SetHintBitsExt(tuple, buffer, HEAP_XMIN_COMMITTED,
HeapTupleHeaderGetRawXmin(tuple), state);
break;
case HEAPTUPLE_CSN_XID_IN_PROGRESS:
case HEAPTUPLE_CSN_XID_COMMITTING:
case HEAPTUPLE_CSN_XID_IN_FUTURE:
return false;
case HEAPTUPLE_CSN_XID_ABORTED:
SetHintBitsExt(tuple, buffer, HEAP_XMIN_INVALID,
InvalidTransactionId, state);
return false;
}
}
else if (!HeapTupleHeaderXminFrozen(tuple))
{
HeapTupleCSNXidVisibility xminvisible;

xminvisible = HeapTupleCSNGetXidVisibility(HeapTupleHeaderGetRawXmin(tuple),
snapshot);
switch (xminvisible)
{
case HEAPTUPLE_CSN_XID_FALLBACK:
return HeapTupleSatisfiesMVCCLegacy(htup, snapshot, buffer, state);
case HEAPTUPLE_CSN_XID_VISIBLE:
break;
case HEAPTUPLE_CSN_XID_IN_PROGRESS:
case HEAPTUPLE_CSN_XID_COMMITTING:
case HEAPTUPLE_CSN_XID_IN_FUTURE:
case HEAPTUPLE_CSN_XID_ABORTED:
return false;
}
}

/*
* by here, the inserting transaction has committed and is
* snapshot-visible
*/

if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
return true;

if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;

if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;

/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));

xmax = HeapTupleGetUpdateXid(tuple);

/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));

if (TransactionIdIsCurrentTransactionId(xmax))
{
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}

switch (HeapTupleCSNGetXidVisibility(xmax, snapshot))
{
case HEAPTUPLE_CSN_XID_FALLBACK:
return HeapTupleSatisfiesMVCCLegacy(htup, snapshot, buffer, state);
case HEAPTUPLE_CSN_XID_VISIBLE:
return false;
case HEAPTUPLE_CSN_XID_IN_PROGRESS:
case HEAPTUPLE_CSN_XID_COMMITTING:
case HEAPTUPLE_CSN_XID_IN_FUTURE:
case HEAPTUPLE_CSN_XID_ABORTED:
return true;
}
}

if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
HeapTupleCSNXidVisibility xmaxvisible;

if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}

xmaxvisible = HeapTupleCSNGetXidVisibility(HeapTupleHeaderGetRawXmax(tuple),
snapshot);
switch (xmaxvisible)
{
case HEAPTUPLE_CSN_XID_FALLBACK:
return HeapTupleSatisfiesMVCCLegacy(htup, snapshot, buffer, state);
case HEAPTUPLE_CSN_XID_VISIBLE:
SetHintBitsExt(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetRawXmax(tuple), state);
return false;
case HEAPTUPLE_CSN_XID_IN_PROGRESS:
case HEAPTUPLE_CSN_XID_COMMITTING:
case HEAPTUPLE_CSN_XID_IN_FUTURE:
return true;
case HEAPTUPLE_CSN_XID_ABORTED:
SetHintBitsExt(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId, state);
return true;
}
}
else
{
switch (HeapTupleCSNGetXidVisibility(HeapTupleHeaderGetRawXmax(tuple),
snapshot))
{
case HEAPTUPLE_CSN_XID_FALLBACK:
return HeapTupleSatisfiesMVCCLegacy(htup, snapshot, buffer, state);
case HEAPTUPLE_CSN_XID_VISIBLE:
return false;
case HEAPTUPLE_CSN_XID_IN_PROGRESS:
case HEAPTUPLE_CSN_XID_COMMITTING:
case HEAPTUPLE_CSN_XID_IN_FUTURE:
case HEAPTUPLE_CSN_XID_ABORTED:
return true;
}
}

pg_unreachable();
}

static inline bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
Buffer buffer, SetHintBitsState *state)
{
/*
* Stage 1 only switches supported MVCC snapshots onto CSN semantics.
* Unsupported shapes and local-buffer relations stay on the legacy
* xid-array path explicitly.
*/
if (!SnapshotUsesCSN(snapshot) || BufferIsLocal(buffer))
return HeapTupleSatisfiesMVCCLegacy(htup, snapshot, buffer, state);

return HeapTupleSatisfiesMVCCCSN(htup, snapshot, buffer, state);
}


/*
* HeapTupleSatisfiesVacuum
Expand Down
2 changes: 2 additions & 0 deletions src/backend/access/transam/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ include $(top_builddir)/src/Makefile.global

OBJS = \
clog.o \
csn_mvcc_vars.o \
csnlog.o \
commit_ts.o \
generic_xlog.o \
multixact.o \
Expand Down
Loading