Re: Usage of epoch in txid_current

From: Andres Freund <andres(at)anarazel(dot)de>
To: Thomas Munro <thomas(dot)munro(at)enterprisedb(dot)com>
Cc: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>, Alexander Korotkov <a(dot)korotkov(at)postgrespro(dot)ru>, Pg Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Usage of epoch in txid_current
Date: 2019-02-04 07:41:26
Message-ID: 20190204074126.cqhjsgdtaafyrfaa@alap3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox
Thread:
Lists: pgsql-hackers

Hi,

On 2018-09-19 13:58:36 +1200, Thomas Munro wrote:

> +/*
> + * Advance nextFullXid to the value after a given xid. The epoch is inferred.
> + * If lock_free_check is true, then the caller must be sure that it's safe to
> + * read nextFullXid without holding XidGenLock (ie during recovery).
> + */
> +void
> +AdvanceNextFullTransactionIdPastXid(TransactionId xid, bool lock_free_check)
> +{
> + TransactionId current_xid;
> + uint32 epoch;
> +
> + if (lock_free_check &&
> + !TransactionIdFollowsOrEquals(xid,
> + XidFromFullTransactionId(ShmemVariableCache->nextFullXid)))
> + return;
> +
> + LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
> + current_xid = XidFromFullTransactionId(ShmemVariableCache->nextFullXid);
> + if (TransactionIdFollowsOrEquals(xid, current_xid))
> + {
> + epoch = EpochFromFullTransactionId(ShmemVariableCache->nextFullXid);
> + if (xid < current_xid)
> + ++epoch; /* epoch wrapped */
> + ShmemVariableCache->nextFullXid = MakeFullTransactionId(epoch, xid);
> + FullTransactionIdAdvance(&ShmemVariableCache->nextFullXid);
> + }
> + LWLockRelease(XidGenLock);
> }

Is this really a good idea? Seems like it's going to perpetuate the
problematic epoch logic we had before?

> --- a/src/bin/pg_resetwal/pg_resetwal.c
> +++ b/src/bin/pg_resetwal/pg_resetwal.c
> @@ -431,11 +431,15 @@ main(int argc, char *argv[])
> * if any, includes these values.)
> */
> if (set_xid_epoch != -1)
> - ControlFile.checkPointCopy.nextXidEpoch = set_xid_epoch;
> + ControlFile.checkPointCopy.nextFullXid =
> + MakeFullTransactionId(set_xid_epoch,
> + XidFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid));
>
> if (set_xid != 0)
> {
> - ControlFile.checkPointCopy.nextXid = set_xid;
> + ControlFile.checkPointCopy.nextFullXid =
> + MakeFullTransactionId(EpochFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid),
> + set_xid);
>

> /*
> * For the moment, just set oldestXid to a value that will force
> @@ -705,8 +709,7 @@ GuessControlValues(void)
> ControlFile.checkPointCopy.ThisTimeLineID = 1;
> ControlFile.checkPointCopy.PrevTimeLineID = 1;
> ControlFile.checkPointCopy.fullPageWrites = false;
> - ControlFile.checkPointCopy.nextXidEpoch = 0;
> - ControlFile.checkPointCopy.nextXid = FirstNormalTransactionId;
> + ControlFile.checkPointCopy.nextFullXid = MakeFullTransactionId(0, FirstNormalTransactionId);
> ControlFile.checkPointCopy.nextOid = FirstBootstrapObjectId;
> ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
> ControlFile.checkPointCopy.nextMultiOffset = 0;
> @@ -786,8 +789,8 @@ PrintControlValues(bool guessed)
> printf(_("Latest checkpoint's full_page_writes: %s\n"),
> ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
> printf(_("Latest checkpoint's NextXID: %u:%u\n"),
> - ControlFile.checkPointCopy.nextXidEpoch,
> - ControlFile.checkPointCopy.nextXid);
> + EpochFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid),
> + XidFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid));
> printf(_("Latest checkpoint's NextOID: %u\n"),
> ControlFile.checkPointCopy.nextOid);
> printf(_("Latest checkpoint's NextMultiXactId: %u\n"),
> @@ -879,7 +882,7 @@ PrintNewControlValues(void)
> if (set_xid != 0)
> {
> printf(_("NextXID: %u\n"),
> - ControlFile.checkPointCopy.nextXid);
> + XidFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid));
> printf(_("OldestXID: %u\n"),
> ControlFile.checkPointCopy.oldestXid);
> printf(_("OldestXID's DB: %u\n"),
> @@ -889,7 +892,7 @@ PrintNewControlValues(void)
> if (set_xid_epoch != -1)
> {
> printf(_("NextXID epoch: %u\n"),
> - ControlFile.checkPointCopy.nextXidEpoch);
> + EpochFromFullTransactionId(ControlFile.checkPointCopy.nextFullXid));
> }

I still think it's a mistake to not display the full xid here, and
rather perpetuate the epoch stuff. I'm ok with splitting that into a
separate commit, but this ought to be fixed in the same release imo.

> diff --git a/src/include/access/transam.h b/src/include/access/transam.h
> index 83ec3f19797..814becf96d7 100644
> --- a/src/include/access/transam.h
> +++ b/src/include/access/transam.h
> @@ -44,6 +44,32 @@
> #define TransactionIdStore(xid, dest) (*(dest) = (xid))
> #define StoreInvalidTransactionId(dest) (*(dest) = InvalidTransactionId)
>
> +#define EpochFromFullTransactionId(x) ((uint32) ((x).value >> 32))
> +#define XidFromFullTransactionId(x) ((uint32) (x).value)
> +#define U64FromFullTransactionId(x) ((x).value)
> +#define FullTransactionIdPrecedes(a, b) ((a).value < (b).value)
> +#define FullTransactionIdPrecedesOrEquals(a, b) ((a).value <= (b).value)
> +
> +/*
> + * A 64 bit value that contains an epoch and a TransactionId. This is
> + * wrapped in a struct to prevent implicit conversion to/from TransactionId.
> + * Allowing such conversions seems likely to be error-prone.
> + */
> +typedef struct FullTransactionId
> +{
> + uint64 value;
> +} FullTransactionId;

Probably good to note here that not all values are valid normal xids.

> +static inline FullTransactionId
> +MakeFullTransactionId(uint32 epoch, TransactionId xid)
> +{
> + FullTransactionId result;
> +
> + result.value = ((uint64) epoch) << 32 | xid;
> +
> + return result;
> +}

Make sounds a bit like it's allocating...

> /* advance a transaction ID variable, handling wraparound correctly */
> #define TransactionIdAdvance(dest) \
> do { \
> @@ -52,6 +78,16 @@
> (dest) = FirstNormalTransactionId; \
> } while(0)
>
> +/* advance a FullTransactionId variable, stepping over special XIDs */
> +static inline void
> +FullTransactionIdAdvance(FullTransactionId *dest)
> +{
> + dest->value++;
> + if (XidFromFullTransactionId(*dest) < FirstNormalTransactionId)
> + *dest = MakeFullTransactionId(EpochFromFullTransactionId(*dest),
> + FirstNormalTransactionId);
> +}

Hm, this seems pretty odd coding to me. Why not do something like

dest->value++;
while (XidFromFullTransactionId(*dest) < FirstNormalTransactionId)
dest->value++;

That'd a) be a bit more readable, b) possible to do in a lockfree way at
a later point.

> diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
> index 1fcd8cf1b59..d1116454095 100644
> --- a/src/include/storage/standby.h
> +++ b/src/include/storage/standby.h
> @@ -72,7 +72,7 @@ typedef struct RunningTransactionsData
> int xcnt; /* # of xact ids in xids[] */
> int subxcnt; /* # of subxact ids in xids[] */
> bool subxid_overflow; /* snapshot overflowed, subxids missing */
> - TransactionId nextXid; /* copy of ShmemVariableCache->nextXid */
> + TransactionId nextXid; /* xid from ShmemVariableCache->nextFullXid */

Probably worthwhile to pgindent partially.

Greetings,

Andres Freund

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Jakub Glapa 2019-02-04 07:52:17 Re: dsa_allocate() faliure
Previous Message Amit Kapila 2019-02-04 07:40:53 Re: WIP: Avoid creation of the free space map for small tables