From 9fe5e544cd8598f7f10d614fd723dd05ddea1f2c Mon Sep 17 00:00:00 2001 From: Zhijie Hou Date: Mon, 19 Oct 2026 17:06:46 +0800 Subject: [PATCH 1/2] Top-up fixes for tracking transactions in BUILDING_SNAPSHOT. 1. Accumulate XLOG_XACT_INVALIDATIONS in SNAPBUILD_BUILDING_SNAPSHOT instead of skipping them via has_snapshot. A transaction that executes DDL during BUILDING_SNAPSHOT may remain open and commit in SNAPBUILD_FULL_SNAPSHOT, at which point SnapBuildDistributeSnapshotAndInval() must distribute its invalidation messages to concurrent transactions. Drop the Assert(txn->ninvalidations == 0) in ReorderBufferForget() for transactions without a base snapshot, as transactions that commit while still in BUILDING_SNAPSHOT have no base snapshot and do not need their invalidations executed. 2. In SnapBuildCommitTxn(), check the last subtransaction XID (when present) rather than only the top-level XID against builder->next_phase_at in SNAPBUILD_BUILDING_SNAPSHOT. A transaction whose top-level XID precedes next_phase_at can still spawn a catalog-modifying subtransaction with an XID >= next_phase_at during BUILDING_SNAPSHOT, which must be tracked for snapshots built in FULL_SNAPSHOT. 3. Handle XLOG_HEAP2_NEW_CID in a single place in heap2_decode() and mark catalog changes during fast-forward mode as well. Author: Zhijie Hou --- src/backend/replication/logical/decode.c | 39 ++++++++----------- .../replication/logical/reorderbuffer.c | 7 +++- src/backend/replication/logical/snapbuild.c | 7 +++- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index 8d0c2b9cba5..4196d448914 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -298,24 +298,18 @@ xact_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { TransactionId xid; xl_xact_invals *invals; - bool has_snapshot; xid = XLogRecGetXid(r); invals = (xl_xact_invals *) XLogRecGetData(r); - has_snapshot = - SnapBuildCurrentState(builder) >= SNAPBUILD_FULL_SNAPSHOT; /* * Execute the invalidations for xid-less transactions, * otherwise, accumulate them so that they can be processed at * the commit time. - * - * Note that we only need to do this when we are not fast-forwarding - * and there is a snapshot. */ if (TransactionIdIsValid(xid)) { - if (!ctx->fast_forward && has_snapshot) + if (!ctx->fast_forward) ReorderBufferAddInvalidations(reorder, xid, buf->origptr, invals->nmsgs, @@ -323,7 +317,7 @@ xact_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr); } - else if (!ctx->fast_forward && has_snapshot) + else if (!ctx->fast_forward) ReorderBufferImmediateInvalidation(ctx->reorder, invals->nmsgs, invals->msgs); @@ -445,20 +439,8 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) * determining the candidate catalog_xmin for the replication slot. See * SnapBuildProcessRunningXacts(). */ - if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT) - { - /* - * If we are building snapshot and the xlog means a catalog - * change, we need to mark it in the reorder buffer. - * - * Now only XLOG_HEAP2_NEW_CID means a catalog change. - */ - if (SnapBuildCurrentState(builder) >= SNAPBUILD_BUILDING_SNAPSHOT && - TransactionIdIsValid(xid) && info == XLOG_HEAP2_NEW_CID) - ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr); - + if (SnapBuildCurrentState(builder) < SNAPBUILD_BUILDING_SNAPSHOT) return; - } switch (info) { @@ -469,13 +451,26 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) DecodeMultiInsert(ctx, buf); break; case XLOG_HEAP2_NEW_CID: - if (!ctx->fast_forward) + if (!ctx->fast_forward && + SnapBuildCurrentState(builder) >= SNAPBUILD_FULL_SNAPSHOT) { xl_heap_new_cid *xlrec; xlrec = (xl_heap_new_cid *) XLogRecGetData(buf->record); SnapBuildProcessNewCid(builder, xid, buf->origptr, xlrec); } + else + { + /* + * In BUILDING_SNAPSHOT or fast-forward mode, the transaction + * will not be replayed, so tuple CID mappings and command IDs + * are not needed, but we still must mark the transaction as + * having catalog changes so that the historic snapshot tracks + * it. + */ + ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, + buf->origptr); + } break; case XLOG_HEAP2_REWRITE: diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 6c4eadc9972..7f2e32a944a 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -3236,12 +3236,15 @@ ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn) * are any. Even if we're not interested in the transaction's contents, it * could have manipulated the catalog and we need to update the caches * according to that. + * + * Note that transactions committing in SNAPBUILD_BUILDING_SNAPSHOT will + * not have a base snapshot even if they accumulated invalidation + * messages; no snapshot has been built or used to access catalogs yet, so + * those invalidations do not need to be executed. */ if (txn->base_snapshot != NULL && txn->ninvalidations > 0) ReorderBufferImmediateInvalidation(rb, txn->ninvalidations, txn->invalidations); - else - Assert(txn->ninvalidations == 0); /* remove potential on-disk data, and deallocate */ ReorderBufferCleanupTXN(rb, txn); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 765e25b03f1..b60fa953070 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -996,10 +996,15 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid, /* * Transactions preceding BUILDING_SNAPSHOT will neither be decoded, nor * will they be part of a snapshot. So we don't need to record anything. + * Note that a transaction whose top-level XID precedes next_phase_at may + * still have subtransactions assigned during BUILDING_SNAPSHOT (>= + * next_phase_at); since subxacts are ordered by XID assignment (see + * AtSubCommit_childXids), check the last subxact XID if any. */ if (builder->state == SNAPBUILD_START || (builder->state == SNAPBUILD_BUILDING_SNAPSHOT && - TransactionIdPrecedes(xid, builder->next_phase_at))) + TransactionIdPrecedes(nsubxacts > 0 ? subxacts[nsubxacts - 1] : xid, + builder->next_phase_at))) { /* ensure that only commits after this are getting replayed */ if (builder->start_decoding_at <= lsn) -- 2.50.1 (Apple Git-155)