Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 20 additions & 28 deletions src/backend/access/heap/heapam.c
Original file line number Diff line number Diff line change
Expand Up @@ -2185,7 +2185,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
int bufflags = 0;
#ifdef USE_PGRAC_CLUSTER
xl_heap_itl_delta_block cluster_itl_hdr;
xl_heap_itl_delta_v2 cluster_itl_delta; /* spec-3.4b D6 F9 — v2 40B */
xl_heap_itl_delta_v3 cluster_itl_delta; /* spec-5.19 MG-D — v3 32B (commit_scn dropped) */
#endif

/*
Expand Down Expand Up @@ -2241,12 +2241,11 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
{
cluster_itl_hdr.ndeltas = 1;
cluster_itl_hdr.reserved = 0;
cluster_itl_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
cluster_itl_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
cluster_itl_delta.slot_idx = cluster_itl_slot;
cluster_itl_delta.flags_after = ITL_FLAG_ACTIVE;
cluster_itl_delta.xid = xid;
cluster_itl_delta.write_scn = ClusterPageGetItlSlots(BufferGetPage(buffer))[cluster_itl_slot].write_scn;
cluster_itl_delta.commit_scn = InvalidScn;
cluster_itl_delta.undo_segment_head = cluster_itl_uba;

xlrec.flags |= XLH_INSERT_ITL_DELTA;
Expand Down Expand Up @@ -2777,16 +2776,15 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
if (cluster_mi_active)
{
xl_heap_itl_delta_block mi_hdr;
xl_heap_itl_delta_v2 mi_delta; /* spec-3.4b D6 F9 — v2 40B */
xl_heap_itl_delta_v3 mi_delta; /* spec-5.19 MG-D — v3 32B (commit_scn dropped) */

mi_hdr.ndeltas = 1;
mi_hdr.reserved = 0;
mi_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
mi_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
mi_delta.slot_idx = cluster_mi_slot;
mi_delta.flags_after = ITL_FLAG_ACTIVE;
mi_delta.xid = xid;
mi_delta.write_scn = ClusterPageGetItlSlots(page)[cluster_mi_slot].write_scn;
mi_delta.commit_scn = InvalidScn;
mi_delta.undo_segment_head = cluster_mi_uba;

XLogRegisterData((char *) &mi_hdr,
Expand Down Expand Up @@ -3603,7 +3601,7 @@ heap_delete(Relation relation, ItemPointer tid,
XLogRecPtr recptr;
#ifdef USE_PGRAC_CLUSTER
xl_heap_itl_delta_block cluster_itl_hdr;
xl_heap_itl_delta_v2 cluster_itl_delta; /* spec-3.4b D6 F9 — v2 40B */
xl_heap_itl_delta_v3 cluster_itl_delta; /* spec-5.19 MG-D — v3 32B (commit_scn dropped) */
#endif

/*
Expand Down Expand Up @@ -3638,12 +3636,11 @@ heap_delete(Relation relation, ItemPointer tid,
{
cluster_itl_hdr.ndeltas = 1;
cluster_itl_hdr.reserved = 0;
cluster_itl_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
cluster_itl_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
cluster_itl_delta.slot_idx = cluster_itl_slot;
cluster_itl_delta.flags_after = ITL_FLAG_ACTIVE;
cluster_itl_delta.xid = xid;
cluster_itl_delta.write_scn = ClusterPageGetItlSlots(page)[cluster_itl_slot].write_scn;
cluster_itl_delta.commit_scn = InvalidScn;
cluster_itl_delta.undo_segment_head = cluster_itl_uba;
xlrec.flags |= XLH_DELETE_ITL_DELTA;
}
Expand Down Expand Up @@ -6464,7 +6461,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
XLogRecPtr recptr;
#ifdef USE_PGRAC_CLUSTER
xl_heap_itl_delta_block hdr;
xl_heap_itl_delta_v2 delta;
xl_heap_itl_delta_v3 delta;
#endif

XLogBeginInsert();
Expand All @@ -6485,15 +6482,15 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,

#ifdef USE_PGRAC_CLUSTER
/*
* PGRAC (spec-3.4d D4 WAL emit / Q4 A2): append v2 40B ITL delta
* PGRAC (spec-3.4d D4 WAL emit / Q4 A2): append v3 32B ITL delta (spec-5.19 MG-D)
* + 4B block header inside same xlrec so heap_xlog_lock can replay
* the lock-only ITL slot stamp on standbys. Layout mirrors
* spec-3.4b D6 single-block delta WAL ABI.
*/
if (cluster_did_lock_stamp)
{
memset(&hdr, 0, sizeof(hdr));
hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
hdr.ndeltas = 1;
hdr.reserved = 0;
XLogRegisterData((char *) &hdr, offsetof(xl_heap_itl_delta_block, deltas));
Expand All @@ -6503,7 +6500,6 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
delta.flags_after = ITL_FLAG_LOCK_ONLY_ACTIVE;
delta.xid = xid;
delta.write_scn = cluster_lock_write_scn;
delta.commit_scn = InvalidScn;
delta.undo_segment_head = cluster_lock_uba;
XLogRegisterData((char *) &delta, sizeof(delta));
}
Expand Down Expand Up @@ -7366,7 +7362,7 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax,
Page page = BufferGetPage(buf);
#ifdef USE_PGRAC_CLUSTER
xl_heap_itl_delta_block chain_hdr;
xl_heap_itl_delta_v2 chain_delta;
xl_heap_itl_delta_v3 chain_delta;
#endif

XLogBeginInsert();
Expand All @@ -7388,7 +7384,7 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax,
if (cluster_chain_lock_stamp)
{
memset(&chain_hdr, 0, sizeof(chain_hdr));
chain_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
chain_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
chain_hdr.ndeltas = 1;
chain_hdr.reserved = 0;
XLogRegisterData((char *) &chain_hdr,
Expand All @@ -7399,7 +7395,6 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax,
chain_delta.flags_after = ITL_FLAG_LOCK_ONLY_ACTIVE;
chain_delta.xid = xid;
chain_delta.write_scn = cluster_chain_write_scn;
chain_delta.commit_scn = InvalidScn;
chain_delta.undo_segment_head = cluster_chain_uba;
XLogRegisterData((char *) &chain_delta, sizeof(chain_delta));
}
Expand Down Expand Up @@ -10590,9 +10585,9 @@ log_heap_update(Relation reln, Buffer oldbuf,
int bufflags;
#ifdef USE_PGRAC_CLUSTER
xl_heap_itl_delta_block cluster_itl_old_hdr;
xl_heap_itl_delta_v2 cluster_itl_old_delta; /* spec-3.4b D6 F9 — v2 40B */
xl_heap_itl_delta_v3 cluster_itl_old_delta; /* spec-5.19 MG-D — v3 32B (commit_scn dropped) */
xl_heap_itl_delta_block cluster_itl_new_hdr;
xl_heap_itl_delta_v2 cluster_itl_new_delta; /* spec-3.4b D6 F9 — v2 40B */
xl_heap_itl_delta_v3 cluster_itl_new_delta; /* spec-5.19 MG-D — v3 32B (commit_scn dropped) */
#endif

/* Caller should not call me on a non-WAL-logged relation */
Expand Down Expand Up @@ -10695,24 +10690,22 @@ log_heap_update(Relation reln, Buffer oldbuf,
{
cluster_itl_new_hdr.ndeltas = 1;
cluster_itl_new_hdr.reserved = 0;
cluster_itl_new_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
cluster_itl_new_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
cluster_itl_new_delta.slot_idx = cluster_itl_new_slot;
cluster_itl_new_delta.flags_after = ITL_FLAG_ACTIVE;
cluster_itl_new_delta.xid = cluster_itl_xid;
cluster_itl_new_delta.write_scn = ClusterPageGetItlSlots(BufferGetPage(newbuf))[cluster_itl_new_slot].write_scn;
cluster_itl_new_delta.commit_scn = InvalidScn;
cluster_itl_new_delta.undo_segment_head = cluster_itl_uba;
}
if (cluster_itl_old_active && oldbuf != newbuf)
{
cluster_itl_old_hdr.ndeltas = 1;
cluster_itl_old_hdr.reserved = 0;
cluster_itl_old_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
cluster_itl_old_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
cluster_itl_old_delta.slot_idx = cluster_itl_old_slot;
cluster_itl_old_delta.flags_after = ITL_FLAG_ACTIVE;
cluster_itl_old_delta.xid = cluster_itl_xid;
cluster_itl_old_delta.write_scn = ClusterPageGetItlSlots(BufferGetPage(oldbuf))[cluster_itl_old_slot].write_scn;
cluster_itl_old_delta.commit_scn = InvalidScn;
cluster_itl_old_delta.undo_segment_head = cluster_itl_uba;
}
else if (cluster_itl_old_active)
Expand All @@ -10724,12 +10717,11 @@ log_heap_update(Relation reln, Buffer oldbuf,
*/
cluster_itl_new_hdr.ndeltas = 1;
cluster_itl_new_hdr.reserved = 0;
cluster_itl_new_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V2;
cluster_itl_new_hdr.format_version = CLUSTER_ITL_DELTA_FORMAT_V3;
cluster_itl_new_delta.slot_idx = cluster_itl_old_slot;
cluster_itl_new_delta.flags_after = ITL_FLAG_ACTIVE;
cluster_itl_new_delta.xid = cluster_itl_xid;
cluster_itl_new_delta.write_scn = ClusterPageGetItlSlots(BufferGetPage(newbuf))[cluster_itl_old_slot].write_scn;
cluster_itl_new_delta.commit_scn = InvalidScn;
cluster_itl_new_delta.undo_segment_head = cluster_itl_uba;
}
}
Expand Down Expand Up @@ -11524,7 +11516,7 @@ heap_xlog_delete(XLogReaderState *record)
/*
* PGRAC (spec-3.4a D9 / spec-3.4b D6): replay block-local ITL
* delta when XLH_DELETE_ITL_DELTA is set. The helper dispatches
* by format_version (v1 24B legacy / v2 40B with UBA).
* by format_version (v1 24B legacy / v2 40B / v3 32B, all with UBA).
*/
if (xlrec->flags & XLH_DELETE_ITL_DELTA)
{
Expand Down Expand Up @@ -11666,7 +11658,7 @@ heap_xlog_insert(XLogReaderState *record)
/*
* PGRAC (spec-3.4a D9 / spec-3.4b D6): replay block-local ITL
* delta array when XLH_INSERT_ITL_DELTA is set. The helper
* dispatches by format_version (v1 24B legacy / v2 40B with UBA).
* dispatches by format_version (v1 24B legacy / v2 40B / v3 32B, all with UBA).
*/
if (xlrec->flags & XLH_INSERT_ITL_DELTA)
{
Expand Down Expand Up @@ -12343,7 +12335,7 @@ heap_xlog_lock(XLogReaderState *record)
#ifdef USE_PGRAC_CLUSTER
/*
* PGRAC (spec-3.4d D6 redo / Q4 A2): replay lock-only ITL slot
* stamp from v2 40B delta appended after xlrec. See spec-3.4b D6
* stamp from the dispatched ITL delta (v1 24B / v2 40B / v3 32B) appended after xlrec. See spec-3.4b D6
* for delta block layout. htup tuple header has no
* t_lock_itl_slot_idx field (per F2 raw_xmax scan derivation), so
* we do not patch any tuple header field; the slot stamp itself
Expand Down Expand Up @@ -12419,7 +12411,7 @@ heap_xlog_lock_updated(XLogReaderState *record)

#ifdef USE_PGRAC_CLUSTER
/* PGRAC (spec-3.4d D6 redo / follow_updates): replay lock-only
* ITL slot for successor tuple from v2 40B delta. */
* ITL slot for successor tuple from the dispatched ITL delta (v2 40B / v3 32B). */
if (xlrec->flags & XLH_LOCK_UPDATED_ITL_DELTA)
{
const char *delta_start = ((const char *) xlrec) + SizeOfHeapLockUpdated;
Expand Down
24 changes: 23 additions & 1 deletion src/backend/cluster/cluster_itl.c
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,8 @@ cluster_itl_redo_apply_block_local_delta(Page page, HeapTupleHeader htup,
delta_size = sizeof(xl_heap_itl_delta);
else if (hdr.format_version == CLUSTER_ITL_DELTA_FORMAT_V2)
delta_size = sizeof(xl_heap_itl_delta_v2);
else if (hdr.format_version == CLUSTER_ITL_DELTA_FORMAT_V3)
delta_size = sizeof(xl_heap_itl_delta_v3);
else
elog(PANIC, "spec-3.4b D6: unknown xl_heap_itl_delta_block.format_version %u",
(unsigned)hdr.format_version);
Expand Down Expand Up @@ -996,7 +998,7 @@ cluster_itl_redo_apply_block_local_delta(Page page, HeapTupleHeader htup,
* slot's existing UBA on page is preserved. Legacy ACTIVE
* stamps wrote InvalidUba to the page anyway, so reader
* 3-branch (D7) will fall back to zero triple. */
} else {
} else if (hdr.format_version == CLUSTER_ITL_DELTA_FORMAT_V2) {
xl_heap_itl_delta_v2 d;

memcpy(&d, p, sizeof(d));
Expand All @@ -1006,6 +1008,24 @@ cluster_itl_redo_apply_block_local_delta(Page page, HeapTupleHeader htup,
d_write_scn = d.write_scn;
d_commit_scn = d.commit_scn;
d_uba = d.undo_segment_head;
} else {
/* CLUSTER_ITL_DELTA_FORMAT_V3 (delta_size dispatch above already
* PANICked on any other value). */
xl_heap_itl_delta_v3 d;

memcpy(&d, p, sizeof(d));
slot_idx = d.slot_idx;
flags_after = d.flags_after;
d_xid = d.xid;
d_write_scn = d.write_scn;
/* spec-5.19 MG-D: v3 elides the write-time commit_scn (it is
* always InvalidScn for the ACTIVE / LOCK_ONLY_ACTIVE transitions
* the write path emits). Reconstruct it as InvalidScn. If a v3
* delta ever carries ITL_FLAG_COMMITTED, the COMMITTED-requires-
* valid-SCN guard below fails closed (PANIC) -- v3 must never be
* used for a COMMITTED transition. */
d_commit_scn = InvalidScn;
d_uba = d.undo_segment_head;
}

if (flags_after == ITL_FLAG_COMMITTED && !SCN_VALID(d_commit_scn))
Expand Down Expand Up @@ -1085,6 +1105,8 @@ cluster_itl_wal_block_consumed_bytes(const char *itl_block_start)
delta_size = sizeof(xl_heap_itl_delta);
else if (hdr.format_version == CLUSTER_ITL_DELTA_FORMAT_V2)
delta_size = sizeof(xl_heap_itl_delta_v2);
else if (hdr.format_version == CLUSTER_ITL_DELTA_FORMAT_V3)
delta_size = sizeof(xl_heap_itl_delta_v3);
else
elog(PANIC, "spec-3.4b D6: unknown xl_heap_itl_delta_block.format_version %u",
(unsigned)hdr.format_version);
Expand Down
49 changes: 49 additions & 0 deletions src/include/access/heapam_xlog.h
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,34 @@ StaticAssertDecl(offsetof(xl_heap_itl_delta_block, deltas) == 8,
* falls back to zero triple → PG-native).
* xl_heap_itl_delta_block.format_version == 1 → v2 (40B deltas;
* UBA bytes restored from delta).
* xl_heap_itl_delta_block.format_version == 2 → v3 (32B deltas;
* UBA bytes restored from delta; commit_scn elided, see below).
* Other values → PANIC (corruption).
*
* PGRAC (spec-5.19 MG-D): v3 ITL delta drops the write-time commit_scn
* field (8B). Every write-path emit site (heap_insert / multi_insert /
* delete / lock / lock-chain / update old+new) only ever stamps an
* ITL_FLAG_ACTIVE / ITL_FLAG_LOCK_ONLY_ACTIVE transition, for which
* commit_scn is *always* InvalidScn at write time (the slot is not yet
* committed -- COMMITTED stamping happens later via the commit-time /
* delayed-cleanout page mutation, which is FPI-logged, not via a write-path
* delta). Dropping an always-Invalid field is therefore lossless: redo
* reconstructs commit_scn = InvalidScn for every v3 delta. This shrinks
* the per-mutating-record heap-ITL WAL footprint from 8 + 40 == 48 B to
* 8 + 32 == 40 B (MG-D measure baseline). The COMMITTED-requires-valid-SCN
* redo guard (heap_redo) still fires for any v3 delta that somehow carries
* ITL_FLAG_COMMITTED, so a mis-emitted COMMITTED v3 delta fails closed
* (PANIC) rather than installing a committed slot with InvalidScn.
*
* Wire-stable layout (cluster_unit test_cluster_itl_wal enforces):
* xl_heap_itl_delta_v3 (32 bytes):
* offset 0, 2B : slot_idx
* offset 2, 2B : flags_after
* offset 4, 4B : xid
* offset 8, 8B : write_scn
* offset 16, 16B : undo_segment_head (UBA; InvalidUba on finish
* deltas that do not re-bind -- same semantic as v2)
*
* Wire-stable layout (cluster_unit test_cluster_itl_wal enforces):
* xl_heap_itl_delta_v2 (40 bytes):
* offset 0, 2B : slot_idx
Expand All @@ -567,6 +593,7 @@ StaticAssertDecl(offsetof(xl_heap_itl_delta_block, deltas) == 8,
*/
#define CLUSTER_ITL_DELTA_FORMAT_V1 ((uint32) 0)
#define CLUSTER_ITL_DELTA_FORMAT_V2 ((uint32) 1)
#define CLUSTER_ITL_DELTA_FORMAT_V3 ((uint32) 2)

typedef struct xl_heap_itl_delta_v2
{
Expand All @@ -593,6 +620,28 @@ StaticAssertDecl(offsetof(xl_heap_itl_delta_v2, commit_scn) == 16,
StaticAssertDecl(offsetof(xl_heap_itl_delta_v2, undo_segment_head) == 24,
"spec-3.4b D6 — undo_segment_head at offset 24");

typedef struct xl_heap_itl_delta_v3
{
uint16 slot_idx; /* offset 0, 2B */
uint16 flags_after; /* offset 2, 2B (ClusterItlFlags) */
TransactionId xid; /* offset 4, 4B */
SCN write_scn; /* offset 8, 8B */
UBA undo_segment_head; /* offset 16, 16B (commit_scn elided) */
} xl_heap_itl_delta_v3;

StaticAssertDecl(sizeof(xl_heap_itl_delta_v3) == 32,
"spec-5.19 MG-D — xl_heap_itl_delta_v3 must be 32 bytes (v2 40B minus the always-Invalid 8B commit_scn)");
StaticAssertDecl(offsetof(xl_heap_itl_delta_v3, slot_idx) == 0,
"spec-5.19 MG-D — slot_idx at offset 0");
StaticAssertDecl(offsetof(xl_heap_itl_delta_v3, flags_after) == 2,
"spec-5.19 MG-D — flags_after at offset 2");
StaticAssertDecl(offsetof(xl_heap_itl_delta_v3, xid) == 4,
"spec-5.19 MG-D — xid at offset 4");
StaticAssertDecl(offsetof(xl_heap_itl_delta_v3, write_scn) == 8,
"spec-5.19 MG-D — write_scn at offset 8");
StaticAssertDecl(offsetof(xl_heap_itl_delta_v3, undo_segment_head) == 16,
"spec-5.19 MG-D — undo_segment_head at offset 16 (commit_scn dropped vs v2)");


#endif /* USE_PGRAC_CLUSTER */

Expand Down
9 changes: 8 additions & 1 deletion src/include/catalog/catversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,14 @@
/* spec-5.18: permanent node removal — pg_cluster_node_removal_state SRF (oid 8963)
* + pg_cluster_remove_node UDF (oid 8964) + pg_cluster_membership +2 cols
* (removed/removed_epoch) + 53R63/53R64 SQLSTATE. Bump 202606320 -> 202606330. */
#define CATALOG_VERSION_NO 202606330
/* spec-5.19 MG-D (2026-06-29): heap-ITL WAL delta v3 — new xl_heap_itl_delta_v3
* (32B) + CLUSTER_ITL_DELTA_FORMAT_V3; the always-Invalid write-time commit_scn
* (8B) is dropped from every write-path ITL delta, shrinking the per-mutating-
* record footprint 8+40==48B -> 8+32==40B. Redo keeps v1/v2 branches for
* backward replay and reconstructs commit_scn=InvalidScn for v3. No catalog
* surface change; the bump fences an old binary from replaying v3-format WAL
* (unknown format_version -> redo PANIC). Bump 202606330 -> 202606340. */
#define CATALOG_VERSION_NO 202606340

/* spec-5.13 (2026-06-27): clean-leave catalog surface — cluster_get_clean_leave_state
* SRF (oid 8960) + pg_cluster_clean_leave_state view + pg_cluster_clean_leave_request
Expand Down
Loading
Loading