/* * Make sure that CommitTs has room for a newly-allocated XID. * * NB: this is called while holding XidGenLock. We want it to be very fast * most of the time; even when it's not so fast, no actual I/O need happen * unless we're forced to write out a dirty CommitTs or xlog page to make room * in shared memory. * * NB: the current implementation relies on track_commit_timestamp being * PGC_POSTMASTER. */ void ExtendCommitTs(TransactionId newestXact) { int pageno; /* * Nothing to do if module not enabled. Note we do an unlocked read of * the flag here, which is okay because this routine is only called from * GetNewTransactionId, which is never called in a standby. */ Assert(!InRecovery); if (!commitTsShared->commitTsActive) return; /* * No work except at first XID of a page. But beware: just after * wraparound, the first XID of page zero is FirstNormalTransactionId. */ if (TransactionIdToCTsEntry(newestXact) != 0 && !TransactionIdEquals(newestXact, FirstNormalTransactionId)) return; pageno = TransactionIdToCTsPage(newestXact); LWLockAcquire(CommitTsControlLock, LW_EXCLUSIVE); /* Zero the page and make an XLOG entry about it */ ZeroCommitTsPage(pageno, !InRecovery); LWLockRelease(CommitTsControlLock); }
/* * Activate this module whenever necessary. * This must happen during postmaster or standalong-backend startup, * or during WAL replay anytime the track_commit_timestamp setting is * changed in the master. * * The reason why this SLRU needs separate activation/deactivation functions is * that it can be enabled/disabled during start and the activation/deactivation * on master is propagated to slave via replay. Other SLRUs don't have this * property and they can be just initialized during normal startup. * * This is in charge of creating the currently active segment, if it's not * already there. The reason for this is that the server might have been * running with this module disabled for a while and thus might have skipped * the normal creation point. */ static void ActivateCommitTs(void) { TransactionId xid = ShmemVariableCache->nextXid; int pageno = TransactionIdToCTsPage(xid); /* * Re-Initialize our idea of the latest page number. */ LWLockAcquire(CommitTsControlLock, LW_EXCLUSIVE); CommitTsCtl->shared->latest_page_number = pageno; LWLockRelease(CommitTsControlLock); /* * If CommitTs is enabled, but it wasn't in the previous server run, we * need to set the oldest and newest values to the next Xid; that way, we * will not try to read data that might not have been set. * * XXX does this have a problem if a server is started with commitTs * enabled, then started with commitTs disabled, then restarted with it * enabled again? It doesn't look like it does, because there should be a * checkpoint that sets the value to InvalidTransactionId at end of * recovery; and so any chance of injecting new transactions without * CommitTs values would occur after the oldestCommitTs has been set to * Invalid temporarily. */ LWLockAcquire(CommitTsLock, LW_EXCLUSIVE); if (ShmemVariableCache->oldestCommitTs == InvalidTransactionId) { ShmemVariableCache->oldestCommitTs = ShmemVariableCache->newestCommitTs = ReadNewTransactionId(); } LWLockRelease(CommitTsLock); /* Create the current segment file, if necessary */ if (!SimpleLruDoesPhysicalPageExist(CommitTsCtl, pageno)) { int slotno; LWLockAcquire(CommitTsControlLock, LW_EXCLUSIVE); slotno = ZeroCommitTsPage(pageno, false); SimpleLruWritePage(CommitTsCtl, slotno); Assert(!CommitTsCtl->shared->page_dirty[slotno]); LWLockRelease(CommitTsControlLock); } /* Change the activation status in shared memory. */ LWLockAcquire(CommitTsLock, LW_EXCLUSIVE); commitTsShared->commitTsActive = true; LWLockRelease(CommitTsLock); }
/* * Remove all CommitTs segments before the one holding the passed * transaction ID. * * Note that we don't need to flush XLOG here. */ void TruncateCommitTs(TransactionId oldestXact) { int cutoffPage; /* * The cutoff point is the start of the segment containing oldestXact. We * pass the *page* containing oldestXact to SimpleLruTruncate. */ cutoffPage = TransactionIdToCTsPage(oldestXact); /* Check to see if there's any files that could be removed */ if (!SlruScanDirectory(CommitTsCtl, SlruScanDirCbReportPresence, &cutoffPage)) return; /* nothing to remove */ /* Write XLOG record */ WriteTruncateXlogRec(cutoffPage); /* Now we can remove the old CommitTs segment(s) */ SimpleLruTruncate(CommitTsCtl, cutoffPage); }
/* * Interrogate the commit timestamp of a transaction. * * The return value indicates whether a commit timestamp record was found for * the given xid. The timestamp value is returned in *ts (which may not be * null), and the origin node for the Xid is returned in *nodeid, if it's not * null. */ bool TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts, RepOriginId *nodeid) { int pageno = TransactionIdToCTsPage(xid); int entryno = TransactionIdToCTsEntry(xid); int slotno; CommitTimestampEntry entry; TransactionId oldestCommitTsXid; TransactionId newestCommitTsXid; if (!TransactionIdIsValid(xid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot retrieve commit timestamp for transaction %u", xid))); else if (!TransactionIdIsNormal(xid)) { /* frozen and bootstrap xids are always committed far in the past */ *ts = 0; if (nodeid) *nodeid = 0; return false; } LWLockAcquire(CommitTsLock, LW_SHARED); /* Error if module not enabled */ if (!commitTsShared->commitTsActive) error_commit_ts_disabled(); /* * If we're asked for the cached value, return that. Otherwise, fall * through to read from SLRU. */ if (commitTsShared->xidLastCommit == xid) { *ts = commitTsShared->dataLastCommit.time; if (nodeid) *nodeid = commitTsShared->dataLastCommit.nodeid; LWLockRelease(CommitTsLock); return *ts != 0; } oldestCommitTsXid = ShmemVariableCache->oldestCommitTsXid; newestCommitTsXid = ShmemVariableCache->newestCommitTsXid; /* neither is invalid, or both are */ Assert(TransactionIdIsValid(oldestCommitTsXid) == TransactionIdIsValid(newestCommitTsXid)); LWLockRelease(CommitTsLock); /* * Return empty if the requested value is outside our valid range. */ if (!TransactionIdIsValid(oldestCommitTsXid) || TransactionIdPrecedes(xid, oldestCommitTsXid) || TransactionIdPrecedes(newestCommitTsXid, xid)) { *ts = 0; if (nodeid) *nodeid = InvalidRepOriginId; return false; } /* lock is acquired by SimpleLruReadPage_ReadOnly */ slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, xid); memcpy(&entry, CommitTsCtl->shared->page_buffer[slotno] + SizeOfCommitTimestampEntry * entryno, SizeOfCommitTimestampEntry); *ts = entry.time; if (nodeid) *nodeid = entry.nodeid; LWLockRelease(CommitTsControlLock); return *ts != 0; }
/* * TransactionTreeSetCommitTsData * * Record the final commit timestamp of transaction entries in the commit log * for a transaction and its subtransaction tree, as efficiently as possible. * * xid is the top level transaction id. * * subxids is an array of xids of length nsubxids, representing subtransactions * in the tree of xid. In various cases nsubxids may be zero. * The reason why tracking just the parent xid commit timestamp is not enough * is that the subtrans SLRU does not stay valid across crashes (it's not * permanent) so we need to keep the information about them here. If the * subtrans implementation changes in the future, we might want to revisit the * decision of storing timestamp info for each subxid. * * The write_xlog parameter tells us whether to include an XLog record of this * or not. Normally, this is called from transaction commit routines (both * normal and prepared) and the information will be stored in the transaction * commit XLog record, and so they should pass "false" for this. The XLog redo * code should use "false" here as well. Other callers probably want to pass * true, so that the given values persist in case of crashes. */ void TransactionTreeSetCommitTsData(TransactionId xid, int nsubxids, TransactionId *subxids, TimestampTz timestamp, RepOriginId nodeid, bool write_xlog) { int i; TransactionId headxid; TransactionId newestXact; /* * No-op if the module is not active. * * An unlocked read here is fine, because in a standby (the only place * where the flag can change in flight) this routine is only called by the * recovery process, which is also the only process which can change the * flag. */ if (!commitTsShared->commitTsActive) return; /* * Comply with the WAL-before-data rule: if caller specified it wants this * value to be recorded in WAL, do so before touching the data. */ if (write_xlog) WriteSetTimestampXlogRec(xid, nsubxids, subxids, timestamp, nodeid); /* * Figure out the latest Xid in this batch: either the last subxid if * there's any, otherwise the parent xid. */ if (nsubxids > 0) newestXact = subxids[nsubxids - 1]; else newestXact = xid; /* * We split the xids to set the timestamp to in groups belonging to the * same SLRU page; the first element in each such set is its head. The * first group has the main XID as the head; subsequent sets use the first * subxid not on the previous page as head. This way, we only have to * lock/modify each SLRU page once. */ for (i = 0, headxid = xid;;) { int pageno = TransactionIdToCTsPage(headxid); int j; for (j = i; j < nsubxids; j++) { if (TransactionIdToCTsPage(subxids[j]) != pageno) break; } /* subxids[i..j] are on the same page as the head */ SetXidCommitTsInPage(headxid, j - i, subxids + i, timestamp, nodeid, pageno); /* if we wrote out all subxids, we're done. */ if (j + 1 >= nsubxids) break; /* * Set the new head and skip over it, as well as over the subxids we * just wrote. */ headxid = subxids[j]; i += j - i + 1; } /* update the cached value in shared memory */ LWLockAcquire(CommitTsLock, LW_EXCLUSIVE); commitTsShared->xidLastCommit = xid; commitTsShared->dataLastCommit.time = timestamp; commitTsShared->dataLastCommit.nodeid = nodeid; /* and move forwards our endpoint, if needed */ if (TransactionIdPrecedes(ShmemVariableCache->newestCommitTsXid, newestXact)) ShmemVariableCache->newestCommitTsXid = newestXact; LWLockRelease(CommitTsLock); }
/* * Interrogate the commit timestamp of a transaction. * * The return value indicates whether a commit timestamp record was found for * the given xid. The timestamp value is returned in *ts (which may not be * null), and the origin node for the Xid is returned in *nodeid, if it's not * null. */ bool TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts, RepOriginId *nodeid) { int pageno = TransactionIdToCTsPage(xid); int entryno = TransactionIdToCTsEntry(xid); int slotno; CommitTimestampEntry entry; TransactionId oldestCommitTs; TransactionId newestCommitTs; /* error if the given Xid doesn't normally commit */ if (!TransactionIdIsNormal(xid)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot retrieve commit timestamp for transaction %u", xid))); LWLockAcquire(CommitTsLock, LW_SHARED); /* Error if module not enabled */ if (!commitTsShared->commitTsActive) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("could not get commit timestamp data"), errhint("Make sure the configuration parameter \"%s\" is set.", "track_commit_timestamp"))); /* * If we're asked for the cached value, return that. Otherwise, fall * through to read from SLRU. */ if (commitTsShared->xidLastCommit == xid) { *ts = commitTsShared->dataLastCommit.time; if (nodeid) *nodeid = commitTsShared->dataLastCommit.nodeid; LWLockRelease(CommitTsLock); return *ts != 0; } oldestCommitTs = ShmemVariableCache->oldestCommitTs; newestCommitTs = ShmemVariableCache->newestCommitTs; /* neither is invalid, or both are */ Assert(TransactionIdIsValid(oldestCommitTs) == TransactionIdIsValid(newestCommitTs)); LWLockRelease(CommitTsLock); /* * Return empty if the requested value is outside our valid range. */ if (!TransactionIdIsValid(oldestCommitTs) || TransactionIdPrecedes(xid, oldestCommitTs) || TransactionIdPrecedes(newestCommitTs, xid)) { *ts = 0; if (nodeid) *nodeid = InvalidRepOriginId; return false; } /* lock is acquired by SimpleLruReadPage_ReadOnly */ slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, xid); memcpy(&entry, CommitTsCtl->shared->page_buffer[slotno] + SizeOfCommitTimestampEntry * entryno, SizeOfCommitTimestampEntry); *ts = entry.time; if (nodeid) *nodeid = entry.nodeid; LWLockRelease(CommitTsControlLock); return *ts != 0; }