lastOverflowedXid does not handle transaction ID wraparound

Started by Stan Huover 4 years ago22 messages
#1Stan Hu
stanhu@gmail.com

In a blog post (https://about.gitlab.com/blog/2021/09/29/why-we-spent-the-last-month-eliminating-postgresql-subtransactions/),
I described how PostgreSQL can enter into a suboverflow condition on
the replica under a number of conditions:

1. A long transaction starts.
2. A single SAVEPOINT is issued.
3. Many rows are updated on the primary, and the same rows are read
from the replica.

This can cause a significant performance degradation with a replica
due to SubtransSLRU wait events since the replica needs to perform a
parent lookup on an ever-growing range of XIDs. Full details on how to
replicate this: https://gitlab.com/-/snippets/2187338.

The main two lines of code that cause the replica to enter in the
suboverflowed state are here
(https://github.com/postgres/postgres/blob/317632f3073fc06047a42075eb5e28a9577a4f96/src/backend/storage/ipc/procarray.c#L2431-L2432):

if (TransactionIdPrecedesOrEquals(xmin, procArray->lastOverflowedXid))
suboverflowed = true;

I noticed that lastOverflowedXid doesn't get cleared even after all
subtransactions have been completed. On a replica, it only seems to be
updated via a XLOG_XACT_ASSIGNMENT, but no such message will be sent
if subtransactions halt. If the XID wraps around again and a long
transaction starts before lastOverflowedXid, the replica might
unnecessarily enter in the suboverflow condition again.

I've validated this by issuing a SAVEPOINT, running the read/write
test, logging lastOverflowedXid to stderr, and then using pg_bench to
advance XID with SELECT txid_current(). After many hours, I validated
that lastOverflowedXid remained the same, and I could induce a high
degree of SubtransSLRU wait events without issuing a new SAVEPOINT.

I'm wondering a few things:

1. Should lastOverflowedXid be reset to 0 at some point? I'm not sure
if there's a good way at the moment for the replica to know that all
subtransactions have completed.
2. Alternatively, should the epoch number be used to compare xmin and
lastOverflowedXid?

To mitigate this issue, we've considered:

1. Restarting the replicas. This isn't great, and if another SAVEPOINT
comes along, we'd have to do this again. It would be nice to be able
to monitor the exact value of lastOverflowedXid.
2. Raise the NUM_SUBTRANS_BUFFERS as a workaround until the scalable
SLRU patches are available
(https://commitfest.postgresql.org/34/2627/).
3. Issue SAVEPOINTs periodically to "run away" from this wraparound issue.

#2Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Stan Hu (#1)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Tue, Oct 12, 2021 at 09:53:22PM -0700, Stan Hu wrote:

I described how PostgreSQL can enter into a suboverflow condition on
the replica under a number of conditions:

1. A long transaction starts.
2. A single SAVEPOINT is issued.
3. Many rows are updated on the primary, and the same rows are read
from the replica.

I noticed that lastOverflowedXid doesn't get cleared even after all
subtransactions have been completed. On a replica, it only seems to be
updated via a XLOG_XACT_ASSIGNMENT, but no such message will be sent
if subtransactions halt. If the XID wraps around again and a long
transaction starts before lastOverflowedXid, the replica might
unnecessarily enter in the suboverflow condition again.

Hi,

that's an interesting finding, thanks for the investigation. I didn't
reproduce it fully (haven't checked the wraparound part), but indeed
lastOverflowedXid is not changing that often, only every
PGPROC_MAX_CACHED_SUBXIDS subtransactions. I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

#3Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Dmitry Dolgov (#2)
Re: lastOverflowedXid does not handle transaction ID wraparound

17 окт. 2021 г., в 21:55, Dmitry Dolgov <9erthalion6@gmail.com> написал(а):
I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

I think we should just invalidate lastOverflowedXid on every XLOG_RUNNING_XACTS if subxid_overflow == false. I can't find a reason not to do so.

Best regards, Andrey Borodin.

#4Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Andrey Borodin (#3)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Wed, Oct 20, 2021 at 04:00:35PM +0500, Andrey Borodin wrote:

17 окт. 2021 г., в 21:55, Dmitry Dolgov <9erthalion6@gmail.com> написал(а):
I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

I think we should just invalidate lastOverflowedXid on every XLOG_RUNNING_XACTS if subxid_overflow == false. I can't find a reason not to do so.

From what I understand that was actually the case, lastOverflowedXid was
set to InvalidTransactionId in ProcArrayApplyRecoveryInfo if
subxid_overflow wasn't set. Looks like 10b7c686e52a6d1bb has changed it,
to what I didn't pay attention originally.

#5Stan Hu
stanhu@gmail.com
In reply to: Andrey Borodin (#3)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Wed, Oct 20, 2021 at 4:00 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

17 окт. 2021 г., в 21:55, Dmitry Dolgov <9erthalion6@gmail.com>

написал(а):

I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

I think we should just invalidate lastOverflowedXid on every
XLOG_RUNNING_XACTS if subxid_overflow == false. I can't find a reason not
to do so.

On a replica, I think it's possible for lastOverflowedXid to be set even if
subxid_overflow is false on the primary and secondary (
https://github.com/postgres/postgres/blob/dc899146dbf0e1d23fb24155a5155826ddce34c9/src/backend/storage/ipc/procarray.c#L1327).
I thought subxid_overflow only gets set if there are more than
PGPROC_MAX_CACHED_SUBXIDS (64) used in a given transaction.

Should the replica be invalidating lastOverflowedXid if subxcnt goes to
zero in XLOG_RUNNING_XACTS? But if there's an outstanding snapshot with an
xmin that precedes lastOverflowedXid we might violate MVCC if we invalidate
this, so I wonder if we also need to check the snapshot with the lowest
xmin?

#6Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Dmitry Dolgov (#4)
Re: lastOverflowedXid does not handle transaction ID wraparound

At Wed, 20 Oct 2021 13:48:33 +0200, Dmitry Dolgov <9erthalion6@gmail.com> wrote in

On Wed, Oct 20, 2021 at 04:00:35PM +0500, Andrey Borodin wrote:

17 окт. 2021 г., в 21:55, Dmitry Dolgov <9erthalion6@gmail.com> написал(а):
I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

I think we should just invalidate lastOverflowedXid on every XLOG_RUNNING_XACTS if subxid_overflow == false. I can't find a reason not to do so.

From what I understand that was actually the case, lastOverflowedXid was
set to InvalidTransactionId in ProcArrayApplyRecoveryInfo if
subxid_overflow wasn't set. Looks like 10b7c686e52a6d1bb has changed it,
to what I didn't pay attention originally.

Unfortunately(?), that doesn't happen once standbyState reaches
STANDBY_SNAPSHOT_READY.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#7Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Stan Hu (#5)
Re: lastOverflowedXid does not handle transaction ID wraparound

At Wed, 20 Oct 2021 08:55:12 -0700, Stan Hu <stanhu@gmail.com> wrote in

On Wed, Oct 20, 2021 at 4:00 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

17 окт. 2021 г., в 21:55, Dmitry Dolgov <9erthalion6@gmail.com>

написал(а):

I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

I think we should just invalidate lastOverflowedXid on every
XLOG_RUNNING_XACTS if subxid_overflow == false. I can't find a reason not
to do so.

On a replica, I think it's possible for lastOverflowedXid to be set even if
subxid_overflow is false on the primary and secondary (
https://github.com/postgres/postgres/blob/dc899146dbf0e1d23fb24155a5155826ddce34c9/src/backend/storage/ipc/procarray.c#L1327).
I thought subxid_overflow only gets set if there are more than
PGPROC_MAX_CACHED_SUBXIDS (64) used in a given transaction.

Should the replica be invalidating lastOverflowedXid if subxcnt goes to
zero in XLOG_RUNNING_XACTS? But if there's an outstanding snapshot with an
xmin that precedes lastOverflowedXid we might violate MVCC if we invalidate
this, so I wonder if we also need to check the snapshot with the lowest
xmin?

lastOverflowedXid is the smallest subxid that possibly exists but
possiblly not known to the standby. So if all top-level transactions
older than lastOverflowedXid end, that means that all the
subtransactions in doubt are known to have been ended.

XLOG_RUNNING_XACTS reports oldestRunningXid, which is the oldest
running top-transaction. Standby expires xids in KnownAssignedXids
array that precede to the oldestRunningXid. We are sure that all
possiblly-overflown subtransactions are gone as well if the oldest xid
is newer than the first overflowed subtransaction.

As a cross check, the following existing code in GetSnapshotData means
that no overflow is not happening if the smallest xid in the known
assigned list is larger than lastOverflowedXid, which agrees to the
consideration above.

procaray.c:2428

subcount = KnownAssignedXidsGetAndSetXmin(snapshot->subxip, &xmin,
xmax);

if (TransactionIdPrecedesOrEquals(xmin, procArray->lastOverflowedXid))
suboverflowed = true;

If the discussion so far is correct, the following diff will fix the
issue.

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe..19682b73ec 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -4463,6 +4463,12 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
 {
        LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
        KnownAssignedXidsRemovePreceding(xid);
+       /*
+        * reset lastOverflowedXid if we know transactions that have been possiblly
+        * running are being gone.
+        */
+       if (TransactionIdPrecedes(procArray->lastOverflowedXid, xid))
+               procArray->lastOverflowedXid = InvalidTransactionId;
        LWLockRelease(ProcArrayLock);
 }

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#8Stan Hu
stanhu@gmail.com
In reply to: Kyotaro Horiguchi (#7)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Wed, Oct 20, 2021 at 9:01 PM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:

lastOverflowedXid is the smallest subxid that possibly exists but
possiblly not known to the standby. So if all top-level transactions
older than lastOverflowedXid end, that means that all the
subtransactions in doubt are known to have been ended.

Thanks for the patch! I verified that it appears to reset
lastOverflowedXid properly.

I may not be understanding
https://github.com/postgres/postgres/blob/dc899146dbf0e1d23fb24155a5155826ddce34c9/src/backend/storage/ipc/procarray.c#L1326-L1327
correctly, but isn't lastOverflowedXid the last subxid for a given
top-level XID, so isn't it actually the largest subxid that possibly
exists?

#9Nikolay Samokhvalov
samokhvalov@gmail.com
In reply to: Stan Hu (#8)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Thu, Oct 21, 2021 at 07:21 Stan Hu <stanhu@gmail.com> wrote:

On Wed, Oct 20, 2021 at 9:01 PM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:

lastOverflowedXid is the smallest subxid that possibly exists but
possiblly not known to the standby. So if all top-level transactions
older than lastOverflowedXid end, that means that all the
subtransactions in doubt are known to have been ended.

Thanks for the patch! I verified that it appears to reset
lastOverflowedXid properly.

Is it right time to register the patch in the current commit fest, right?
(How to do that?)

On a separate note, I think it would be really good to improve
observability for SLRUs -- particularly for Subtrans SLRU and this
overflow-related aspects. pg_stat_slru added in PG13 is really helpful,
but not enough to troubleshoot, analyze and tune issues like this, and the
patches related to SLRU. Basic ideas:
- expose to the user how many pages are currently used (especially useful
if SLRU sizes will be configurable, see
https://commitfest.postgresql.org/34/2627/)
- Andrew Borodin also expressed the idea to extend pageinspect to allow
seeing the content of SLRUs
- a more specific thing: allow seeing lastOverflowedXid somehow (via SQL or
in logs) - we see how important it for standbys health, but we cannot see
it now.

Any ideas in the direction of observability?

Nik

Show quoted text
#10Nikolay Samokhvalov
samokhvalov@gmail.com
In reply to: Nikolay Samokhvalov (#9)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Mon, Oct 25, 2021 at 11:41 AM Nikolay Samokhvalov <samokhvalov@gmail.com>
wrote:

On Thu, Oct 21, 2021 at 07:21 Stan Hu <stanhu@gmail.com> wrote:

On Wed, Oct 20, 2021 at 9:01 PM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:

lastOverflowedXid is the smallest subxid that possibly exists but
possiblly not known to the standby. So if all top-level transactions
older than lastOverflowedXid end, that means that all the
subtransactions in doubt are known to have been ended.

Thanks for the patch! I verified that it appears to reset
lastOverflowedXid properly.

...

Any ideas in the direction of observability?

Perhaps, anything additional should be considered separately.

The behavior discussed here looks like a bug.

I also have tested the patch. It works fully as expected, details of
testing – below.

I think this is a serious bug hitting heavily loaded Postgres setups with
hot standbys
and propose fixing it in all supported major versions ASAP since the fix
looks simple.

Any standby in heavily loaded systems (10k+ TPS) where subtransactions are
used
may experience huge performance degradation on standbys [1]https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful. This is what
happened
recently with GitLab [2]https://about.gitlab.com/blog/2021/09/29/why-we-spent-the-last-month-eliminating-postgresql-subtransactions/. While a full solution to this problem is
something more complex, probably
requiring changes in SLRU [3]/messages/by-id/494C5E7F-E410-48FA-A93E-F7723D859561@yandex-team.ru, the problem discussed here definitely feels
like a serious bug
– if we fully get rid of subtransactions, since 32-bit lastOverflowedXid is
not reset, in new
XID epoch standbys start experience SubtransControlLock/SubtransSLRU again

without any subtransactions. This problem is extremely difficult to
diagnose on one hand,
and it may fully make standbys irresponsible while a long-lasting
transaction last on the primary
("long" here may be a matter of minutes or even dozens of seconds – it
depends on the
TPS level). It is especially hard to diagnose in PG 12 or older – because
it doesn't have
pg_stat_slru yet, so one cannot easily notice Subtrans reads.)

The only current solution to this problem is to restart standby Postgres.

How I tested the patch. First, I reproduced the problem:
- current 15devel Postgres, installed on 2 x c5ad.2xlarge on AWS (8 vCPUs,
16 GiB), working as
primary + standby
- follow the steps described in [3]/messages/by-id/494C5E7F-E410-48FA-A93E-F7723D859561@yandex-team.ru to initiate SubtransSLRU on the standby
- at some point, stop using SAVEPOINTs on the primary - use regular UPDATEs
instead, wait.

Using the following, observe procArray->lastOverflowedXid:

diff --git a/src/backend/storage/ipc/procarray.c
b/src/backend/storage/ipc/procarray.c
index
bd3c7a47fe21949ba63da26f0d692b2ee618f885..ccf3274344d7ba52a6f28a10b08dbfc310cf97e9
100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2428,6 +2428,9 @@ GetSnapshotData(Snapshot snapshot)
  subcount = KnownAssignedXidsGetAndSetXmin(snapshot->subxip, &xmin,
   xmax);
+        if (random() % 100000 == 0)
+                elog(WARNING, "procArray->lastOverflowedXid: %u",
procArray->lastOverflowedXid);
+
  if (TransactionIdPrecedesOrEquals(xmin, procArray->lastOverflowedXid))
  suboverflowed = true;
  }

Once we stop using SAVEPOINTs on the primary, the
value procArray->lastOverflowedXid stop
changing, as expected.

Without the patch applied, lastOverflowedXid remains constant forever –
till the server restart.
And as I mentioned, we start experiencing SubtransSLRU and pg_subtrans
reads.

With the patch, lastOverflowedXid is reset to 0, as expected, shortly after
an ongoing "long"
the transaction ends on the primary.

This solves the bug – we don't have SubtransSLRU on standby without actual
use of subtransactions
on the primary.

[1]: https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful
https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful
[2]: https://about.gitlab.com/blog/2021/09/29/why-we-spent-the-last-month-eliminating-postgresql-subtransactions/
https://about.gitlab.com/blog/2021/09/29/why-we-spent-the-last-month-eliminating-postgresql-subtransactions/
[3]: /messages/by-id/494C5E7F-E410-48FA-A93E-F7723D859561@yandex-team.ru
/messages/by-id/494C5E7F-E410-48FA-A93E-F7723D859561@yandex-team.ru
[4]: https://gitlab.com/postgres-ai/postgresql-consulting/tests-and-benchmarks/-/issues/21
https://gitlab.com/postgres-ai/postgresql-consulting/tests-and-benchmarks/-/issues/21

#11Nikolay Samokhvalov
nikolay@samokhvalov.com
In reply to: Nikolay Samokhvalov (#10)
Re: lastOverflowedXid does not handle transaction ID wraparound

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: tested, failed
Spec compliant: not tested
Documentation: not tested

The fix is trivial and works as expected, solving the problem

Tested, described details of the testing in the email thread.

#12Nikolay Samokhvalov
nikolay@samokhvalov.com
In reply to: Nikolay Samokhvalov (#11)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Mon, Nov 1, 2021 at 11:55 PM Nikolay Samokhvalov <nikolay@samokhvalov.com>
wrote:

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed

Please ignore this – I didn't understand the UI.

#13Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Kyotaro Horiguchi (#7)
Re: lastOverflowedXid does not handle transaction ID wraparound

21 окт. 2021 г., в 09:01, Kyotaro Horiguchi <horikyota.ntt@gmail.com> написал(а):

If the discussion so far is correct, the following diff will fix the
issue.

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe..19682b73ec 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -4463,6 +4463,12 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
KnownAssignedXidsRemovePreceding(xid);
+       /*
+        * reset lastOverflowedXid if we know transactions that have been possiblly
+        * running are being gone.
+        */
+       if (TransactionIdPrecedes(procArray->lastOverflowedXid, xid))
+               procArray->lastOverflowedXid = InvalidTransactionId;
LWLockRelease(ProcArrayLock);
}

The patch seems correct bugfix to me. The only question I have: is it right place from modularity standpoint? procArray->lastOverflowedXid is not a part of KnownAssignedTransactionIds?

Best regards, Andrey Borodin.

#14Alexander Korotkov
aekorotkov@gmail.com
In reply to: Andrey Borodin (#13)
Re: lastOverflowedXid does not handle transaction ID wraparound

( a.On Wed, Nov 3, 2021 at 11:44 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

21 окт. 2021 г., в 09:01, Kyotaro Horiguchi <horikyota.ntt@gmail.com> написал(а):

If the discussion so far is correct, the following diff will fix the
issue.

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe..19682b73ec 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -4463,6 +4463,12 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
KnownAssignedXidsRemovePreceding(xid);
+       /*
+        * reset lastOverflowedXid if we know transactions that have been possiblly
+        * running are being gone.
+        */
+       if (TransactionIdPrecedes(procArray->lastOverflowedXid, xid))
+               procArray->lastOverflowedXid = InvalidTransactionId;
LWLockRelease(ProcArrayLock);
}

The patch seems correct bugfix to me. The only question I have: is it right place from modularity standpoint? procArray->lastOverflowedXid is not a part of KnownAssignedTransactionIds?

It seems the right place because we take ProcArrayLock here. It would
be undesirable to take it twice. We could give a better name for
ExpireOldKnownAssignedTransactionIds() indicating that it could modify
lastOverflowedXid as well. Any ideas?

Should ExpireAllKnownAssignedTransactionIds() be also involved here?

------
Regards,
Alexander Korotkov

#15Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexander Korotkov (#14)
Re: lastOverflowedXid does not handle transaction ID wraparound

3 нояб. 2021 г., в 14:08, Alexander Korotkov <aekorotkov@gmail.com> написал(а):

( a.On Wed, Nov 3, 2021 at 11:44 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

21 окт. 2021 г., в 09:01, Kyotaro Horiguchi <horikyota.ntt@gmail.com> написал(а):

If the discussion so far is correct, the following diff will fix the
issue.

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe..19682b73ec 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -4463,6 +4463,12 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
KnownAssignedXidsRemovePreceding(xid);
+       /*
+        * reset lastOverflowedXid if we know transactions that have been possiblly
+        * running are being gone.
+        */
+       if (TransactionIdPrecedes(procArray->lastOverflowedXid, xid))
+               procArray->lastOverflowedXid = InvalidTransactionId;
LWLockRelease(ProcArrayLock);
}

The patch seems correct bugfix to me. The only question I have: is it right place from modularity standpoint? procArray->lastOverflowedXid is not a part of KnownAssignedTransactionIds?

It seems the right place because we take ProcArrayLock here.

Oh.. I see. ProcArrayApplyRecoveryInfo() is taking ProcArrayLock in so many places.

It would
be undesirable to take it twice. We could give a better name for
ExpireOldKnownAssignedTransactionIds() indicating that it could modify
lastOverflowedXid as well. Any ideas?

Looking more I think the name is OK. KnownAssignedXidsReset() and KnownAssignedXidsRemovePreceding() interferes with procArray a lot.

Should ExpireAllKnownAssignedTransactionIds() be also involved here?

I think it's good for unification, but I do not see how procArray->lastOverflowedXid can be used after ExpireAllKnownAssignedTransactionIds().

Best regards, Andrey Borodin.

#16Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Kyotaro Horiguchi (#7)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Thu, 21 Oct 2021 at 05:01, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:

At Wed, 20 Oct 2021 08:55:12 -0700, Stan Hu <stanhu@gmail.com> wrote in

On Wed, Oct 20, 2021 at 4:00 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

17 окт. 2021 г., в 21:55, Dmitry Dolgov <9erthalion6@gmail.com>

написал(а):

I wonder what would be side
effects of clearing it when the snapshot is not suboverfloved anymore?

I think we should just invalidate lastOverflowedXid on every
XLOG_RUNNING_XACTS if subxid_overflow == false. I can't find a reason not
to do so.

I believe that to be an incorrect fix, but so very nearly correct.
There is a documented race condition in the generation of a
XLOG_RUNNING_XACTS that means there could be a new overflow event
after the snapshot was taken but before it was logged.

On a replica, I think it's possible for lastOverflowedXid to be set even if
subxid_overflow is false on the primary and secondary (
https://github.com/postgres/postgres/blob/dc899146dbf0e1d23fb24155a5155826ddce34c9/src/backend/storage/ipc/procarray.c#L1327).
I thought subxid_overflow only gets set if there are more than
PGPROC_MAX_CACHED_SUBXIDS (64) used in a given transaction.

Should the replica be invalidating lastOverflowedXid if subxcnt goes to
zero in XLOG_RUNNING_XACTS? But if there's an outstanding snapshot with an
xmin that precedes lastOverflowedXid we might violate MVCC if we invalidate
this, so I wonder if we also need to check the snapshot with the lowest
xmin?

lastOverflowedXid is the smallest subxid that possibly exists but
possiblly not known to the standby. So if all top-level transactions
older than lastOverflowedXid end, that means that all the
subtransactions in doubt are known to have been ended.

Agreed

XLOG_RUNNING_XACTS reports oldestRunningXid, which is the oldest
running top-transaction. Standby expires xids in KnownAssignedXids
array that precede to the oldestRunningXid. We are sure that all
possiblly-overflown subtransactions are gone as well if the oldest xid
is newer than the first overflowed subtransaction.

Agreed

As a cross check, the following existing code in GetSnapshotData means
that no overflow is not happening if the smallest xid in the known
assigned list is larger than lastOverflowedXid, which agrees to the
consideration above.

procaray.c:2428

subcount = KnownAssignedXidsGetAndSetXmin(snapshot->subxip, &xmin,
xmax);

if (TransactionIdPrecedesOrEquals(xmin, procArray->lastOverflowedXid))
suboverflowed = true;

If the discussion so far is correct, the following diff will fix the
issue.

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe..19682b73ec 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -4463,6 +4463,12 @@ ExpireOldKnownAssignedTransactionIds(TransactionId xid)
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
KnownAssignedXidsRemovePreceding(xid);
+       /*
+        * reset lastOverflowedXid if we know transactions that have been possiblly
+        * running are being gone.
+        */
+       if (TransactionIdPrecedes(procArray->lastOverflowedXid, xid))
+               procArray->lastOverflowedXid = InvalidTransactionId;
LWLockRelease(ProcArrayLock);
}

So I agree with this fix.

It is however, an undocumented modularity violation. I think that is
acceptable because of the ProcArrayLock traffic, but needs to have a
comment to explain this at the call to
ExpireOldKnownAssignedTransactionIds() i.e. " and potentially reset
lastOverflowedXid", as well as a comment on the
ExpireOldKnownAssignedTransactionIds() function.

--
Simon Riggs http://www.EnterpriseDB.com/

#17Alexander Korotkov
aekorotkov@gmail.com
In reply to: Simon Riggs (#16)
1 attachment(s)
Re: lastOverflowedXid does not handle transaction ID wraparound

Hi!

On Wed, Nov 3, 2021 at 8:51 PM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

It is however, an undocumented modularity violation. I think that is
acceptable because of the ProcArrayLock traffic, but needs to have a
comment to explain this at the call to
ExpireOldKnownAssignedTransactionIds() i.e. " and potentially reset
lastOverflowedXid", as well as a comment on the
ExpireOldKnownAssignedTransactionIds() function.

Thank you for your feedback. Please find the revised patch attached.
It incorporates this function comment changes altogether with minor
editings and commit message. Let me know if you have further
suggestions.

I'm going to push this if no objections.

------
Regards,
Alexander Korotkov

Attachments:

0001-Reset-lastOverflowedXid-on-standby-when-needed.patchapplication/octet-stream; name=0001-Reset-lastOverflowedXid-on-standby-when-needed.patchDownload
From e2feb2b45339cc724697663229177535a0f37623 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Thu, 4 Nov 2021 00:55:01 +0300
Subject: [PATCH] Reset lastOverflowedXid on standby when needed

Currently, lastOverflowedXid is never reset.  It's just adjusted on new
transactions known to be overflowed.  But if there are no overflowed
transactions for a long time, snapshots could be mistakenly marked as
suboverflowed due to wraparound.

This commit fixes this issue by resetting lastOverflowedXid when needed
altogether with KnownAssignedXids.

Backpatch to all supported versions.

Reported-by: Stan Hu
Discussion: https://postgr.es/m/CAMBWrQ%3DFp5UAsU_nATY7EMY7NHczG4-DTDU%3DmCvBQZAQ6wa2xQ%40mail.gmail.com
Author: Kyotaro Horiguchi
Reviewed-by: Stan Hu, Simon Riggs, Nikolay Samokhvalov, Andrey Borodin, Dmitry Dolgov
---
 src/backend/storage/ipc/procarray.c | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd3c7a47fe2..892f0f67998 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -4444,24 +4444,41 @@ ExpireTreeKnownAssignedTransactionIds(TransactionId xid, int nsubxids,
 
 /*
  * ExpireAllKnownAssignedTransactionIds
- *		Remove all entries in KnownAssignedXids
+ *		Remove all entries in KnownAssignedXids and reset lastOverflowedXid.
  */
 void
 ExpireAllKnownAssignedTransactionIds(void)
 {
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
 	KnownAssignedXidsRemovePreceding(InvalidTransactionId);
+
+	/*
+	 * Reset lastOverflowedXid.  Currently, lastOverflowedXid has no use after
+	 * the call of this function.  But do this for unification with what
+	 * ExpireOldKnownAssignedTransactionIds() do.
+	 */
+	procArray->lastOverflowedXid = InvalidTransactionId;
 	LWLockRelease(ProcArrayLock);
 }
 
 /*
  * ExpireOldKnownAssignedTransactionIds
- *		Remove KnownAssignedXids entries preceding the given XID
+ *		Remove KnownAssignedXids entries preceding the given XID and
+ *		potentially reset lastOverflowedXid.
  */
 void
 ExpireOldKnownAssignedTransactionIds(TransactionId xid)
 {
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+
+	/*
+	 * Reset lastOverflowedXid if we know all transactions that have been
+	 * possibly running are being gone.  Not doing so could cause an incorrect
+	 * lastOverflowedXid value, which makes extra snapshots be marked as
+	 * suboverflowed.
+	 */
+	if (TransactionIdPrecedes(procArray->lastOverflowedXid, xid))
+		procArray->lastOverflowedXid = InvalidTransactionId;
 	KnownAssignedXidsRemovePreceding(xid);
 	LWLockRelease(ProcArrayLock);
 }
-- 
2.24.3 (Apple Git-128)

#18Stan Hu
stanhu@gmail.com
In reply to: Alexander Korotkov (#17)
Re: lastOverflowedXid does not handle transaction ID wraparound

Good catch on doing this in ExpireAllKnownAssignedTransactionIds() as well.
Thanks. Looks good to me!

As Nikolay mentioned, I think this is an important bug that we are seeing
in production and would appreciate a backport to v12 if possible.

On Wed, Nov 3, 2021 at 3:07 PM Alexander Korotkov <aekorotkov@gmail.com>
wrote:

Show quoted text

Hi!

On Wed, Nov 3, 2021 at 8:51 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

It is however, an undocumented modularity violation. I think that is
acceptable because of the ProcArrayLock traffic, but needs to have a
comment to explain this at the call to
ExpireOldKnownAssignedTransactionIds() i.e. " and potentially reset
lastOverflowedXid", as well as a comment on the
ExpireOldKnownAssignedTransactionIds() function.

Thank you for your feedback. Please find the revised patch attached.
It incorporates this function comment changes altogether with minor
editings and commit message. Let me know if you have further
suggestions.

I'm going to push this if no objections.

------
Regards,
Alexander Korotkov

#19Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Alexander Korotkov (#17)
Re: lastOverflowedXid does not handle transaction ID wraparound

On Wed, 3 Nov 2021 at 22:07, Alexander Korotkov <aekorotkov@gmail.com> wrote:

Hi!

On Wed, Nov 3, 2021 at 8:51 PM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

It is however, an undocumented modularity violation. I think that is
acceptable because of the ProcArrayLock traffic, but needs to have a
comment to explain this at the call to
ExpireOldKnownAssignedTransactionIds() i.e. " and potentially reset
lastOverflowedXid", as well as a comment on the
ExpireOldKnownAssignedTransactionIds() function.

Thank you for your feedback. Please find the revised patch attached.
It incorporates this function comment changes altogether with minor
editings and commit message. Let me know if you have further
suggestions.

I'm going to push this if no objections.

Looks good, go for it.

--
Simon Riggs http://www.EnterpriseDB.com/

#20Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Alexander Korotkov (#17)
Re: lastOverflowedXid does not handle transaction ID wraparound

At Thu, 4 Nov 2021 01:07:05 +0300, Alexander Korotkov <aekorotkov@gmail.com> wrote in

Hi!

On Wed, Nov 3, 2021 at 8:51 PM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

It is however, an undocumented modularity violation. I think that is
acceptable because of the ProcArrayLock traffic, but needs to have a
comment to explain this at the call to
ExpireOldKnownAssignedTransactionIds() i.e. " and potentially reset
lastOverflowedXid", as well as a comment on the
ExpireOldKnownAssignedTransactionIds() function.

Thank you for your feedback. Please find the revised patch attached.
It incorporates this function comment changes altogether with minor
editings and commit message. Let me know if you have further
suggestions.

I'm going to push this if no objections.

Thanks for taking a look on and refining this, Simon and Alex! (while
I was sick in bed X:)

It looks good to me except the commit Author doesn't contain the name
of Alexander Korotkov?

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#21Alexander Korotkov
aekorotkov@gmail.com
In reply to: Kyotaro Horiguchi (#20)
Re: lastOverflowedXid does not handle transaction ID wraparound

Hi!

On Fri, Nov 5, 2021 at 10:31 AM Kyotaro Horiguchi
<horikyota.ntt@gmail.com> wrote:

At Thu, 4 Nov 2021 01:07:05 +0300, Alexander Korotkov <aekorotkov@gmail.com> wrote in

On Wed, Nov 3, 2021 at 8:51 PM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

It is however, an undocumented modularity violation. I think that is
acceptable because of the ProcArrayLock traffic, but needs to have a
comment to explain this at the call to
ExpireOldKnownAssignedTransactionIds() i.e. " and potentially reset
lastOverflowedXid", as well as a comment on the
ExpireOldKnownAssignedTransactionIds() function.

Thank you for your feedback. Please find the revised patch attached.
It incorporates this function comment changes altogether with minor
editings and commit message. Let me know if you have further
suggestions.

I'm going to push this if no objections.

Thanks for taking a look on and refining this, Simon and Alex! (while
I was sick in bed X:)

It looks good to me except the commit Author doesn't contain the name
of Alexander Korotkov?

Thank you for the suggestion. And thanks to everybody for the feedback.
Pushed!

------
Regards,
Alexander Korotkov

#22Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Alexander Korotkov (#21)
Re: lastOverflowedXid does not handle transaction ID wraparound

At Sat, 6 Nov 2021 19:16:09 +0300, Alexander Korotkov <aekorotkov@gmail.com> wrote in

Pushed!

Thanks!

--
Kyotaro Horiguchi
NTT Open Source Software Center