/*-------------------------------------------------------------------------
*
* snapbuild . c
*
* Infrastructure for building historic catalog snapshots based on contents
* of the WAL , for the purpose of decoding heapam . c style values in the
* WAL .
*
* NOTES :
*
* We build snapshots which can * only * be used to read catalog contents and we
* do so by reading and interpreting the WAL stream . The aim is to build a
* snapshot that behaves the same as a freshly taken MVCC snapshot would have
* at the time the XLogRecord was generated .
*
* To build the snapshots we reuse the infrastructure built for Hot
* Standby . The in - memory snapshots we build look different than HS ' because
* we have different needs . To successfully decode data from the WAL we only
* need to access catalog tables and ( sys | rel | cat ) cache , not the actual user
* tables since the data we decode is wholly contained in the WAL
* records . Also , our snapshots need to be different in comparison to normal
* MVCC ones because in contrast to those we cannot fully rely on the clog and
* pg_subtrans for information about committed transactions because they might
* commit in the future from the POV of the WAL entry we ' re currently
* decoding . This definition has the advantage that we only need to prevent
* removal of catalog rows , while normal table ' s rows can still be
* removed . This is achieved by using the replication slot mechanism .
*
* As the percentage of transactions modifying the catalog normally is fairly
* small in comparisons to ones only manipulating user data , we keep track of
* the committed catalog modifying ones inside [ xmin , xmax ) instead of keeping
* track of all running transactions like it ' s done in a normal snapshot . Note
* that we ' re generally only looking at transactions that have acquired an
* xid . That is we keep a list of transactions between snapshot - > ( xmin , xmax )
* that we consider committed , everything else is considered aborted / in
* progress . That also allows us not to care about subtransactions before they
* have committed which means this module , in contrast to HS , doesn ' t have to
* care about suboverflowed subtransactions and similar .
*
* One complexity of doing this is that to e . g . handle mixed DDL / DML
* transactions we need Snapshots that see intermediate versions of the
* catalog in a transaction . During normal operation this is achieved by using
* CommandIds / cmin / cmax . The problem with that however is that for space
* efficiency reasons , the cmin and cmax are not included in WAL records . We
* cannot read the cmin / cmax from the tuple itself , either , because it is
* reset on crash recovery . Even if we could , we could not decode combocids
* which are only tracked in the original backend ' s memory . To work around
* that , heapam writes an extra WAL record ( XLOG_HEAP2_NEW_CID ) every time a
* catalog row is modified , which includes the cmin and cmax of the
* tuple . During decoding , we insert the ctid - > ( cmin , cmax ) mappings into the
* reorder buffer , and use them at visibility checks instead of the cmin / cmax
* on the tuple itself . Check the reorderbuffer . c ' s comment above
* ResolveCminCmaxDuringDecoding ( ) for details .
*
* To facilitate all this we need our own visibility routine , as the normal
* ones are optimized for different usecases .
*
* To replace the normal catalog snapshots with decoding ones use the
* SetupHistoricSnapshot ( ) and TeardownHistoricSnapshot ( ) functions .
*
*
*
* The snapbuild machinery is starting up in several stages , as illustrated
* by the following graph describing the SnapBuild - > state transitions :
*
* + - - - - - - - - - - - - - - - - - - - - - - - - - +
* + - - - - | START | - - - - - - - - - - - - - +
* | + - - - - - - - - - - - - - - - - - - - - - - - - - + |
* | | |
* | | |
* | running_xacts # 1 |
* | | |
* | | |
* | v |
* | + - - - - - - - - - - - - - - - - - - - - - - - - - + v
* | | BUILDING_SNAPSHOT | - - - - - - - - - - - - > |
* | + - - - - - - - - - - - - - - - - - - - - - - - - - + |
* | | |
* | | |
* | running_xacts # 2 , xacts from # 1 finished |
* | | |
* | | |
* | v |
* | + - - - - - - - - - - - - - - - - - - - - - - - - - + v
* | | FULL_SNAPSHOT | - - - - - - - - - - - - > |
* | + - - - - - - - - - - - - - - - - - - - - - - - - - + |
* | | |
* running_xacts | saved snapshot
* with zero xacts | at running_xacts ' s lsn
* | | |
* | running_xacts with xacts from # 2 finished |
* | | |
* | v |
* | + - - - - - - - - - - - - - - - - - - - - - - - - - + |
* + - - - > | SNAPBUILD_CONSISTENT | < - - - - - - - - - - - - +
* + - - - - - - - - - - - - - - - - - - - - - - - - - +
*
* Initially the machinery is in the START stage . When an xl_running_xacts
* record is read that is sufficiently new ( above the safe xmin horizon ) ,
* there ' s a state transition . If there were no running xacts when the
* xl_running_xacts record was generated , we ' ll directly go into CONSISTENT
* state , otherwise we ' ll switch to the BUILDING_SNAPSHOT state . Having a full
* snapshot means that all transactions that start henceforth can be decoded
* in their entirety , but transactions that started previously can ' t . In
* FULL_SNAPSHOT we ' ll switch into CONSISTENT once all those previously
* running transactions have committed or aborted .
*
* Only transactions that commit after CONSISTENT state has been reached will
* be replayed , even though they might have started while still in
* FULL_SNAPSHOT . That ensures that we ' ll reach a point where no previous
* changes has been exported , but all the following ones will be . That point
* is a convenient point to initialize replication from , which is why we
* export a snapshot at that point , which * can * be used to read normal data .
*
* Copyright ( c ) 2012 - 2025 , PostgreSQL Global Development Group
*
* IDENTIFICATION
* src / backend / replication / logical / snapbuild . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include <sys/stat.h>
#include <unistd.h>
#include "access/heapam_xlog.h"
#include "access/transam.h"
#include "access/xact.h"
#include "common/file_utils.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "replication/logical.h"
#include "replication/reorderbuffer.h"
#include "replication/snapbuild.h"
#include "replication/snapbuild_internal.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/standby.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/snapshot.h"
/*
* Starting a transaction - - which we need to do while exporting a snapshot - -
* removes knowledge about the previously used resowner , so we save it here .
*/
static ResourceOwner SavedResourceOwnerDuringExport = NULL;
static bool ExportInProgress = false ;
/* ->committed and ->catchange manipulation */
static void SnapBuildPurgeOlderTxn(SnapBuild *builder);
/* snapshot building/manipulation/distribution functions */
static Snapshot SnapBuildBuildSnapshot(SnapBuild *builder);
static void SnapBuildFreeSnapshot(Snapshot snap);
static void SnapBuildSnapIncRefcount(Snapshot snap);
static void SnapBuildDistributeSnapshotAndInval(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid);
static inline bool SnapBuildXidHasCatalogChanges(SnapBuild *builder, TransactionId xid,
uint32 xinfo);
/* xlog reading helper functions for SnapBuildProcessRunningXacts */
static bool SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running);
static void SnapBuildWaitSnapshot(xl_running_xacts *running, TransactionId cutoff);
/* serialization functions */
static void SnapBuildSerialize(SnapBuild *builder, XLogRecPtr lsn);
static bool SnapBuildRestore(SnapBuild *builder, XLogRecPtr lsn);
static void SnapBuildRestoreContents(int fd, void *dest, Size size, const char *path);
/*
* Allocate a new snapshot builder .
*
* xmin_horizon is the xid > = which we can be sure no catalog rows have been
* removed , start_lsn is the LSN > = we want to replay commits .
*/
SnapBuild *
AllocateSnapshotBuilder(ReorderBuffer *reorder,
TransactionId xmin_horizon,
XLogRecPtr start_lsn,
bool need_full_snapshot,
bool in_slot_creation,
XLogRecPtr two_phase_at)
{
MemoryContext context;
MemoryContext oldcontext;
SnapBuild *builder;
/* allocate memory in own context, to have better accountability */
context = AllocSetContextCreate(CurrentMemoryContext,
"snapshot builder context" ,
ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(context);
builder = palloc0(sizeof (SnapBuild));
builder->state = SNAPBUILD_START;
builder->context = context;
builder->reorder = reorder;
/* Other struct members initialized by zeroing via palloc0 above */
builder->committed.xcnt = 0 ;
builder->committed.xcnt_space = 128 ; /* arbitrary number */
builder->committed.xip =
palloc0(builder->committed.xcnt_space * sizeof (TransactionId));
builder->committed.includes_all_transactions = true ;
builder->catchange.xcnt = 0 ;
builder->catchange.xip = NULL;
builder->initial_xmin_horizon = xmin_horizon;
builder->start_decoding_at = start_lsn;
builder->in_slot_creation = in_slot_creation;
builder->building_full_snapshot = need_full_snapshot;
builder->two_phase_at = two_phase_at;
MemoryContextSwitchTo(oldcontext);
return builder;
}
/*
* Free a snapshot builder .
*/
void
FreeSnapshotBuilder(SnapBuild *builder)
{
MemoryContext context = builder->context;
/* free snapshot explicitly, that contains some error checking */
if (builder->snapshot != NULL)
{
SnapBuildSnapDecRefcount(builder->snapshot);
builder->snapshot = NULL;
}
/* other resources are deallocated via memory context reset */
MemoryContextDelete(context);
}
/*
* Free an unreferenced snapshot that has previously been built by us .
*/
static void
SnapBuildFreeSnapshot(Snapshot snap)
{
/* make sure we don't get passed an external snapshot */
Assert(snap->snapshot_type == SNAPSHOT_HISTORIC_MVCC);
/* make sure nobody modified our snapshot */
Assert(snap->curcid == FirstCommandId);
Assert(!snap->suboverflowed);
Assert(!snap->takenDuringRecovery);
Assert(snap->regd_count == 0 );
/* slightly more likely, so it's checked even without c-asserts */
if (snap->copied)
elog(ERROR, "cannot free a copied snapshot" );
if (snap->active_count)
elog(ERROR, "cannot free an active snapshot" );
pfree(snap);
}
/*
* In which state of snapshot building are we ?
*/
SnapBuildState
SnapBuildCurrentState(SnapBuild *builder)
{
return builder->state;
}
/*
* Return the LSN at which the two - phase decoding was first enabled .
*/
XLogRecPtr
SnapBuildGetTwoPhaseAt(SnapBuild *builder)
{
return builder->two_phase_at;
}
/*
* Set the LSN at which two - phase decoding is enabled .
*/
void
SnapBuildSetTwoPhaseAt(SnapBuild *builder, XLogRecPtr ptr)
{
builder->two_phase_at = ptr;
}
/*
* Should the contents of transaction ending at ' ptr ' be decoded ?
*/
bool
SnapBuildXactNeedsSkip(SnapBuild *builder, XLogRecPtr ptr)
{
return ptr < builder->start_decoding_at;
}
/*
* Increase refcount of a snapshot .
*
* This is used when handing out a snapshot to some external resource or when
* adding a Snapshot as builder - > snapshot .
*/
static void
SnapBuildSnapIncRefcount(Snapshot snap)
{
snap->active_count++;
}
/*
* Decrease refcount of a snapshot and free if the refcount reaches zero .
*
* Externally visible , so that external resources that have been handed an
* IncRef ' ed Snapshot can adjust its refcount easily .
*/
void
SnapBuildSnapDecRefcount(Snapshot snap)
{
/* make sure we don't get passed an external snapshot */
Assert(snap->snapshot_type == SNAPSHOT_HISTORIC_MVCC);
/* make sure nobody modified our snapshot */
Assert(snap->curcid == FirstCommandId);
Assert(!snap->suboverflowed);
Assert(!snap->takenDuringRecovery);
Assert(snap->regd_count == 0 );
Assert(snap->active_count > 0 );
/* slightly more likely, so it's checked even without casserts */
if (snap->copied)
elog(ERROR, "cannot free a copied snapshot" );
snap->active_count--;
if (snap->active_count == 0 )
SnapBuildFreeSnapshot(snap);
}
/*
* Build a new snapshot , based on currently committed catalog - modifying
* transactions .
*
* In - progress transactions with catalog access are * not * allowed to modify
* these snapshots ; they have to copy them and fill in appropriate - > curcid
* and - > subxip / subxcnt values .
*/
static Snapshot
SnapBuildBuildSnapshot(SnapBuild *builder)
{
Snapshot snapshot;
Size ssize;
Assert(builder->state >= SNAPBUILD_FULL_SNAPSHOT);
ssize = sizeof (SnapshotData)
+ sizeof (TransactionId) * builder->committed.xcnt
+ sizeof (TransactionId) * 1 /* toplevel xid */ ;
snapshot = MemoryContextAllocZero(builder->context, ssize);
snapshot->snapshot_type = SNAPSHOT_HISTORIC_MVCC;
/*
* We misuse the original meaning of SnapshotData ' s xip and subxip fields
* to make the more fitting for our needs .
*
* In the ' xip ' array we store transactions that have to be treated as
* committed . Since we will only ever look at tuples from transactions
* that have modified the catalog it ' s more efficient to store those few
* that exist between xmin and xmax ( frequently there are none ) .
*
* Snapshots that are used in transactions that have modified the catalog
* also use the ' subxip ' array to store their toplevel xid and all the
* subtransaction xids so we can recognize when we need to treat rows as
* visible that are not in xip but still need to be visible . Subxip only
* gets filled when the transaction is copied into the context of a
* catalog modifying transaction since we otherwise share a snapshot
* between transactions . As long as a txn hasn ' t modified the catalog it
* doesn ' t need to treat any uncommitted rows as visible , so there is no
* need for those xids .
*
* Both arrays are qsort ' ed so that we can use bsearch ( ) on them .
*/
Assert(TransactionIdIsNormal(builder->xmin));
Assert(TransactionIdIsNormal(builder->xmax));
snapshot->xmin = builder->xmin;
snapshot->xmax = builder->xmax;
/* store all transactions to be treated as committed by this snapshot */
snapshot->xip =
(TransactionId *) ((char *) snapshot + sizeof (SnapshotData));
snapshot->xcnt = builder->committed.xcnt;
memcpy(snapshot->xip,
builder->committed.xip,
builder->committed.xcnt * sizeof (TransactionId));
/* sort so we can bsearch() */
qsort(snapshot->xip, snapshot->xcnt, sizeof (TransactionId), xidComparator);
/*
* Initially , subxip is empty , i . e . it ' s a snapshot to be used by
* transactions that don ' t modify the catalog . Will be filled by
* ReorderBufferCopySnap ( ) if necessary .
*/
snapshot->subxcnt = 0 ;
snapshot->subxip = NULL;
snapshot->suboverflowed = false ;
snapshot->takenDuringRecovery = false ;
snapshot->copied = false ;
snapshot->curcid = FirstCommandId;
snapshot->active_count = 0 ;
snapshot->regd_count = 0 ;
snapshot->snapXactCompletionCount = 0 ;
return snapshot;
}
/*
* Build the initial slot snapshot and convert it to a normal snapshot that
* is understood by HeapTupleSatisfiesMVCC .
*
* The snapshot will be usable directly in current transaction or exported
* for loading in different transaction .
*/
Snapshot
SnapBuildInitialSnapshot(SnapBuild *builder)
{
Snapshot snap;
TransactionId xid;
TransactionId safeXid;
TransactionId *newxip;
int newxcnt = 0 ;
Assert(XactIsoLevel == XACT_REPEATABLE_READ);
Assert(builder->building_full_snapshot);
/* don't allow older snapshots */
InvalidateCatalogSnapshot(); /* about to overwrite MyProc->xmin */
if (HaveRegisteredOrActiveSnapshot())
elog(ERROR, "cannot build an initial slot snapshot when snapshots exist" );
Assert(!HistoricSnapshotActive());
if (builder->state != SNAPBUILD_CONSISTENT)
elog(ERROR, "cannot build an initial slot snapshot before reaching a consistent state" );
if (!builder->committed.includes_all_transactions)
elog(ERROR, "cannot build an initial slot snapshot, not all transactions are monitored anymore" );
/* so we don't overwrite the existing value */
if (TransactionIdIsValid(MyProc->xmin))
elog(ERROR, "cannot build an initial slot snapshot when MyProc->xmin already is valid" );
snap = SnapBuildBuildSnapshot(builder);
/*
* We know that snap - > xmin is alive , enforced by the logical xmin
* mechanism . Due to that we can do this without locks , we ' re only
* changing our own value .
*
* Building an initial snapshot is expensive and an unenforced xmin
* horizon would have bad consequences , therefore always double - check that
* the horizon is enforced .
*/
LWLockAcquire(ProcArrayLock, LW_SHARED);
safeXid = GetOldestSafeDecodingTransactionId(false );
LWLockRelease(ProcArrayLock);
if (TransactionIdFollows(safeXid, snap->xmin))
elog(ERROR, "cannot build an initial slot snapshot as oldest safe xid %u follows snapshot's xmin %u" ,
safeXid, snap->xmin);
MyProc->xmin = snap->xmin;
/* allocate in transaction context */
newxip = (TransactionId *)
palloc(sizeof (TransactionId) * GetMaxSnapshotXidCount());
/*
* snapbuild . c builds transactions in an " inverted " manner , which means it
* stores committed transactions in - > xip , not ones in progress . Build a
* classical snapshot by marking all non - committed transactions as
* in - progress . This can be expensive .
*/
for (xid = snap->xmin; NormalTransactionIdPrecedes(xid, snap->xmax);)
{
void *test;
/*
* Check whether transaction committed using the decoding snapshot
* meaning of - > xip .
*/
test = bsearch(&xid, snap->xip, snap->xcnt,
sizeof (TransactionId), xidComparator);
if (test == NULL)
{
if (newxcnt >= GetMaxSnapshotXidCount())
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("initial slot snapshot too large" )));
newxip[newxcnt++] = xid;
}
TransactionIdAdvance(xid);
}
/* adjust remaining snapshot fields as needed */
snap->snapshot_type = SNAPSHOT_MVCC;
snap->xcnt = newxcnt;
snap->xip = newxip;
return snap;
}
/*
* Export a snapshot so it can be set in another session with SET TRANSACTION
* SNAPSHOT .
*
* For that we need to start a transaction in the current backend as the
* importing side checks whether the source transaction is still open to make
* sure the xmin horizon hasn ' t advanced since then .
*/
const char *
SnapBuildExportSnapshot(SnapBuild *builder)
{
Snapshot snap;
char *snapname;
if (IsTransactionOrTransactionBlock())
elog(ERROR, "cannot export a snapshot from within a transaction" );
if (SavedResourceOwnerDuringExport)
elog(ERROR, "can only export one snapshot at a time" );
SavedResourceOwnerDuringExport = CurrentResourceOwner;
ExportInProgress = true ;
StartTransactionCommand();
/* There doesn't seem to a nice API to set these */
XactIsoLevel = XACT_REPEATABLE_READ;
XactReadOnly = true ;
snap = SnapBuildInitialSnapshot(builder);
/*
* now that we ' ve built a plain snapshot , make it active and use the
* normal mechanisms for exporting it
*/
snapname = ExportSnapshot(snap);
ereport(LOG,
(errmsg_plural("exported logical decoding snapshot: \" %s\" with %u transaction ID" ,
"exported logical decoding snapshot: \" %s\" with %u transaction IDs" ,
snap->xcnt,
snapname, snap->xcnt)));
return snapname;
}
/*
* Ensure there is a snapshot and if not build one for current transaction .
*/
Snapshot
SnapBuildGetOrBuildSnapshot(SnapBuild *builder)
{
Assert(builder->state == SNAPBUILD_CONSISTENT);
/* only build a new snapshot if we don't have a prebuilt one */
if (builder->snapshot == NULL)
{
builder->snapshot = SnapBuildBuildSnapshot(builder);
/* increase refcount for the snapshot builder */
SnapBuildSnapIncRefcount(builder->snapshot);
}
return builder->snapshot;
}
/*
* Reset a previously SnapBuildExportSnapshot ( ) ' ed snapshot if there is
* any . Aborts the previously started transaction and resets the resource
* owner back to its original value .
*/
void
SnapBuildClearExportedSnapshot(void )
{
ResourceOwner tmpResOwner;
/* nothing exported, that is the usual case */
if (!ExportInProgress)
return ;
if (!IsTransactionState())
elog(ERROR, "clearing exported snapshot in wrong transaction state" );
/*
* AbortCurrentTransaction ( ) takes care of resetting the snapshot state ,
* so remember SavedResourceOwnerDuringExport .
*/
tmpResOwner = SavedResourceOwnerDuringExport;
/* make sure nothing could have ever happened */
AbortCurrentTransaction();
CurrentResourceOwner = tmpResOwner;
}
/*
* Clear snapshot export state during transaction abort .
*/
void
SnapBuildResetExportedSnapshotState(void )
{
SavedResourceOwnerDuringExport = NULL;
ExportInProgress = false ;
}
/*
* Handle the effects of a single heap change , appropriate to the current state
* of the snapshot builder and returns whether changes made at ( xid , lsn ) can
* be decoded .
*/
bool
SnapBuildProcessChange(SnapBuild *builder, TransactionId xid, XLogRecPtr lsn)
{
/*
* We can ' t handle data in transactions if we haven ' t built a snapshot
* yet , so don ' t store them .
*/
if (builder->state < SNAPBUILD_FULL_SNAPSHOT)
return false ;
/*
* No point in keeping track of changes in transactions that we don ' t have
* enough information about to decode . This means that they started before
* we got into the SNAPBUILD_FULL_SNAPSHOT state .
*/
if (builder->state < SNAPBUILD_CONSISTENT &&
TransactionIdPrecedes(xid, builder->next_phase_at))
return false ;
/*
* If the reorderbuffer doesn ' t yet have a snapshot , add one now , it will
* be needed to decode the change we ' re currently processing .
*/
if (!ReorderBufferXidHasBaseSnapshot(builder->reorder, xid))
{
/* only build a new snapshot if we don't have a prebuilt one */
if (builder->snapshot == NULL)
{
builder->snapshot = SnapBuildBuildSnapshot(builder);
/* increase refcount for the snapshot builder */
SnapBuildSnapIncRefcount(builder->snapshot);
}
/*
* Increase refcount for the transaction we ' re handing the snapshot
* out to .
*/
SnapBuildSnapIncRefcount(builder->snapshot);
ReorderBufferSetBaseSnapshot(builder->reorder, xid, lsn,
builder->snapshot);
}
return true ;
}
/*
* Do CommandId / combo CID handling after reading an xl_heap_new_cid record .
* This implies that a transaction has done some form of write to system
* catalogs .
*/
void
SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid,
XLogRecPtr lsn, xl_heap_new_cid *xlrec)
{
CommandId cid;
/*
* we only log new_cid ' s if a catalog tuple was modified , so mark the
* transaction as containing catalog modifications
*/
ReorderBufferXidSetCatalogChanges(builder->reorder, xid, lsn);
ReorderBufferAddNewTupleCids(builder->reorder, xlrec->top_xid, lsn,
xlrec->target_locator, xlrec->target_tid,
xlrec->cmin, xlrec->cmax,
xlrec->combocid);
/* figure out new command id */
if (xlrec->cmin != InvalidCommandId &&
xlrec->cmax != InvalidCommandId)
cid = Max(xlrec->cmin, xlrec->cmax);
else if (xlrec->cmax != InvalidCommandId)
cid = xlrec->cmax;
else if (xlrec->cmin != InvalidCommandId)
cid = xlrec->cmin;
else
{
cid = InvalidCommandId; /* silence compiler */
elog(ERROR, "xl_heap_new_cid record without a valid CommandId" );
}
ReorderBufferAddNewCommandId(builder->reorder, xid, lsn, cid + 1 );
}
/*
* Add a new Snapshot and invalidation messages to all transactions we ' re
* decoding that currently are in - progress so they can see new catalog contents
* made by the transaction that just committed . This is necessary because those
* in - progress transactions will use the new catalog ' s contents from here on
* ( at the very least everything they do needs to be compatible with newer
* catalog contents ) .
*/
static void
SnapBuildDistributeSnapshotAndInval(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid)
{
dlist_iter txn_i;
ReorderBufferTXN *txn;
/*
* Iterate through all toplevel transactions . This can include
* subtransactions which we just don ' t yet know to be that , but that ' s
* fine , they will just get an unnecessary snapshot and invalidations
* queued .
*/
dlist_foreach(txn_i, &builder->reorder->toplevel_by_lsn)
{
txn = dlist_container(ReorderBufferTXN, node, txn_i.cur);
Assert(TransactionIdIsValid(txn->xid));
/*
* If we don ' t have a base snapshot yet , there are no changes in this
* transaction which in turn implies we don ' t yet need a snapshot at
* all . We ' ll add a snapshot when the first change gets queued .
*
* Similarly , we don ' t need to add invalidations to a transaction
* whose base snapshot is not yet set . Once a base snapshot is built ,
* it will include the xids of committed transactions that have
* modified the catalog , thus reflecting the new catalog contents . The
* existing catalog cache will have already been invalidated after
* processing the invalidations in the transaction that modified
* catalogs , ensuring that a fresh cache is constructed during
* decoding .
*
* NB : This works correctly even for subtransactions because
* ReorderBufferAssignChild ( ) takes care to transfer the base snapshot
* to the top - level transaction , and while iterating the changequeue
* we ' ll get the change from the subtxn .
*/
if (!ReorderBufferXidHasBaseSnapshot(builder->reorder, txn->xid))
continue ;
/*
* We don ' t need to add snapshot or invalidations to prepared
* transactions as they should not see the new catalog contents .
*/
if (rbtxn_is_prepared(txn))
continue ;
elog(DEBUG2, "adding a new snapshot and invalidations to %u at %X/%X" ,
txn->xid, LSN_FORMAT_ARGS(lsn));
/*
* increase the snapshot ' s refcount for the transaction we are handing
* it out to
*/
SnapBuildSnapIncRefcount(builder->snapshot);
ReorderBufferAddSnapshot(builder->reorder, txn->xid, lsn,
builder->snapshot);
/*
* Add invalidation messages to the reorder buffer of in - progress
* transactions except the current committed transaction , for which we
* will execute invalidations at the end .
*
* It is required , otherwise , we will end up using the stale catcache
* contents built by the current transaction even after its decoding ,
* which should have been invalidated due to concurrent catalog
* changing transaction .
*
* Distribute only the invalidation messages generated by the current
* committed transaction . Invalidation messages received from other
* transactions would have already been propagated to the relevant
* in - progress transactions . This transaction would have processed
* those invalidations , ensuring that subsequent transactions observe
* a consistent cache state .
*/
if (txn->xid != xid)
{
uint32 ninvalidations;
SharedInvalidationMessage *msgs = NULL;
ninvalidations = ReorderBufferGetInvalidations(builder->reorder,
xid, &msgs);
if (ninvalidations > 0 )
{
Assert(msgs != NULL);
ReorderBufferAddDistributedInvalidations(builder->reorder,
txn->xid, lsn,
ninvalidations, msgs);
}
}
}
}
/*
* Keep track of a new catalog changing transaction that has committed .
*/
static void
SnapBuildAddCommittedTxn(SnapBuild *builder, TransactionId xid)
{
Assert(TransactionIdIsValid(xid));
if (builder->committed.xcnt == builder->committed.xcnt_space)
{
builder->committed.xcnt_space = builder->committed.xcnt_space * 2 + 1 ;
elog(DEBUG1, "increasing space for committed transactions to %u" ,
(uint32) builder->committed.xcnt_space);
builder->committed.xip = repalloc(builder->committed.xip,
builder->committed.xcnt_space * sizeof (TransactionId));
}
/*
* TODO : It might make sense to keep the array sorted here instead of
* doing it every time we build a new snapshot . On the other hand this
* gets called repeatedly when a transaction with subtransactions commits .
*/
builder->committed.xip[builder->committed.xcnt++] = xid;
}
/*
* Remove knowledge about transactions we treat as committed or containing catalog
* changes that are smaller than - > xmin . Those won ' t ever get checked via
* the - > committed or - > catchange array , respectively . The committed xids will
* get checked via the clog machinery .
*
* We can ideally remove the transaction from catchange array once it is
* finished ( committed / aborted ) but that could be costly as we need to maintain
* the xids order in the array .
*/
static void
SnapBuildPurgeOlderTxn(SnapBuild *builder)
{
int off;
TransactionId *workspace;
int surviving_xids = 0 ;
/* not ready yet */
if (!TransactionIdIsNormal(builder->xmin))
return ;
/* TODO: Neater algorithm than just copying and iterating? */
workspace =
MemoryContextAlloc(builder->context,
builder->committed.xcnt * sizeof (TransactionId));
/* copy xids that still are interesting to workspace */
for (off = 0 ; off < builder->committed.xcnt; off++)
{
if (NormalTransactionIdPrecedes(builder->committed.xip[off],
builder->xmin))
; /* remove */
else
workspace[surviving_xids++] = builder->committed.xip[off];
}
/* copy workspace back to persistent state */
memcpy(builder->committed.xip, workspace,
surviving_xids * sizeof (TransactionId));
elog(DEBUG3, "purged committed transactions from %u to %u, xmin: %u, xmax: %u" ,
(uint32) builder->committed.xcnt, (uint32) surviving_xids,
builder->xmin, builder->xmax);
builder->committed.xcnt = surviving_xids;
pfree(workspace);
/*
* Purge xids in - > catchange as well . The purged array must also be sorted
* in xidComparator order .
*/
if (builder->catchange.xcnt > 0 )
{
/*
* Since catchange . xip is sorted , we find the lower bound of xids that
* are still interesting .
*/
for (off = 0 ; off < builder->catchange.xcnt; off++)
{
if (TransactionIdFollowsOrEquals(builder->catchange.xip[off],
builder->xmin))
break ;
}
surviving_xids = builder->catchange.xcnt - off;
if (surviving_xids > 0 )
{
memmove(builder->catchange.xip, &(builder->catchange.xip[off]),
surviving_xids * sizeof (TransactionId));
}
else
{
pfree(builder->catchange.xip);
builder->catchange.xip = NULL;
}
elog(DEBUG3, "purged catalog modifying transactions from %u to %u, xmin: %u, xmax: %u" ,
(uint32) builder->catchange.xcnt, (uint32) surviving_xids,
builder->xmin, builder->xmax);
builder->catchange.xcnt = surviving_xids;
}
}
/*
* Handle everything that needs to be done when a transaction commits
*/
void
SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
int nsubxacts, TransactionId *subxacts, uint32 xinfo)
{
int nxact;
bool needs_snapshot = false ;
bool needs_timetravel = false ;
bool sub_needs_timetravel = false ;
TransactionId xmax = 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 .
*/
if (builder->state == SNAPBUILD_START ||
(builder->state == SNAPBUILD_BUILDING_SNAPSHOT &&
TransactionIdPrecedes(xid, builder->next_phase_at)))
{
/* ensure that only commits after this are getting replayed */
if (builder->start_decoding_at <= lsn)
builder->start_decoding_at = lsn + 1 ;
return ;
}
if (builder->state < SNAPBUILD_CONSISTENT)
{
/* ensure that only commits after this are getting replayed */
if (builder->start_decoding_at <= lsn)
builder->start_decoding_at = lsn + 1 ;
/*
* If building an exportable snapshot , force xid to be tracked , even
* if the transaction didn ' t modify the catalog .
*/
if (builder->building_full_snapshot)
{
needs_timetravel = true ;
}
}
for (nxact = 0 ; nxact < nsubxacts; nxact++)
{
TransactionId subxid = subxacts[nxact];
/*
* Add subtransaction to base snapshot if catalog modifying , we don ' t
* distinguish to toplevel transactions there .
*/
if (SnapBuildXidHasCatalogChanges(builder, subxid, xinfo))
{
sub_needs_timetravel = true ;
needs_snapshot = true ;
elog(DEBUG1, "found subtransaction %u:%u with catalog changes" ,
xid, subxid);
SnapBuildAddCommittedTxn(builder, subxid);
if (NormalTransactionIdFollows(subxid, xmax))
xmax = subxid;
}
/*
* If we ' re forcing timetravel we also need visibility information
* about subtransaction , so keep track of subtransaction ' s state , even
* if not catalog modifying . Don ' t need to distribute a snapshot in
* that case .
*/
else if (needs_timetravel)
{
SnapBuildAddCommittedTxn(builder, subxid);
if (NormalTransactionIdFollows(subxid, xmax))
xmax = subxid;
}
}
/* if top-level modified catalog, it'll need a snapshot */
if (SnapBuildXidHasCatalogChanges(builder, xid, xinfo))
{
elog(DEBUG2, "found top level transaction %u, with catalog changes" ,
xid);
needs_snapshot = true ;
needs_timetravel = true ;
SnapBuildAddCommittedTxn(builder, xid);
}
else if (sub_needs_timetravel)
{
/* track toplevel txn as well, subxact alone isn't meaningful */
elog(DEBUG2, "forced transaction %u to do timetravel due to one of its subtransactions" ,
xid);
needs_timetravel = true ;
SnapBuildAddCommittedTxn(builder, xid);
}
else if (needs_timetravel)
{
elog(DEBUG2, "forced transaction %u to do timetravel" , xid);
SnapBuildAddCommittedTxn(builder, xid);
}
if (!needs_timetravel)
{
/* record that we cannot export a general snapshot anymore */
builder->committed.includes_all_transactions = false ;
}
Assert(!needs_snapshot || needs_timetravel);
/*
* Adjust xmax of the snapshot builder , we only do that for committed ,
* catalog modifying , transactions , everything else isn ' t interesting for
* us since we ' ll never look at the respective rows .
*/
if (needs_timetravel &&
(!TransactionIdIsValid(builder->xmax) ||
TransactionIdFollowsOrEquals(xmax, builder->xmax)))
{
builder->xmax = xmax;
TransactionIdAdvance(builder->xmax);
}
/* if there's any reason to build a historic snapshot, do so now */
if (needs_snapshot)
{
/*
* If we haven ' t built a complete snapshot yet there ' s no need to hand
* it out , it wouldn ' t ( and couldn ' t ) be used anyway .
*/
if (builder->state < SNAPBUILD_FULL_SNAPSHOT)
return ;
/*
* Decrease the snapshot builder ' s refcount of the old snapshot , note
* that it still will be used if it has been handed out to the
* reorderbuffer earlier .
*/
if (builder->snapshot)
SnapBuildSnapDecRefcount(builder->snapshot);
builder->snapshot = SnapBuildBuildSnapshot(builder);
/* we might need to execute invalidations, add snapshot */
if (!ReorderBufferXidHasBaseSnapshot(builder->reorder, xid))
{
SnapBuildSnapIncRefcount(builder->snapshot);
ReorderBufferSetBaseSnapshot(builder->reorder, xid, lsn,
builder->snapshot);
}
/* refcount of the snapshot builder for the new snapshot */
SnapBuildSnapIncRefcount(builder->snapshot);
/*
* Add a new catalog snapshot and invalidations messages to all
* currently running transactions .
*/
SnapBuildDistributeSnapshotAndInval(builder, lsn, xid);
}
}
/*
* Check the reorder buffer and the snapshot to see if the given transaction has
* modified catalogs .
*/
static inline bool
SnapBuildXidHasCatalogChanges(SnapBuild *builder, TransactionId xid,
uint32 xinfo)
{
if (ReorderBufferXidHasCatalogChanges(builder->reorder, xid))
return true ;
/*
* The transactions that have changed catalogs must have invalidation
* info .
*/
if (!(xinfo & XACT_XINFO_HAS_INVALS))
return false ;
/* Check the catchange XID array */
return ((builder->catchange.xcnt > 0 ) &&
(bsearch(&xid, builder->catchange.xip, builder->catchange.xcnt,
sizeof (TransactionId), xidComparator) != NULL));
}
/* -----------------------------------
* Snapshot building functions dealing with xlog records
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
/*
* Process a running xacts record , and use its information to first build a
* historic snapshot and later to release resources that aren ' t needed
* anymore .
*/
void
SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
{
ReorderBufferTXN *txn;
TransactionId xmin;
/*
* If we ' re not consistent yet , inspect the record to see whether it
* allows to get closer to being consistent . If we are consistent , dump
* our snapshot so others or we , after a restart , can use it .
*/
if (builder->state < SNAPBUILD_CONSISTENT)
{
/* returns false if there's no point in performing cleanup just yet */
if (!SnapBuildFindSnapshot(builder, lsn, running))
return ;
}
else
SnapBuildSerialize(builder, lsn);
/*
* Update range of interesting xids based on the running xacts
* information . We don ' t increase - > xmax using it , because once we are in
* a consistent state we can do that ourselves and much more efficiently
* so , because we only need to do it for catalog transactions since we
* only ever look at those .
*
* NB : We only increase xmax when a catalog modifying transaction commits
* ( see SnapBuildCommitTxn ) . Because of this , xmax can be lower than
* xmin , which looks odd but is correct and actually more efficient , since
* we hit fast paths in heapam_visibility . c .
*/
builder->xmin = running->oldestRunningXid;
/* Remove transactions we don't need to keep track off anymore */
SnapBuildPurgeOlderTxn(builder);
/*
* Advance the xmin limit for the current replication slot , to allow
* vacuum to clean up the tuples this slot has been protecting .
*
* The reorderbuffer might have an xmin among the currently running
* snapshots ; use it if so . If not , we need only consider the snapshots
* we ' ll produce later , which can ' t be less than the oldest running xid in
* the record we ' re reading now .
*/
xmin = ReorderBufferGetOldestXmin(builder->reorder);
if (xmin == InvalidTransactionId)
xmin = running->oldestRunningXid;
elog(DEBUG3, "xmin: %u, xmax: %u, oldest running: %u, oldest xmin: %u" ,
builder->xmin, builder->xmax, running->oldestRunningXid, xmin);
LogicalIncreaseXminForSlot(lsn, xmin);
/*
* Also tell the slot where we can restart decoding from . We don ' t want to
* do that after every commit because changing that implies an fsync of
* the logical slot ' s state file , so we only do it every time we see a
* running xacts record .
*
* Do so by looking for the oldest in progress transaction ( determined by
* the first LSN of any of its relevant records ) . Every transaction
* remembers the last location we stored the snapshot to disk before its
* beginning . That point is where we can restart from .
*/
/*
* Can ' t know about a serialized snapshot ' s location if we ' re not
* consistent .
*/
if (builder->state < SNAPBUILD_CONSISTENT)
return ;
txn = ReorderBufferGetOldestTXN(builder->reorder);
/*
* oldest ongoing txn might have started when we didn ' t yet serialize
* anything because we hadn ' t reached a consistent state yet .
*/
if (txn != NULL && txn->restart_decoding_lsn != InvalidXLogRecPtr)
LogicalIncreaseRestartDecodingForSlot(lsn, txn->restart_decoding_lsn);
/*
* No in - progress transaction , can reuse the last serialized snapshot if
* we have one .
*/
else if (txn == NULL &&
builder->reorder->current_restart_decoding_lsn != InvalidXLogRecPtr &&
builder->last_serialized_snapshot != InvalidXLogRecPtr)
LogicalIncreaseRestartDecodingForSlot(lsn,
builder->last_serialized_snapshot);
}
/*
* Build the start of a snapshot that ' s capable of decoding the catalog .
*
* Helper function for SnapBuildProcessRunningXacts ( ) while we ' re not yet
* consistent .
*
* Returns true if there is a point in performing internal maintenance / cleanup
* using the xl_running_xacts record .
*/
static bool
SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
{
/* ---
* Build catalog decoding snapshot incrementally using information about
* the currently running transactions . There are several ways to do that :
*
* a ) There were no running transactions when the xl_running_xacts record
* was inserted , jump to CONSISTENT immediately . We might find such a
* state while waiting on c ) ' s sub - states .
*
* b ) This ( in a previous run ) or another decoding slot serialized a
* snapshot to disk that we can use . Can ' t use this method while finding
* the start point for decoding changes as the restart LSN would be an
* arbitrary LSN but we need to find the start point to extract changes
* where we won ' t see the data for partial transactions . Also , we cannot
* use this method when a slot needs a full snapshot for export or direct
* use , as that snapshot will only contain catalog modifying transactions .
*
* c ) First incrementally build a snapshot for catalog tuples
* ( BUILDING_SNAPSHOT ) , that requires all , already in - progress ,
* transactions to finish . Every transaction starting after that
* ( FULL_SNAPSHOT state ) , has enough information to be decoded . But
* for older running transactions no viable snapshot exists yet , so
* CONSISTENT will only be reached once all of those have finished .
* - - -
*/
/*
* xl_running_xacts record is older than what we can use , we might not
* have all necessary catalog rows anymore .
*/
if (TransactionIdIsNormal(builder->initial_xmin_horizon) &&
NormalTransactionIdPrecedes(running->oldestRunningXid,
builder->initial_xmin_horizon))
{
ereport(DEBUG1,
(errmsg_internal("skipping snapshot at %X/%X while building logical decoding snapshot, xmin horizon too low" ,
LSN_FORMAT_ARGS(lsn)),
errdetail_internal("initial xmin horizon of %u vs the snapshot's %u" ,
builder->initial_xmin_horizon, running->oldestRunningXid)));
SnapBuildWaitSnapshot(running, builder->initial_xmin_horizon);
return true ;
}
/*
* a ) No transaction were running , we can jump to consistent .
*
* This is not affected by races around xl_running_xacts , because we can
* miss transaction commits , but currently not transactions starting .
*
* NB : We might have already started to incrementally assemble a snapshot ,
* so we need to be careful to deal with that .
*/
if (running->oldestRunningXid == running->nextXid)
{
if (builder->start_decoding_at == InvalidXLogRecPtr ||
builder->start_decoding_at <= lsn)
/* can decode everything after this */
builder->start_decoding_at = lsn + 1 ;
/* As no transactions were running xmin/xmax can be trivially set. */
builder->xmin = running->nextXid; /* < are finished */
builder->xmax = running->nextXid; /* >= are running */
/* so we can safely use the faster comparisons */
Assert(TransactionIdIsNormal(builder->xmin));
Assert(TransactionIdIsNormal(builder->xmax));
builder->state = SNAPBUILD_CONSISTENT;
builder->next_phase_at = InvalidTransactionId;
ereport(LOG,
(errmsg("logical decoding found consistent point at %X/%X" ,
LSN_FORMAT_ARGS(lsn)),
errdetail("There are no running transactions." )));
return false ;
}
/*
* b ) valid on disk state and while neither building full snapshot nor
* creating a slot .
*/
else if (!builder->building_full_snapshot &&
!builder->in_slot_creation &&
SnapBuildRestore(builder, lsn))
{
/* there won't be any state to cleanup */
return false ;
}
/*
* c ) transition from START to BUILDING_SNAPSHOT .
*
* In START state , and a xl_running_xacts record with running xacts is
* encountered . In that case , switch to BUILDING_SNAPSHOT state , and
* record xl_running_xacts - > nextXid . Once all running xacts have finished
* ( i . e . they ' re all > = nextXid ) , we have a complete catalog snapshot . It
* might look that we could use xl_running_xacts ' s - > xids information to
* get there quicker , but that is problematic because transactions marked
* as running , might already have inserted their commit record - it ' s
* infeasible to change that with locking .
* /
else if ( builder - > state = = SNAPBUILD_START )
{
builder - > state = SNAPBUILD_BUILDING_SNAPSHOT ;
builder - > next_phase_at = running - > nextXid ;
/ *
* Start with an xmin / xmax that ' s correct for future , when all the
* currently running transactions have finished . We ' ll update both
* while waiting for the pending transactions to finish .
* /
builder - > xmin = running - > nextXid ; / * < are finished * /
builder - > xmax = running - > nextXid ; / * > = are running * /
/ * so we can safely use the faster comparisons * /
Assert ( TransactionIdIsNormal ( builder - > xmin ) ) ;
Assert ( TransactionIdIsNormal ( builder - > xmax ) ) ;
ereport ( LOG ,
( errmsg ( " logical decoding found initial starting point at % X / % X " ,
LSN_FORMAT_ARGS ( lsn ) ) ,
errdetail ( " Waiting for transactions ( approximately % d ) older than % u to end . " ,
running - > xcnt , running - > nextXid ) ) ) ;
SnapBuildWaitSnapshot ( running , running - > nextXid ) ;
}
/ *
* c ) transition from BUILDING_SNAPSHOT to FULL_SNAPSHOT .
*
* In BUILDING_SNAPSHOT state , and this xl_running_xacts ' oldestRunningXid
* is > = than nextXid from when we switched to BUILDING_SNAPSHOT . This
* means all transactions starting afterwards have enough information to
* be decoded . Switch to FULL_SNAPSHOT .
* /
else if ( builder - > state = = SNAPBUILD_BUILDING_SNAPSHOT & &
TransactionIdPrecedesOrEquals ( builder - > next_phase_at ,
running - > oldestRunningXid ) )
{
builder - > state = SNAPBUILD_FULL_SNAPSHOT ;
builder - > next_phase_at = running - > nextXid ;
ereport ( LOG ,
( errmsg ( " logical decoding found initial consistent point at % X / % X " ,
LSN_FORMAT_ARGS ( lsn ) ) ,
errdetail ( " Waiting for transactions ( approximately % d ) older than % u to end . " ,
running - > xcnt , running - > nextXid ) ) ) ;
SnapBuildWaitSnapshot ( running , running - > nextXid ) ;
}
/ *
* c ) transition from FULL_SNAPSHOT to CONSISTENT .
*
* In FULL_SNAPSHOT state , and this xl_running_xacts ' oldestRunningXid is
* > = than nextXid from when we switched to FULL_SNAPSHOT . This means all
* transactions that are currently in progress have a catalog snapshot ,
* and all their changes have been collected . Switch to CONSISTENT .
* /
else if ( builder - > state = = SNAPBUILD_FULL_SNAPSHOT & &
TransactionIdPrecedesOrEquals ( builder - > next_phase_at ,
running - > oldestRunningXid ) )
{
builder - > state = SNAPBUILD_CONSISTENT ;
builder - > next_phase_at = InvalidTransactionId ;
ereport ( LOG ,
( errmsg ( " logical decoding found consistent point at % X / % X " ,
LSN_FORMAT_ARGS ( lsn ) ) ,
errdetail ( " There are no old transactions anymore . " ) ) ) ;
}
/ *
* We already started to track running xacts and need to wait for all
* in - progress ones to finish . We fall through to the normal processing of
* records so incremental cleanup can be performed .
* /
return true ;
}
/ * - - -
* Iterate through xids in record , wait for all older than the cutoff to
* finish . Then , if possible , log a new xl_running_xacts record .
*
* This isn ' t required for the correctness of decoding , but to :
* a ) allow isolationtester to notice that we ' re currently waiting for
* something .
* b ) log a new xl_running_xacts record where it ' d be helpful , without having
* to wait for bgwriter or checkpointer .
* - - -
* /
static void
SnapBuildWaitSnapshot ( xl_running_xacts * running , TransactionId cutoff )
{
int off ;
for ( off = 0 ; off < running - > xcnt ; off + + )
{
TransactionId xid = running - > xids [ off ] ;
/ *
* Upper layers should prevent that we ever need to wait on ourselves .
* Check anyway , since failing to do so would either result in an
* endless wait or an Assert ( ) failure .
* /
if ( TransactionIdIsCurrentTransactionId ( xid ) )
elog ( ERROR , " waiting for ourselves " ) ;
if ( TransactionIdFollows ( xid , cutoff ) )
continue ;
XactLockTableWait ( xid , NULL , NULL , XLTW_None ) ;
}
/ *
* All transactions we needed to finish finished - try to ensure there is
* another xl_running_xacts record in a timely manner , without having to
* wait for bgwriter or checkpointer to log one . During recovery we can ' t
* enforce that , so we ' ll have to wait .
* /
if ( ! RecoveryInProgress ( ) )
{
LogStandbySnapshot ( ) ;
}
}
# define SnapBuildOnDiskConstantSize \
offsetof ( SnapBuildOnDisk , builder )
# define SnapBuildOnDiskNotChecksummedSize \
offsetof ( SnapBuildOnDisk , version )
# define SNAPBUILD_MAGIC 0 x51A1E001
# define SNAPBUILD_VERSION 6
/ *
* Store / Load a snapshot from disk , depending on the snapshot builder ' s state .
*
* Supposed to be used by external ( i . e . not snapbuild . c ) code that just read
* a record that ' s a potential location for a serialized snapshot .
* /
void
SnapBuildSerializationPoint ( SnapBuild * builder , XLogRecPtr lsn )
{
if ( builder - > state < SNAPBUILD_CONSISTENT )
SnapBuildRestore ( builder , lsn ) ;
else
SnapBuildSerialize ( builder , lsn ) ;
}
/ *
* Serialize the snapshot ' builder ' at the location ' lsn ' if it hasn ' t already
* been done by another decoding process .
* /
static void
SnapBuildSerialize ( SnapBuild * builder , XLogRecPtr lsn )
{
Size needed_length ;
SnapBuildOnDisk * ondisk = NULL ;
TransactionId * catchange_xip = NULL ;
MemoryContext old_ctx ;
size_t catchange_xcnt ;
char * ondisk_c ;
int fd ;
char tmppath [ MAXPGPATH ] ;
char path [ MAXPGPATH ] ;
int ret ;
struct stat stat_buf ;
Size sz ;
Assert ( lsn ! = InvalidXLogRecPtr ) ;
Assert ( builder - > last_serialized_snapshot = = InvalidXLogRecPtr | |
builder - > last_serialized_snapshot < = lsn ) ;
/ *
* no point in serializing if we cannot continue to work immediately after
* restoring the snapshot
* /
if ( builder - > state < SNAPBUILD_CONSISTENT )
return ;
/ * consistent snapshots have no next phase * /
Assert ( builder - > next_phase_at = = InvalidTransactionId ) ;
/ *
* We identify snapshots by the LSN they are valid for . We don ' t need to
* include timelines in the name as each LSN maps to exactly one timeline
* unless the user used pg_resetwal or similar . If a user did so , there ' s
* no hope continuing to decode anyway .
* /
sprintf ( path , " % s / % X - % X . snap " ,
PG_LOGICAL_SNAPSHOTS_DIR ,
LSN_FORMAT_ARGS ( lsn ) ) ;
/ *
* first check whether some other backend already has written the snapshot
* for this LSN . It ' s perfectly fine if there ' s none , so we accept ENOENT
* as a valid state . Everything else is an unexpected error .
* /
ret = stat ( path , & stat_buf ) ;
if ( ret ! = 0 & & errno ! = ENOENT )
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not stat file \ " % s \ " : % m " , path ) ) ) ;
else if ( ret = = 0 )
{
/ *
* somebody else has already serialized to this point , don ' t overwrite
* but remember location , so we don ' t need to read old data again .
*
* To be sure it has been synced to disk after the rename ( ) from the
* tempfile filename to the real filename , we just repeat the fsync .
* That ought to be cheap because in most scenarios it should already
* be safely on disk .
* /
fsync_fname ( path , false ) ;
fsync_fname ( PG_LOGICAL_SNAPSHOTS_DIR , true ) ;
builder - > last_serialized_snapshot = lsn ;
goto out ;
}
/ *
* there is an obvious race condition here between the time we stat ( 2 ) the
* file and us writing the file . But we rename the file into place
* atomically and all files created need to contain the same data anyway ,
* so this is perfectly fine , although a bit of a resource waste . Locking
* seems like pointless complication .
* /
elog ( DEBUG1 , " serializing snapshot to % s " , path ) ;
/ * to make sure only we will write to this tempfile , include pid * /
sprintf ( tmppath , " % s / % X - % X . snap . % d . tmp " ,
PG_LOGICAL_SNAPSHOTS_DIR ,
LSN_FORMAT_ARGS ( lsn ) , MyProcPid ) ;
/ *
* Unlink temporary file if it already exists , needs to have been before a
* crash / error since we won ' t enter this function twice from within a
* single decoding slot / backend and the temporary file contains the pid of
* the current process .
* /
if ( unlink ( tmppath ) ! = 0 & & errno ! = ENOENT )
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not remove file \ " % s \ " : % m " , tmppath ) ) ) ;
old_ctx = MemoryContextSwitchTo ( builder - > context ) ;
/ * Get the catalog modifying transactions that are yet not committed * /
catchange_xip = ReorderBufferGetCatalogChangesXacts ( builder - > reorder ) ;
catchange_xcnt = dclist_count ( & builder - > reorder - > catchange_txns ) ;
needed_length = sizeof ( SnapBuildOnDisk ) +
sizeof ( TransactionId ) * ( builder - > committed . xcnt + catchange_xcnt ) ;
ondisk_c = palloc0 ( needed_length ) ;
ondisk = ( SnapBuildOnDisk * ) ondisk_c ;
ondisk - > magic = SNAPBUILD_MAGIC ;
ondisk - > version = SNAPBUILD_VERSION ;
ondisk - > length = needed_length ;
INIT_CRC32C ( ondisk - > checksum ) ;
COMP_CRC32C ( ondisk - > checksum ,
( ( char * ) ondisk ) + SnapBuildOnDiskNotChecksummedSize ,
SnapBuildOnDiskConstantSize - SnapBuildOnDiskNotChecksummedSize ) ;
ondisk_c + = sizeof ( SnapBuildOnDisk ) ;
memcpy ( & ondisk - > builder , builder , sizeof ( SnapBuild ) ) ;
/ * NULL - ify memory - only data * /
ondisk - > builder . context = NULL ;
ondisk - > builder . snapshot = NULL ;
ondisk - > builder . reorder = NULL ;
ondisk - > builder . committed . xip = NULL ;
ondisk - > builder . catchange . xip = NULL ;
/ * update catchange only on disk data * /
ondisk - > builder . catchange . xcnt = catchange_xcnt ;
COMP_CRC32C ( ondisk - > checksum ,
& ondisk - > builder ,
sizeof ( SnapBuild ) ) ;
/ * copy committed xacts * /
if ( builder - > committed . xcnt > 0 )
{
sz = sizeof ( TransactionId ) * builder - > committed . xcnt ;
memcpy ( ondisk_c , builder - > committed . xip , sz ) ;
COMP_CRC32C ( ondisk - > checksum , ondisk_c , sz ) ;
ondisk_c + = sz ;
}
/ * copy catalog modifying xacts * /
if ( catchange_xcnt > 0 )
{
sz = sizeof ( TransactionId ) * catchange_xcnt ;
memcpy ( ondisk_c , catchange_xip , sz ) ;
COMP_CRC32C ( ondisk - > checksum , ondisk_c , sz ) ;
ondisk_c + = sz ;
}
FIN_CRC32C ( ondisk - > checksum ) ;
/ * we have valid data now , open tempfile and write it there * /
fd = OpenTransientFile ( tmppath ,
O_CREAT | O_EXCL | O_WRONLY | PG_BINARY ) ;
if ( fd < 0 )
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not open file \ " % s \ " : % m " , tmppath ) ) ) ;
errno = 0 ;
pgstat_report_wait_start ( WAIT_EVENT_SNAPBUILD_WRITE ) ;
if ( ( write ( fd , ondisk , needed_length ) ) ! = needed_length )
{
int save_errno = errno ;
CloseTransientFile ( fd ) ;
/ * if write didn ' t set errno , assume problem is no disk space * /
errno = save_errno ? save_errno : ENOSPC ;
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not write to file \ " % s \ " : % m " , tmppath ) ) ) ;
}
pgstat_report_wait_end ( ) ;
/ *
* fsync the file before renaming so that even if we crash after this we
* have either a fully valid file or nothing .
*
* It ' s safe to just ERROR on fsync ( ) here because we ' ll retry the whole
* operation including the writes .
*
* TODO : Do the fsync ( ) via checkpoints / restartpoints , doing it here has
* some noticeable overhead since it ' s performed synchronously during
* decoding ?
* /
pgstat_report_wait_start ( WAIT_EVENT_SNAPBUILD_SYNC ) ;
if ( pg_fsync ( fd ) ! = 0 )
{
int save_errno = errno ;
CloseTransientFile ( fd ) ;
errno = save_errno ;
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not fsync file \ " % s \ " : % m " , tmppath ) ) ) ;
}
pgstat_report_wait_end ( ) ;
if ( CloseTransientFile ( fd ) ! = 0 )
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not close file \ " % s \ " : % m " , tmppath ) ) ) ;
fsync_fname ( PG_LOGICAL_SNAPSHOTS_DIR , true ) ;
/ *
* We may overwrite the work from some other backend , but that ' s ok , our
* snapshot is valid as well , we ' ll just have done some superfluous work .
* /
if ( rename ( tmppath , path ) ! = 0 )
{
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not rename file \ " % s \ " to \ " % s \ " : % m " ,
tmppath , path ) ) ) ;
}
/ * make sure we persist * /
fsync_fname ( path , false ) ;
fsync_fname ( PG_LOGICAL_SNAPSHOTS_DIR , true ) ;
/ *
* Now there ' s no way we can lose the dumped state anymore , remember this
* as a serialization point .
* /
builder - > last_serialized_snapshot = lsn ;
MemoryContextSwitchTo ( old_ctx ) ;
out :
ReorderBufferSetRestartPoint ( builder - > reorder ,
builder - > last_serialized_snapshot ) ;
/ * be tidy * /
if ( ondisk )
pfree ( ondisk ) ;
if ( catchange_xip )
pfree ( catchange_xip ) ;
}
/ *
* Restore the logical snapshot file contents to ' ondisk ' .
*
* ' context ' is the memory context where the catalog modifying / committed xid
* will live .
* If ' missing_ok ' is true , will not throw an error if the file is not found .
* /
bool
SnapBuildRestoreSnapshot ( SnapBuildOnDisk * ondisk , XLogRecPtr lsn ,
MemoryContext context , bool missing_ok )
{
int fd ;
pg_crc32c checksum ;
Size sz ;
char path [ MAXPGPATH ] ;
sprintf ( path , " % s / % X - % X . snap " ,
PG_LOGICAL_SNAPSHOTS_DIR ,
LSN_FORMAT_ARGS ( lsn ) ) ;
fd = OpenTransientFile ( path , O_RDONLY | PG_BINARY ) ;
if ( fd < 0 )
{
if ( missing_ok & & errno = = ENOENT )
return false ;
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not open file \ " % s \ " : % m " , path ) ) ) ;
}
/ * - - - -
* Make sure the snapshot had been stored safely to disk , that ' s normally
* cheap .
* Note that we do not need PANIC here , nobody will be able to use the
* slot without fsyncing , and saving it won ' t succeed without an fsync ( )
* either . . .
* - - - -
* /
fsync_fname ( path , false ) ;
fsync_fname ( PG_LOGICAL_SNAPSHOTS_DIR , true ) ;
/ * read statically sized portion of snapshot * /
SnapBuildRestoreContents ( fd , ondisk , SnapBuildOnDiskConstantSize , path ) ;
if ( ondisk - > magic ! = SNAPBUILD_MAGIC )
ereport ( ERROR ,
( errcode ( ERRCODE_DATA_CORRUPTED ) ,
errmsg ( " snapbuild state file \ " % s \ " has wrong magic number : % u instead of % u " ,
path , ondisk - > magic , SNAPBUILD_MAGIC ) ) ) ;
if ( ondisk - > version ! = SNAPBUILD_VERSION )
ereport ( ERROR ,
( errcode ( ERRCODE_DATA_CORRUPTED ) ,
errmsg ( " snapbuild state file \ " % s \ " has unsupported version : % u instead of % u " ,
path , ondisk - > version , SNAPBUILD_VERSION ) ) ) ;
INIT_CRC32C ( checksum ) ;
COMP_CRC32C ( checksum ,
( ( char * ) ondisk ) + SnapBuildOnDiskNotChecksummedSize ,
SnapBuildOnDiskConstantSize - SnapBuildOnDiskNotChecksummedSize ) ;
/ * read SnapBuild * /
SnapBuildRestoreContents ( fd , & ondisk - > builder , sizeof ( SnapBuild ) , path ) ;
COMP_CRC32C ( checksum , & ondisk - > builder , sizeof ( SnapBuild ) ) ;
/ * restore committed xacts information * /
if ( ondisk - > builder . committed . xcnt > 0 )
{
sz = sizeof ( TransactionId ) * ondisk - > builder . committed . xcnt ;
ondisk - > builder . committed . xip = MemoryContextAllocZero ( context , sz ) ;
SnapBuildRestoreContents ( fd , ondisk - > builder . committed . xip , sz , path ) ;
COMP_CRC32C ( checksum , ondisk - > builder . committed . xip , sz ) ;
}
/ * restore catalog modifying xacts information * /
if ( ondisk - > builder . catchange . xcnt > 0 )
{
sz = sizeof ( TransactionId ) * ondisk - > builder . catchange . xcnt ;
ondisk - > builder . catchange . xip = MemoryContextAllocZero ( context , sz ) ;
SnapBuildRestoreContents ( fd , ondisk - > builder . catchange . xip , sz , path ) ;
COMP_CRC32C ( checksum , ondisk - > builder . catchange . xip , sz ) ;
}
if ( CloseTransientFile ( fd ) ! = 0 )
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not close file \ " % s \ " : % m " , path ) ) ) ;
FIN_CRC32C ( checksum ) ;
/ * verify checksum of what we ' ve read * /
if ( ! EQ_CRC32C ( checksum , ondisk - > checksum ) )
ereport ( ERROR ,
( errcode ( ERRCODE_DATA_CORRUPTED ) ,
errmsg ( " checksum mismatch for snapbuild state file \ " % s \ " : is % u , should be % u " ,
path , checksum , ondisk - > checksum ) ) ) ;
return true ;
}
/ *
* Restore a snapshot into ' builder ' if previously one has been stored at the
* location indicated by ' lsn ' . Returns true if successful , false otherwise .
* /
static bool
SnapBuildRestore ( SnapBuild * builder , XLogRecPtr lsn )
{
SnapBuildOnDisk ondisk ;
/ * no point in loading a snapshot if we ' re already there * /
if ( builder - > state = = SNAPBUILD_CONSISTENT )
return false ;
/ * validate and restore the snapshot to ' ondisk ' * /
if ( ! SnapBuildRestoreSnapshot ( & ondisk , lsn , builder - > context , true ) )
return false ;
/ *
* ok , we now have a sensible snapshot here , figure out if it has more
* information than we have .
* /
/ *
* We are only interested in consistent snapshots for now , comparing
* whether one incomplete snapshot is more " advanced " seems to be
* unnecessarily complex .
* /
if ( ondisk . builder . state < SNAPBUILD_CONSISTENT )
goto snapshot_not_interesting ;
/ *
* Don ' t use a snapshot that requires an xmin that we cannot guarantee to
* be available .
* /
if ( TransactionIdPrecedes ( ondisk . builder . xmin , builder - > initial_xmin_horizon ) )
goto snapshot_not_interesting ;
/ *
* Consistent snapshots have no next phase . Reset next_phase_at as it is
* possible that an old value may remain .
* /
Assert ( ondisk . builder . next_phase_at = = InvalidTransactionId ) ;
builder - > next_phase_at = InvalidTransactionId ;
/ * ok , we think the snapshot is sensible , copy over everything important * /
builder - > xmin = ondisk . builder . xmin ;
builder - > xmax = ondisk . builder . xmax ;
builder - > state = ondisk . builder . state ;
builder - > committed . xcnt = ondisk . builder . committed . xcnt ;
/ * We only allocated / stored xcnt , not xcnt_space xids ! * /
/ * don ' t overwrite preallocated xip , if we don ' t have anything here * /
if ( builder - > committed . xcnt > 0 )
{
pfree ( builder - > committed . xip ) ;
builder - > committed . xcnt_space = ondisk . builder . committed . xcnt ;
builder - > committed . xip = ondisk . builder . committed . xip ;
}
ondisk . builder . committed . xip = NULL ;
/ * set catalog modifying transactions * /
if ( builder - > catchange . xip )
pfree ( builder - > catchange . xip ) ;
builder - > catchange . xcnt = ondisk . builder . catchange . xcnt ;
builder - > catchange . xip = ondisk . builder . catchange . xip ;
ondisk . builder . catchange . xip = NULL ;
/ * our snapshot is not interesting anymore , build a new one * /
if ( builder - > snapshot ! = NULL )
{
SnapBuildSnapDecRefcount ( builder - > snapshot ) ;
}
builder - > snapshot = SnapBuildBuildSnapshot ( builder ) ;
SnapBuildSnapIncRefcount ( builder - > snapshot ) ;
ReorderBufferSetRestartPoint ( builder - > reorder , lsn ) ;
Assert ( builder - > state = = SNAPBUILD_CONSISTENT ) ;
ereport ( LOG ,
( errmsg ( " logical decoding found consistent point at % X / % X " ,
LSN_FORMAT_ARGS ( lsn ) ) ,
errdetail ( " Logical decoding will begin using saved snapshot . " ) ) ) ;
return true ;
snapshot_not_interesting :
if ( ondisk . builder . committed . xip ! = NULL )
pfree ( ondisk . builder . committed . xip ) ;
if ( ondisk . builder . catchange . xip ! = NULL )
pfree ( ondisk . builder . catchange . xip ) ;
return false ;
}
/ *
* Read the contents of the serialized snapshot to ' dest ' .
* /
static void
SnapBuildRestoreContents ( int fd , void * dest , Size size , const char * path )
{
int readBytes ;
pgstat_report_wait_start ( WAIT_EVENT_SNAPBUILD_READ ) ;
readBytes = read ( fd , dest , size ) ;
pgstat_report_wait_end ( ) ;
if ( readBytes ! = size )
{
int save_errno = errno ;
CloseTransientFile ( fd ) ;
if ( readBytes < 0 )
{
errno = save_errno ;
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not read file \ " % s \ " : % m " , path ) ) ) ;
}
else
ereport ( ERROR ,
( errcode ( ERRCODE_DATA_CORRUPTED ) ,
errmsg ( " could not read file \ " % s \ " : read % d of % zu " ,
path , readBytes , size ) ) ) ;
}
}
/ *
* Remove all serialized snapshots that are not required anymore because no
* slot can need them . This doesn ' t actually have to run during a checkpoint ,
* but it ' s a convenient point to schedule this .
*
* NB : We run this during checkpoints even if logical decoding is disabled so
* we cleanup old slots at some point after it got disabled .
* /
void
CheckPointSnapBuild ( void )
{
XLogRecPtr cutoff ;
XLogRecPtr redo ;
DIR * snap_dir ;
struct dirent * snap_de ;
char path [ MAXPGPATH + sizeof ( PG_LOGICAL_SNAPSHOTS_DIR ) ] ;
/ *
* We start off with a minimum of the last redo pointer . No new
* replication slot will start before that , so that ' s a safe upper bound
* for removal .
* /
redo = GetRedoRecPtr ( ) ;
/ * now check for the restart ptrs from existing slots * /
cutoff = ReplicationSlotsComputeLogicalRestartLSN ( ) ;
/ * don ' t start earlier than the restart lsn * /
if ( redo < cutoff )
cutoff = redo ;
snap_dir = AllocateDir ( PG_LOGICAL_SNAPSHOTS_DIR ) ;
while ( ( snap_de = ReadDir ( snap_dir , PG_LOGICAL_SNAPSHOTS_DIR ) ) ! = NULL )
{
uint32 hi ;
uint32 lo ;
XLogRecPtr lsn ;
PGFileType de_type ;
if ( strcmp ( snap_de - > d_name , " . " ) = = 0 | |
strcmp ( snap_de - > d_name , " . . " ) = = 0 )
continue ;
snprintf ( path , sizeof ( path ) , " % s / % s " , PG_LOGICAL_SNAPSHOTS_DIR , snap_de - > d_name ) ;
de_type = get_dirent_type ( path , snap_de , false , DEBUG1 ) ;
if ( de_type ! = PGFILETYPE_ERROR & & de_type ! = PGFILETYPE_REG )
{
elog ( DEBUG1 , " only regular files expected : % s " , path ) ;
continue ;
}
/ *
* temporary filenames from SnapBuildSerialize ( ) include the LSN and
* everything but are postfixed by . $ pid . tmp . We can just remove them
* the same as other files because there can be none that are
* currently being written that are older than cutoff .
*
* We just log a message if a file doesn ' t fit the pattern , it ' s
* probably some editors lock / state file or similar . . .
* /
if ( sscanf ( snap_de - > d_name , " % X - % X . snap " , & hi , & lo ) ! = 2 )
{
ereport ( LOG ,
( errmsg ( " could not parse file name \ " % s \ " " , path ) ) ) ;
continue ;
}
lsn = ( ( uint64 ) hi ) < < 32 | lo ;
/ * check whether we still need it * /
if ( lsn < cutoff | | cutoff = = InvalidXLogRecPtr )
{
elog ( DEBUG1 , " removing snapbuild snapshot % s " , path ) ;
/ *
* It ' s not particularly harmful , though strange , if we can ' t
* remove the file here . Don ' t prevent the checkpoint from
* completing , that ' d be a cure worse than the disease .
* /
if ( unlink ( path ) < 0 )
{
ereport ( LOG ,
( errcode_for_file_access ( ) ,
errmsg ( " could not remove file \ " % s \ " : % m " ,
path ) ) ) ;
continue ;
}
}
}
FreeDir ( snap_dir ) ;
}
/ *
* Check if a logical snapshot at the specified point has been serialized .
* /
bool
SnapBuildSnapshotExists ( XLogRecPtr lsn )
{
char path [ MAXPGPATH ] ;
int ret ;
struct stat stat_buf ;
sprintf ( path , " % s / % X - % X . snap " ,
PG_LOGICAL_SNAPSHOTS_DIR ,
LSN_FORMAT_ARGS ( lsn ) ) ;
ret = stat ( path , & stat_buf ) ;
if ( ret ! = 0 & & errno ! = ENOENT )
ereport ( ERROR ,
( errcode_for_file_access ( ) ,
errmsg ( " could not stat file \ " % s \ " : % m " , path ) ) ) ;
return ret = = 0 ;
}
Messung V0.5 in Prozent C=92 H=97 G=94
¤ Dauer der Verarbeitung: 0.105 Sekunden
(vorverarbeitet am 2026-10-11)
¤
*© Formatika GbR, Deutschland