Failing assertion while taking a restartpoint during crash recovery

Started by Imran Zaheer9 days ago9 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253420
psql -h localhost -U postgres

Built from patchset v4 (message #4), August 23, 2026 at 12:10 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253420_4 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253420_4 && git checkout t253420_4

Patchset v4 (message #4) is on t253420_4

Jump to latest
#1Imran Zaheer
imran.zhir@gmail.com

Hi

While doing some benchmarking related to my work with the recovery
pipelining [1]/messages/by-id/CA+UBfa=vDV8wbmAV0pgrx-FuJh+x8YOW23vJ90Jzr=14rV+9jA@mail.gmail.com, I found that the checkpointer fails when trying to
take a time-based restartpoint during the crash recovery.

Right now I am facing this assertion failure.

TRAP: failed Assert("TransactionIdIsValid(initial)"), File:
"../../../../../home/imran/Desktop/work/pg/postgres/src/backend/storage/ipc/procarray.c",
Line: 1698, PID: 1279722
postgres: checkpointer (ExceptionalCondition+0x72)[0x5985b7bfb814]
postgres: checkpointer (+0x52ae2a)[0x5985b7a51e2a]
postgres: checkpointer
(GetOldestTransactionIdConsideredRunning+0x24)[0x5985b7a53797]
postgres: checkpointer (CreateRestartPoint+0x5f4)[0x5985b7707611]
postgres: checkpointer (CheckpointerMain+0x653)[0x5985b79a554b]
postgres: checkpointer (postmaster_child_launch+0x124)[0x5985b79a82bb]
postgres: checkpointer (+0x484204)[0x5985b79ab204]
postgres: checkpointer (PostmasterMain+0x130a)[0x5985b79aeaaf]
postgres: checkpointer (main+0x1e4)[0x5985b78c10b5]
/lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x78c058829d90]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x78c058829e40]
postgres: checkpointer (_start+0x25)[0x5985b7616065]

It looks like the checkpointer is not aware of whether hot standby
initialization was done during startup, which happens only in the case
of ArchiveRecoveryRequested [2]https://github.com/postgres/postgres/blob/b59783502224c0ae721974a5d9b6fb915cbd37e1/src/backend/access/transam/xlog.c#L6212. In this specific case, the
checkpointer assumes hot standby initialization would have been done,
so it tries to call TruncateSUBTRANS(). A simple fix is to add
ArchiveRecoveryRequested to recovery shared memory XLogRecoveryCtl;
then the checkpointer will simply skip TruncateSUBTRANS if archive
recovery was not requested. I have attached my patch below.

Repro:

I had a script for my benchmarking work, but I changed it to reproduce
this specific bug [3]https://github.com/imranzaheer612/pg-recovery-testing/tree/restartpoint-fail.

Before running the script, set up the env file in `config/env.conf`.
You may need to increase WORKLOAD_DURATION so that crash recovery can
run long enough to trigger a restart point.

```
./run_test.sh -i # basebackup & archiving
./run_test.sh # cpy archive to pg_wal & run crash recovery
```

The script copies the archived wal to the basebackup pg_wal and then
starts the cluster. This will make the cluster undergo a crash
recovery. You will see the failing assertion in the log file created
`recoverylog`.

Looks like some work was already done trying to fix a similar issue in
the past [4]/messages/by-id/17744-2c95e2b7783d7232@postgresql.org,[5]/messages/by-id/18119-5f60199d6207f4d1@postgresql.org.

[1]: /messages/by-id/CA+UBfa=vDV8wbmAV0pgrx-FuJh+x8YOW23vJ90Jzr=14rV+9jA@mail.gmail.com
[2]: https://github.com/postgres/postgres/blob/b59783502224c0ae721974a5d9b6fb915cbd37e1/src/backend/access/transam/xlog.c#L6212
[3]: https://github.com/imranzaheer612/pg-recovery-testing/tree/restartpoint-fail
[4]: /messages/by-id/17744-2c95e2b7783d7232@postgresql.org
[5]: /messages/by-id/18119-5f60199d6207f4d1@postgresql.org

Thanks,
Imran Zaheer

Attachments:

t253420_1
v1-0001-Fix-checkpointer-restartpoint-assertion-failure.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Fix-checkpointer-restartpoint-assertion-failure.patchDownload+40-2
#2Fujii Masao
masao.fujii@gmail.com
In reply to: Imran Zaheer (#1)
Re: Failing assertion while taking a restartpoint during crash recovery

On Sat, Aug 15, 2026 at 4:57 PM Imran Zaheer <imran.zhir@gmail.com> wrote:

Hi

While doing some benchmarking related to my work with the recovery
pipelining [1], I found that the checkpointer fails when trying to
take a time-based restartpoint during the crash recovery.

Right now I am facing this assertion failure.

Thanks for the report! I was also able to reproduce the assertion failure.

It looks like the checkpointer is not aware of whether hot standby
initialization was done during startup, which happens only in the case
of ArchiveRecoveryRequested [2]. In this specific case, the
checkpointer assumes hot standby initialization would have been done,
so it tries to call TruncateSUBTRANS(). A simple fix is to add
ArchiveRecoveryRequested to recovery shared memory XLogRecoveryCtl;
then the checkpointer will simply skip TruncateSUBTRANS if archive
recovery was not requested. I have attached my patch below.

I think it would be better and more robust to check directly whether
StartupSUBTRANS() has already been called, rather than checking
ArchiveRecoveryRequested, as in the attached patch.

Thoughts?

Regards,

--
Fujii Masao

Attachments:

t253420_2
v2-0001-Fix-checkpointer-restartpoint-assertion-failure.patchapplication/octet-stream; name=v2-0001-Fix-checkpointer-restartpoint-assertion-failure.patchDownload+38-4
#3Imran Zaheer
imran.zhir@gmail.com
In reply to: Fujii Masao (#2)
Re: Failing assertion while taking a restartpoint during crash recovery

Hi,

I agree that tracking whether StartupSUBTRANS() has actually been
called is more direct and robust. I don't have any strong objection to
your approach; your fix looks reasonable to me.

My initial concern was more about having too many recovery state
management variables at this point, i.e., ArchiveRecoveryRequested,
InArchiveRecovery, EnableHotStandby, StandbyMode,
StandbyModeRequested, etc. I just wanted to keep the context close to
the existing states and did not want to create a new state for this
specific bug.

Thanks,
Imran Zaheer

Show quoted text

On Sat, Aug 15, 2026 at 4:10 PM Fujii Masao <masao.fujii@gmail.com> wrote:

On Sat, Aug 15, 2026 at 4:57 PM Imran Zaheer <imran.zhir@gmail.com> wrote:

Hi

While doing some benchmarking related to my work with the recovery
pipelining [1], I found that the checkpointer fails when trying to
take a time-based restartpoint during the crash recovery.

Right now I am facing this assertion failure.

Thanks for the report! I was also able to reproduce the assertion failure.

It looks like the checkpointer is not aware of whether hot standby
initialization was done during startup, which happens only in the case
of ArchiveRecoveryRequested [2]. In this specific case, the
checkpointer assumes hot standby initialization would have been done,
so it tries to call TruncateSUBTRANS(). A simple fix is to add
ArchiveRecoveryRequested to recovery shared memory XLogRecoveryCtl;
then the checkpointer will simply skip TruncateSUBTRANS if archive
recovery was not requested. I have attached my patch below.

I think it would be better and more robust to check directly whether
StartupSUBTRANS() has already been called, rather than checking
ArchiveRecoveryRequested, as in the attached patch.

Thoughts?

Regards,

--
Fujii Masao

#4Fujii Masao
masao.fujii@gmail.com
In reply to: Imran Zaheer (#3)
Re: Failing assertion while taking a restartpoint during crash recovery

On Sat, Aug 15, 2026 at 9:28 PM Imran Zaheer <imran.zhir@gmail.com> wrote:

I agree that tracking whether StartupSUBTRANS() has actually been
called is more direct and robust. I don't have any strong objection to
your approach; your fix looks reasonable to me.

Thanks for the review!

I've applied the cosmetic changes to the patch and created patches
for the older branches.

My initial concern was more about having too many recovery state
management variables at this point, i.e., ArchiveRecoveryRequested,
InArchiveRecovery, EnableHotStandby, StandbyMode,
StandbyModeRequested, etc. I just wanted to keep the context close to
the existing states and did not want to create a new state for this
specific bug.

I agree that adding such new recovery state variables basically would
not be a good idea. But, I don't think the flag introduced by this patch
falls into that category. It simply tracks whether pg_subtrans has been
started during recovery, so I don't have much concern about adding it.

Regards,

--
Fujii Masao

Attachments:

t253420_4
v3-PG15-0001-Fix-checkpointer-restartpoint-assertion-failure.txttext/plain; charset=US-ASCII; name=v3-PG15-0001-Fix-checkpointer-restartpoint-assertion-failure.txtDownload+38-4
v3-0001-Fix-checkpointer-restartpoint-assertion-failure.patchapplication/octet-stream; name=v3-0001-Fix-checkpointer-restartpoint-assertion-failure.patchDownload+38-4
v3-PG14-0001-Fix-checkpointer-restartpoint-assertion-failure.txttext/plain; charset=US-ASCII; name=v3-PG14-0001-Fix-checkpointer-restartpoint-assertion-failure.txtDownload+39-4
v3-PG16_PG18-0001-Fix-checkpointer-restartpoint-assertion-failure.txttext/plain; charset=US-ASCII; name=v3-PG16_PG18-0001-Fix-checkpointer-restartpoint-assertion-failure.txtDownload+38-4
#5Michael Paquier
michael@paquier.xyz
In reply to: Fujii Masao (#4)
Re: Failing assertion while taking a restartpoint during crash recovery

On Sun, Aug 16, 2026 at 02:14:32AM +0900, Fujii Masao wrote:

I agree that adding such new recovery state variables basically would
not be a good idea. But, I don't think the flag introduced by this patch
falls into that category. It simply tracks whether pg_subtrans has been
started during recovery, so I don't have much concern about adding it.

I am not concerned with the addition of this new flag. You are
outsourcing a check based on ArchiveRecoveryRequested *and*
EnableHotStandby to rely on a single state once we have called
StartupSUBTRANS(), so you are simplifying the recovery logic. This
argument counts as a +1 here.

What I do see an an issue is that the script used in the report is
mentioned as a link on github. If this content is lost, we would also
lose the history related to this thread. Attaching it here for the
sake of the archives. That's a better practice in general.

Fujii-san, how did you reproduce the problem? Just by reusing the
script? If we've failed detected this defect for so many years,
perhaps this warrants a test? If it proves to be expensive in terms
of cycles, I'd be a bit meh. Perhaps it can be made cheap, even if I
suspect that injection points cannot be used: we need something that
would be set up at very early startup.

One question that has not been raised is if exposing this new flag has
any value for pg_stat_recovery. This could not be a v19 item, perhaps
a v20 item. I don't see how useful it would be as it is no better
than connections getting accepted by the postmaster once a hot standby
is set up (flag would be always true at SQL level).
--
Michael

Attachments:

run_test.tar.gzapplication/gzipDownload
#6Fujii Masao
masao.fujii@gmail.com
In reply to: Michael Paquier (#5)
Re: Failing assertion while taking a restartpoint during crash recovery

On Sun, Aug 16, 2026 at 10:21 AM Michael Paquier <michael@paquier.xyz> wrote:

I am not concerned with the addition of this new flag. You are
outsourcing a check based on ArchiveRecoveryRequested *and*
EnableHotStandby to rely on a single state once we have called
StartupSUBTRANS(), so you are simplifying the recovery logic. This
argument counts as a +1 here.

Thanks for the review!

Fujii-san, how did you reproduce the problem? Just by reusing the
script?

TBH, I didn't see the attached script. Based on Imran's explanation,
I came up with the following procedure to reproduce the issue.

------------------------------------------
initdb -D data
cat <<EOF >> data/postgresql.conf
checkpoint_timeout = 30s
wal_keep_size = 100GB
full_page_writes = off
EOF
pg_ctl -D data start
pg_basebackup -D test -c fast

psql -c "CREATE TABLE t (i uuid primary key, j uuid unique)"
for tmp in $(seq 1 4); do
psql -c "INSERT INTO t SELECT uuidv7(), uuidv4() FROM
generate_series(1, 5000000)" &
done

(*) At this point, I usually wait until a sufficient amount of WAL
has been generated, rather than waiting for all four INSERTs to
complete, since they take quite a while.

pg_ctl -D data -m i stop
mv data/pg_wal/0000000100000000000000* test/pg_wal/
pg_ctl -D test start
------------------------------------------

If we've failed detected this defect for so many years,
perhaps this warrants a test? If it proves to be expensive in terms
of cycles, I'd be a bit meh.

The test would need to trigger a restartpoint during crash recovery,
which I'm afraid could be expensive.

One question that has not been raised is if exposing this new flag has
any value for pg_stat_recovery.

I'm not sure how useful it would be to expose this flag to end users...

Regards,

--
Fujii Masao

#7Fujii Masao
masao.fujii@gmail.com
In reply to: Fujii Masao (#4)
Re: Failing assertion while taking a restartpoint during crash recovery

On Sun, Aug 16, 2026 at 2:14 AM Fujii Masao <masao.fujii@gmail.com> wrote:

I've applied the cosmetic changes to the patch and created patches
for the older branches.

Regarding the patch for v14, since the checkpointer is not started
during crash recovery in v14, it never runs a restartpoint during
crash recovery, so the reported issue cannot occur in the usual way.

But, a fast shutdown during crash recovery does run a restartpoint,
which can trigger the same assertion failure in v14. So the bug fix
should be backpatched to v14 as well, I think.

Regards,

--
Fujii Masao

#8Imran Zaheer
imran.zhir@gmail.com
In reply to: Fujii Masao (#7)
Re: Failing assertion while taking a restartpoint during crash recovery

Hi

I was looking into the patch, and I was wondering if we could make use
of the following small changes.

* Instead of setting the shared state in StartupXLOG, why not set it
in the StartupSUBTRANS itself?
```
@@ -6234,7 +6234,6 @@ StartupXLOG(void)
                         * during recovery and need not be started yet.
                         */
                        StartupSUBTRANS(oldestActiveXID);
-                       SetRecoverySubtransInitialized();
```

* This way we can also add an assertion that subtrans shouldn't
already be initialized.
```
@@ -307,6 +307,8 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
LWLock *prevlock = NULL;
LWLock *lock;

+       Assert(!RecoverySubtransInitialized());
+
        /*
         * Since we don't expect pg_subtrans to be valid across crashes, we
         * initialize the currently-active page(s) to zeroes during startup.
@@ -339,6 +341,8 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
        }
        LWLockRelease(lock);
+
+       SetRecoverySubtransInitialized();
 }
 ```

* Also we can add an assertion while truncating subtrans for safety.

```
@@ -405,6 +409,8 @@ TruncateSUBTRANS(TransactionId oldestXact)
{
int64 cutoffPage;

+       Assert(RecoverySubtransInitialized());
+
```
* There is is another subtrans call under StartupXLOG that can also be improved
```
@@ -6518,7 +6517,7 @@ StartupXLOG(void)
         * Start up subtrans, if not already done for hot standby.  (commit
         * timestamps are started below, if necessary.)
         */
-       if (standbyState == STANDBY_DISABLED)
+       if (standbyState == STANDBY_DISABLED && !RecoverySubtransInitialized())
                StartupSUBTRANS(oldestActiveXID);
```

* Other than that we also call TruncateSUBTRANS() while creating a
checkpoint; maybe we can also improve the guard here, although the
assertion under TruncateSUBTRANS could be enough?

@@ -7879,7 +7878,7 @@ CreateCheckPoint(int flags)
         * in subtrans.c).  During recovery, though, we mustn't do this because
         * StartupSUBTRANS hasn't been called yet.
         */
-       if (!RecoveryInProgress())
+       if (!RecoveryInProgress() && RecoverySubtransInitialized())
                TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());

Thoughts?

Thanks,
Imran Zaheer

#9Fujii Masao
masao.fujii@gmail.com
In reply to: Imran Zaheer (#8)
Re: Failing assertion while taking a restartpoint during crash recovery

On Tue, Aug 18, 2026 at 1:37 PM Imran Zaheer <imran.zhir@gmail.com> wrote:

I was looking into the patch

Thanks!

* Instead of setting the shared state in StartupXLOG, why not set it
in the StartupSUBTRANS itself?

*If* we take this approach, I think the flag should no longer mean that hot
standby initialization has started pg_subtrans, but rather that
StartupSUBTRANS() has completed. We would therefore also need to rename
the flag and update the comments accordingly.

For a bug fix in stable branches, I would prefer to keep the change as
small as possible. Also, subtrans.c says that there are no XLOG
interactions, so having it update shared recovery state in
xlogrecovery.c feels a bit odd to me. I therefore prefer the current
patch and approach.

* Other than that we also call TruncateSUBTRANS() while creating a
checkpoint; maybe we can also improve the guard here, although the
assertion under TruncateSUBTRANS could be enough?

@@ -7879,7 +7878,7 @@ CreateCheckPoint(int flags)
* in subtrans.c).  During recovery, though, we mustn't do this because
* StartupSUBTRANS hasn't been called yet.
*/
-       if (!RecoveryInProgress())
+       if (!RecoveryInProgress() && RecoverySubtransInitialized())
TruncateSUBTRANS(GetOldestTransactionIdConsideredRunning());

I don't think this extra check is necessary. If !RecoveryInProgress(),
StartupSUBTRANS() should already have been called, so the existing
guard should be sufficient.

Regards,

--
Fujii Masao