| From: | João Rito <joaovictor(dot)rito(at)gmail(dot)com> |
|---|---|
| To: | JoongHyuk Shin <sjh910805(at)gmail(dot)com> |
| Cc: | shihao zhong <zhong950419(at)gmail(dot)com>, pgsql-hackers mailing list <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: [PATCH] Add recovery boundary WAL record for database and tablespace commands |
| Date: | 2026-11-11 14:27:39 |
| Message-ID: | CADMs5OMeBCK6rNKVD3BdM6g=ia_4MdrdDoH5GiYdhi6+3bSTjA@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi JoongHyuk,
I tested v2 (both patches) on top of master 9f191dce61e, built with
--enable-cassert --enable-debug --enable-tap-tests. Both patches apply
in order with git apply and build without warnings. These TAP tests pass:
0001: src/test/recovery t/003_recovery_targets.pl (33 tests)
0001+0002: t/003_recovery_targets.pl and t/005_replay_delay.pl
(37 tests)
I also ran PITR by hand: a primary with archive_mode, a base backup,
then DROP DATABASE, CREATE DATABASE (STRATEGY file_copy), CREATE
TABLESPACE, DROP TABLESPACE and ALTER DATABASE SET TABLESPACE, each
preceded by a recorded time, LSN and restore point. I recovered every
case with recovery_target_time (inclusive on and off), LSN (on and
off), name and xid, on master and with 0001, and looked at the
catalog and at base/ and pg_tblspc/ after promotion.
What works well:
- On master, a time target just before the command ends in a broken
state, exactly as you describe. DROP DATABASE: dbx is in the catalog
but its directory is gone ("cannot connect to invalid database").
CREATE DATABASE leaves an orphan base/<oid>. CREATE TABLESPACE leaves
an orphan pg_tblspc/<oid>. DROP TABLESPACE leaves a tablespace in
pg_tablespace with no directory.
- With 0001, the same targets stop with "recovery stopping before
recovery boundary" and the cluster is clean in all four cases, for
both inclusive on and off.
- LSN and name targets behave the same as on master.
- pg_waldump shows the new record as "XLOG2 ... RECOVERY_BOUNDARY time
...", and the record sits just before the first non-transactional
record of each command.
- xid target: your explanation matches what I see. With the xid of
DROP DATABASE and inclusive = off I get the same "cannot connect to
invalid database", and with the xid of the previous transaction and
inclusive = on the database is intact.
Points I found:
1. A time target between the boundary record and the commit record
still breaks the database. The boundary time is taken when the
command starts, but the commit comes later, and CREATE DATABASE and
DROP DATABASE wait for checkpoints in between. In my runs:
- DROP DATABASE, target = boundary time + 1 microsecond, inclusive
on: "recovery stopping before commit of transaction 672", dbx is
in the catalog, its directory is missing, and connecting gives
"cannot connect to invalid database".
- CREATE DATABASE (file_copy), target halfway between the boundary
(13:43:49.99) and the commit (13:43:51.01), inclusive on or off:
recovery stops before the commit and base/16397 is left as an
orphan directory.
I do not see an easy code fix, because recovery only learns about
the commit later. Maybe the docs for recovery_target_time could
say that a target taken while such a command is running can still
leave this result.
2. With recovery_target_inclusive = on and a target exactly equal to
the boundary time, recovery does not stop at the boundary (the test
is boundary_time > target), replays the file operation, and then
stops before the commit because the commit time is later. Result
for DROP DATABASE: dbx in the catalog, directory missing. With
inclusive = off the same target stops before the boundary and the
database is fine. Since the boundary record is not a transaction
that can be "included", maybe it should use >= for both settings.
This is only an edge case (microsecond equality), but the two
settings give different results for the same WAL.
3. 0001 does not touch the documentation. The recovery_target_time
and recovery_target_inclusive text in config.sgml (or the PITR
chapter) does not mention that recovery now also stops before these
commands, nor that xid and LSN targets are not covered (the xid
case Shihao reported). Even one sentence with the workaround you
gave (use the xid of the previous transaction with inclusive = on)
would help operators. 0002 does update the recovery_min_apply_delay
paragraph.
4. Small thing about messages. "recovery stopping before recovery
boundary, time ..." and the pg_waldump line do not say which
command the boundary belongs to, so an operator reading the log
cannot tell if it was a DROP DATABASE or a CREATE TABLESPACE
without looking at the next WAL record. Not important, only a
suggestion (for example a one-byte command type in the record).
For 0002 I only ran the TAP test (005_replay_delay.pl passes with the
new case); I did not test it by hand.
Thanks for working on this. Losing a database or getting orphan
directories after PITR is a real problem.
Regards,
João Rito
Em qua., 11 de nov. de 2026 às 11:18, JoongHyuk Shin
<sjh910805(at)gmail(dot)com> escreveu:
>
> Hi Shihao,
>
> Thanks for the review. I should have described the problem in more
> detail in my first message.
>
> For now, I don't plan to handle the xid case in this patch, in code or
> in the docs. With the xid of DROP DATABASE and
> recovery_target_inclusive = off, recovery stops right before its commit
> record, as documented, but the changes DROP DATABASE makes before
> committing have been replayed by then. Recovery knows it has reached an
> xid target when it reads that transaction's commit record, so the
> operator can instead give the xid of a transaction known to have
> committed before DROP DATABASE started, with
> recovery_target_inclusive = on. Whether the docs should say this is a
> separate question. It is not specific to this patch or to xid targets,
> as an LSN target between those changes and the commit, with
> recovery_target_inclusive = off, leaves the same result on master.
>
> But a time target is different. Recovery knows it has passed a time
> target only when it reads a later timestamp, which on master comes only
> from commit and abort records, so the first one can be the commit of
> DROP DATABASE itself, after its changes have been replayed. The
> boundary record this patch adds gives recovery a timestamp ahead of
> those changes.
>
> v2 is only a rebase onto current master.
>
> --
> JoongHyuk Shin
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Zhijie Hou | 2026-11-11 14:34:35 | Re: Crashes on a partition whose concurrent detach never finished |
| Previous Message | Fujii Masao | 2026-11-11 14:17:08 | Re: pgbench --unlogged-tables silently ignored for branches/tellers/history when --partitions is used |