Re: POC: Unlocked path for GetSnapshotDataReuse

From: Andres Freund <andres(at)anarazel(dot)de>
To: Matthias van de Meent <boekewurm+postgres(at)gmail(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: POC: Unlocked path for GetSnapshotDataReuse
Date: 2026-10-09 12:49:48
Message-ID: lgelgkjrkvnrqb2mfbgcx3fnr3cdr6f2tyanfvhsiyfduw5njz@okoelncmxfmo
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On 2026-08-03 13:17:12 +0200, Matthias van de Meent wrote:
> Hi,
> GetSnapshotData() takes the ProcArrayLock in read-only mode for several reasons:
>
> 1. To acquire new snapshot data;
> 2. To make sure that no active transaction commits before we install
> the new snapshot data's xmin, if we didn't already have an xmin
> installed; and
> 3. To validate that the snapshot it currently holds hasn't been
> invalidated by a newly committed transaction, by comparing
> snapshot->transXactCompletionCount against
> TransamVariables->xactCompletionCount.
>
> I noticed [0] that the reuse path could be implemented as just an
> atomic read (thus avoiding touching the cache line that backs
> ProcArrayLock) if these conditions hold:

> [0] after implementing this, I looked at commit logs for the Reuse
> path, it looks like the original xactCompletionCount commit 623a9ba79b
> already hinted at this optimization in its message as being a likely
> possible further optimization. However, nobody seems to have since
> gotten to actually implementing it.

That was indeed always the goal. When I was working on that whole thing the
patch initially did that, but I just had too many nagging doubts that it was
entirely safe (and had a hard time roping in others to think about it), and
decided that the overall wins were big enough to forgo that optimization for
now.

It's a pretty nice win in a bunch of workloads though, so we really ought to
do that.

I think it might be possible to even do the ->xmin assignment without a lock,
but that might take more thought. That'd make it *much* more broadly
applicable.

> I've attached a patch that applies this optimization. The patch is
> quite a bit larger larger than I'd hoped, because adding
> port/atomics.h to access/transam.h causes frontend compilation errors.
> This effectively required me to move TransamVariables into a different
> header, this case a new varsup.h, which we then need to included in
> many sources, which increases the size of the patch.

I'd encourage doing that in a separate commit, makes it easier to review the
actually interesting bits.

> @@ -765,7 +766,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
> MaintainLatestCompletedXid(latestXid);
>
> /* Same with xactCompletionCount */
> - TransamVariables->xactCompletionCount++;
> + pg_atomic_add_fetch_u64(&TransamVariables->xactCompletionCount, 1);
> }
...
> @@ -934,7 +935,7 @@ ProcArrayClearTransaction(PGPROC *proc)
> * otherwise could end up reusing the snapshot later. Which would be bad,
> * because it might not count the prepared transaction as running.
> */
> - TransamVariables->xactCompletionCount++;
> + pg_atomic_add_fetch_u64(&TransamVariables->xactCompletionCount, 1);
>
> /* Clear the subtransaction-XID cache too */
> Assert(ProcGlobal->subxidStates[pgxactoff].count == proc->subxidStatus.count &&

Doing another atomic operation while holding ProcArrayLock exclusively will
actually *increase* pressure on ProcArrayLock. Given that there can't be two
backends doing xactCompletionCount++ at the same time, due to holding
ProcArrayLock exclusively, why does these need to be an atomic increments?

> +static inline bool
> +GetSnapshotReuseUnlocked(Snapshot snapshot, bool *try_reuse)
> +{
> + uint64 completions;
> +
> + if (!TransactionIdIsValid(MyProc->xmin))
> + {
> + *try_reuse = true;
> + return false;
> + }
> +
> + if (unlikely(snapshot->snapXactCompletionCount == 0))
> + {
> + *try_reuse = false;
> + return false;
> + }
> +
> + pg_memory_barrier();

You really should document what precisely that barrier pairs with.

> + completions = pg_atomic_read_u64(&TransamVariables->xactCompletionCount);
> +
> + if (snapshot->snapXactCompletionCount != completions)
> + {
> + *try_reuse = false;
> + return false;
> + }
> +
> + pg_memory_barrier();

What does this barrier do?

Greetings,

Andres Freund

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Andres Freund 2026-10-09 12:54:04 Re: POC: Unlocked path for GetSnapshotDataReuse
Previous Message Andres Freund 2026-10-09 12:32:36 Re: [Patch] New pg_stat_tablespace view