Skip to content

Add archive_mode=shared for coordinated WAL archiving - #96

Open
reshke wants to merge 45 commits into
masterfrom
sa
Open

Add archive_mode=shared for coordinated WAL archiving#96
reshke wants to merge 45 commits into
masterfrom
sa

Conversation

@reshke

@reshke reshke commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Introduce a new archive_mode setting "shared" to prevent WAL history loss during standby promotion in HA streaming replication setups.

In shared mode, the primary proactively sends archival status updates to standbys via the replication protocol. The standby creates .ready files for received WAL segments but defers marking them as .done until the primary confirms archival. This prevents WAL from being recycled before it's safely archived, addressing a critical gap in PITR continuity during failover.

Key implementation details:

  • Primary periodically sends last archived WAL segment via new PqReplMsg_ArchiveStatusReport ('a') message
  • Standby marks all segments <= reported segment as .done using alphanumeric comparison on segment part (timeline-safe)
  • Archiver skips during recovery in shared mode, activates on promotion
  • Cascading replication: each standby coordinates with immediate upstream
  • Startup check rejects archive_mode=on during recovery

Based on Heikki Linnakangas's 2014 design and Greenplum's production implementation, modernized for current HEAD (PostgreSQL 20 at time).

Includes TAP tests covering basic synchronization, promotion, cascading replication, and multiple standbys scenarios.

Author: Andrey Borodin x4mmm@yandex-team.ru
Co-authored-by: Heikki Linnakangas hlinnaka@iki.fi (earlier versions)
Co-authored-by: Grigory Smolkin smallkeen@gmail.com
Reviewed-by: Jaroslav Novikov njrslv@yandex-team.ru

alvherre and others added 30 commits July 20, 2026 12:12
Some of its code was pointlessly in its caller.  This makes it better
contained and clearer.

Backpatch to 19, to avoid having two different copies in case we have to
modify it again later.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: ChangAo Chen <cca5507@qq.com>
Discussion: https://postgr.es/m/alD9l-XlCuu3eUEe@alvherre.pgsql
Commit 6f6f284 introduced use of LSN_FORMAT_ARGS across the whole
tree to remove use of manual bit-shifting, and commit 2633dae
changed the printf format to be %X/%08X; however commit 28d534e
violated both conventions by reintroducing the old manual-shift style
with the deprecated %X/%X format in one debug message.  Make that new
message conform to our style.

Author:  kenny <kennychen851228@gmail.com>
Backpatch-through: 19
Discussion: https://postgr.es/m/CAPXstDuWD8jg0=C8PXTXGSTTsZcjqJ+u+xKCrMpN99CXsxQzCg@mail.gmail.com
Previously, tests that needed extra postmaster command-line options had
to invoke pg_ctl start directly, because
PostgreSQL::Test::Cluster::start() provided no way to pass them. That
bypassed the test framework's postmaster PID tracking, so a postmaster
could be left running if the test failed after startup.

Add an options parameter to
PostgreSQL::Test::Cluster::start(), which is passed to pg_ctl's
--options argument. This allows tests to use start() while
preserving the framework's normal cleanup behavior.

Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: JoongHyuk Shin <sjh910805@gmail.com>
Discussion: https://postgr.es/m/CAHGQGwEpfE0CDUUODjBt7GO9U4ZF11hqga_Ci3wP8=O49oFKVw@mail.gmail.com
Recovery target parameters are postmaster-startup GUCs, but their
assign hooks previously did more than assign individual parameter
values. They also updated the global recoveryTarget state and raised
ERROR if more than one recovery target appeared to be set.

This was not a good fit for GUC assign hooks. Assign hooks should not
throw ERROR, and deriving cross-parameter state while individual GUCs
are still being assigned makes the result depend on assignment order
rather than the final configuration.

For example, setting one recovery target and then setting another
recovery_target_* parameter to an empty string could clear
recoveryTarget, causing recovery to proceed with no target even
though a valid target remained configured.

Fix this by having the assign hooks only store their own parameter
values. The effective recoveryTarget is now derived once from the
final recovery_target* settings in
validateRecoveryParameters(), which also rejects configurations that
specify more than one recovery target with FATAL. This preserves the
expected behavior for repeated assignments of the same GUC, treats empty
values as "not set", and removes cross-GUC validation from the assign
hooks.

Author: JoongHyuk Shin <sjh910805@gmail.com>
Reviewed-by: Greg Lamberson <greg@lamco.io>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Scott Ray <scott@scottray.io>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Reviewed-by: Henson Choi <assam258@gmail.com>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CACSdjfPUa4UvKjADgOERXoxNYmCg2mqqiqKkiJk6mX6E4qgVFw@mail.gmail.com
Commit 8c852ba allowed exclusion constraints to be added to
partitioned tables, but wasn't careful to verify that pg_restore worked
correctly for them.  Fix that by making CompareIndexInfo() more
selective about what needs to be rejected.

Author: Japin Li <japinli@hotmail.com>
Reported-by: Keith Paskett <keith.paskett@logansw.com>
Discussion: https://postgr.es/m/2A40921D-83AB-411E-ADA6-7E509A46F1E4@logansw.com
It's always been the case that granting these privileges to users that
you don't fully trust was a bad idea, but it hasn't always been
obvious to people reading the documentation that this is the case.
To prevent confusion, and also repeated reports to pgsql-security,
mention it explicitly.

Discussion: http://postgr.es/m/CA+TgmobrjCHBuWHrvX3=2vndUCO2thUOdevrCcMDFW86cqCYvw@mail.gmail.com
Reviewed-by: Nathan Bossart <nathandbossart@gmail.com>
Backpatch-through: 14
Buildfarm member skink reported a failure in
recovery/003_recovery_targets after commit d5751c3. The newly
added recovery_target_xid set-then-cleared test could time out while
waiting for pg_last_wal_replay_lsn() to reach the expected LSN.

The test recorded lsn6 after calling pg_switch_wal(). As a result,
lsn6 pointed into the next WAL segment, but pg_switch_wal() only
archived the previous one. Since the standby in this test restores WAL
from the archive only, it could not obtain the segment containing
lsn6 and waited indefinitely.

Fix this by recording lsn6 before calling pg_switch_wal(), so the
archived WAL contains the LSN that the standby is waiting for.

Per buildfarm member skink.

Reported-by: Álvaro Herrera <alvherre@kurilemu.de>
Author: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/al5Y2mWffRs1NP34@alvherre.pgsql
Commit 8108765 added the logical decoding status to the
pg_controldata output, but overlooked the pg_control_checkpoint() SQL
function, which reports the same checkpoint information. This commit
adds a logical_decoding column to pg_control_checkpoint(), placed
after full_page_writes to match the pg_controldata output order.

Oversight in 8108765.

Bump catalog version.

Reported-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CAHGQGwEkp1-1n5iC38+yHSNh955+KshwtCL6DzA0vk_vuUF_Eg@mail.gmail.com
Backpatch-through: 19
…only.

Sequence synchronization updates sequence state via setval(), which
explicitly calls PreventCommandIfReadOnly(). If
default_transaction_read_only is enabled on the subscriber, this causes
sequencesync workers to fail with "cannot execute setval() in a read-only
transaction". Apply and tablesync workers are not affected, since they
write via direct heap access rather than through these read-only-checked
functions.

Rather than special-casing sequencesync, override
default_transaction_read_only to "off" for all logical replication workers
in InitializeLogRepWorker(), the same way session_replication_role and
search_path are already forced there. This keeps the initialization
uniform.

For PG-19, we kept the fix narrow by overriding
default_transaction_read_only to "off" only for sequencesync workers.

Reported-by: Noah Misch <noah@leadboat.com>
Author: vignesh C <vignesh21@gmail.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Backpatch-through: 19
Discussion: https://postgr.es/m/20260710045217.f0.noahmisch@microsoft.com
If a BEFORE trigger changes NEW.valid_at, what is the interaction with
FOR PORTION OF?  This commit gives a test to capture our current
behavior: The trigger's change replaces the value we computed
automatically, but it does not change the bounds of the temporal
leftovers.

This matches the behavior of MariaDB.  On the other hand, DB2 rejects
changing the start/end columns of a PERIOD.  Since we don't have
PERIODs, we can't reject the change at trigger definition time as DB2
does, but we could reject it at run time by comparing the values
before and after running triggers.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Discussion: https://www.postgresql.org/message-id/CA%2BrenyV3Cr9BvWsPeb1t8b%3DPk24apuzyGbubAEs_YsgLUTfXpg%40mail.gmail.com
…rrors

An in-flight failure when trying to set rd_partcheckcxt or
rd_partcheck, while for example doing an allocation in copyObject(),
would leave a backend cache in a corrupted state.  The operations are
now ordered so as we avoid a leak in the cache memory context and a
semi-filled cache state when an allocation failure happens.

This is unlikely going to be hit in practice.  Like the other
improvements of this kind, no backpatch is done.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/95c64dc2-3abe-4f4e-b285-4c681f565d9f@gmail.com
Change DETAIL messages to conform to the style guide by capitalizing
the first word of sentences and ending sentences with a period.

Author: Peter Smith <peter.b.smith@fujitsu.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: vignesh C <vignesh21.gmail.com>
Reviewed-by: Xiaopeng Wang <wxp_728.163.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Discussion: https://www.postgresql.org/message-id/flat/CAHut%2BPszSntkUgN%2BQa9matGY6MLEoFGSuVbuKDgnnTdZ7YPRwg%40mail.gmail.com
For internally consistent terminology
We've got no reports of problems.  Get rid of it.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Backpatch-through: 19
Discussion: https://postgr.es/m/alewd1f2G0kKeM1i@alvherre.pgsql
from commit c1fe2d1
Change formatting and chars to be less of a match against actual
formatting characters.

Reported-by: Phil

Discussion: https://postgr.es/m/177801333530.795.16999885814007014333@wrigleys.postgresql.org

Backpatch-through: 19
Mention "time zone conversion" as a way to clarify the time zone is not
stored in the database.

Reported-by: Richard Neill

Discussion: https://postgr.es/m/ddf41f033a8add84e1f28a095defafae@richardneill.org

Backpatch-through: 19
This commit adds pgstat_prep_pending_from_entry_ref(), a new pgstats
routine that is able to prepare an existing PgStat_EntryRef to receive
pending stats.  This split gives a way for callers to obtain first a
reference via pgstat_get_entry_ref(), then set up pending data as two
separate, distinctive, steps.

Previously, the only way to get an entry reference with pending data
ready was pgstat_prep_pending_entry(), which bundles lookup, creation,
and pending setup in a single call.  Callers that need finer control
over the entry creation had no way to attach pending data to an
already-obtained entry reference.  One case where this has shown to
matter for a stats kind is where one wants to check some capacity (for
example where a GUC bounds the maximum numer of entries allowed) before
deciding if a new entry should be created.  So this split can help in
reducing calls to pgstat_get_entry_ref(), meaning less shmem hash table
lookups.

The only logical ordering change is that pgStatPendingContext is
initialized after calling pgstat_get_entry_ref() in
pgstat_prep_pending_entry().  This does not matter in practice.

pgstat_prep_pending_entry() is refactored to use the new function
internally.  All the existing callers are unchanged.  Existing
out-of-core custom stats kinds should see no impact.

Author: Sami Imseih <samimseih@gmail.com>
Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAA5RZ0sV+TsLejUMhAM=PJoOm8u-t8ru7B67KvyLCy=19sM87g@mail.gmail.com
If an error happens during the initialization of the TYPEOID catcache,
as part of lookup_type_cache(), the error handling of that lookup would
cause an assertion failure via finalize_in_progress_typentries(), called
during error recovery, the presence of an in-progress type OID causing
a catcache initialization outside of a transaction context.

The in-progress list is now delayed to happen after the initial entry
lookup.  Alexander Lakhin has found a fancy way to reproduce the
problem, with the injection of probabilistic memory allocation failures.

This problem is unlikely going to show up in practice.  Like the other
changes of this kind, no backpatch is done.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/95c64dc2-3abe-4f4e-b285-4c681f565d9f@gmail.com
SummarizeWAL documents that maximum_lsn should be passed as "the switch
point when reading a historic timeline, or the most-recently-measured end of
WAL when reading the current timeline." But the caller always passed the
most recently measured end-of-WAL even when reading from a historic
timeline, due to an oversight on my part. Fix that.

As far as I can determine, for this to become an issue in practice, it's
necessary to have a corrupted WAL file in the archive.  SummarizeWAL checks
that every record it processes both starts and ends before switch_lsn; so if
all the WAL files in the archive are valid, SummarizeWAL will still discover
where it should stop summarizing and do the right thing.  However, if
there's a corrupted file in the WAL archive, and if it is also the case that
the end of the current timeline has advanced past the switch point, then the
incorrect maximum_lsn value can result in trying to read an invalid record
and erroring out, which leads repeatedly retrying and failing with an error
every time.

One way this could occur is if a new primary is promoted and creates a
.partial file, and the user manually renames that file to remove the suffix,
and it is then archived. In that situation, the tail end of the file need
not be valid WAL, and that could lead to a stuck WAL summarizer.

Reported-by: Fabrice Chapuis <fabrice636861@gmail.com>
Analyzed-by: Thom Brown <thom@linux.com> (using claude)
Discussion: http://postgr.es/m/CAA5-nLDdvGMkN6Z-GaHGHG5T7QWEgv4YoHO7XvOJbeD00cghNg@mail.gmail.com
Backpatch-through: 17
Sequence synchronization requires the page_lsn field returned by
pg_get_sequence_data(), which was added in PostgreSQL 19. Previously,
requesting sequence synchronization against an older publisher (via
ALTER SUBSCRIPTION ... REFRESH SEQUENCES or by running
ALTER SUBSCRIPTION ... CONNECTION on a disabled subscription with
sequences in the INIT state and subsequently enabling the subscription)
would cause the sequence synchronization worker to repeatedly fail with a
confusing "invalid query response" error.

Check the publisher's server version up front in both
AlterSubscription_refresh_seq() and copy_sequences(), and error out
immediately when it predates PostgreSQL 19.

Also document the PostgreSQL 19 publisher requirement for sequence
replication in the logical replication documentation and in
ALTER SUBSCRIPTION ... REFRESH SEQUENCES.

Reported-by: Noah Misch <noah@leadboat.com>
Author: vignesh C <vignesh21@gmail.com>
Reviewed-by: Shveta Malik <shveta.malik@gmail.com>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Backpatch-through: 19
Discussion: https://postgr.es/m/20260710045217.f0.noahmisch@microsoft.com
injection_wait() only clears its slot in the waiter array after the
wait loop finishes.  When the waiting query is canceled or the backend
is terminated (wait look has a CHECK_FOR_INTERRUPS), the slot leaks.
Later wakeups of the same point then bump the counter of the leaked slot
instead of the real waiter, that sleeps forever.  Repeated leaks can
exhaust all the slots.

The code is changed so as the waiting loop is wrapped with
PG_ENSURE_ERROR_CLEANUP, so as the injection point slots, that are
shared resources, can be cleaned up on ERROR as much as a FATAL.

An isolation test is added: cancel one waiter, terminate another waiter,
then check that a later waiter still receives a wakeup.  Without the
fixed code, the test would fail on timeout.

Author: Zsolt Parragi <zsolt.parragi@percona.com>
Discussion: https://postgr.es/m/CAN4CZFO+KF=cc0-iEg28RhqRBp_fTs6D4b8b7D7DB-pGYP3Ccg@mail.gmail.com
Backpatch-through: 17
The documentation of pg_stat_activity used an incomplete list of values
for backend_type.  While on it, it is improved to use an itemized list,
now ordered alphabetically, with a short description about each item.

Author: Laurenz Albe <laurenz.albe@cybertec.at>
Reviewed-By: Michael Paquier <michael@paquier.xyz>
Reviewed-By: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/5e94c0196084f648ae6a00107125494f5804318a.camel@cybertec.at
socket_putmessage_noblock() used pq_putmessage(), which redirects to
PqCommMethods->putmessage.  In the common cases, this points to
socket_putmessage(), but it would become incorrect if PqCommMethods
points to a different implementation.

This change may look like a bug, but as far as I can see this is mostly
cosmetic.  The code is able to work currently, as the repalloc() done in
the noblock() call ensures that the blocking path of internal_putbytes()
is never reached.  The issue has gone unnoticed since 2bd9e41.

Author: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CAO6_Xqpf5+Rzw_-XOOz-d-R5x6_2JHtpnzXP0nrYWiHyZokA_Q@mail.gmail.com
Improve the documentation for pg_stat_recovery in several ways:

- Mention the view in high-availability.sgml as a way to monitor
  recovery state and replay progress, alongside the existing recovery
  information functions.
- Clarify that the view returns at most one row, not exactly one row,
  and no rows to users who lack the pg_read_all_stats privilege.
- Correct the description of last_replayed_end_lsn to clarify that it
  is the end LSN of the last replayed record plus one.
- Document that replay_end_tli equals last_replayed_tli when no WAL
  record is currently being replayed.
- Clarify that current_chunk_start_time is NULL until streaming WAL
  has been received.

Backpatch to v19, where pg_stat_recovery was introduced.

Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAHGQGwGRavm18HqnQn_f68QB96qk6arhjET1V93OJH09Mgojkg@mail.gmail.com
Backpatch-through: 19
For subscriptions using SERVER, changing the owner can change the
effective connection string. However, ALTER SUBSCRIPTION ... OWNER TO
did not validate the generated conninfo for the new owner.

As a result, ownership could be transferred to a non-superuser whose
generated connection string did not satisfy password_required=true.
The ownership change succeeded, but the subscription would fail later
when the worker or another command tried to connect.

Fix this by making ALTER SUBSCRIPTION ... OWNER TO validate the new
owner's generated conninfo with walrcv_check_conninfo().

Backpatch to v19, where SERVER subscriptions were introduced.

Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Yuanchao Zhang <145zhangyc@gmail.com>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Discussion: https://postgr.es/m/CAHGQGwFGa6+wWVgUmZPFwN=fBY59mYPkMK3=TxT=Pv5C1mNNRQ@mail.gmail.com
Backpatch-through: 19
Commit fd36606 added tests intended to verify that rows inserted
on the publisher are replicated to the subscriber when using multiple
publications, with one excluding the target table via EXCEPT and
another including it.

However, the tests queried the publisher instead of the subscriber.
Since the rows were inserted directly into the publisher, the checks
would always succeed, providing no coverage of replication.

Fix this by querying the subscriber so the tests verify the replicated
state.

Author: Fujii Masao <masao.fujii@gmail.com>
Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Discussion: https://postgr.es/m/CAHGQGwGfXUO7f4t6KNGurYwg6QsnLtpP0K3EACbAwYWtxGfKfQ@mail.gmail.com
Backpatch-through: 19
Document table_name, column_name, and schema_name in the CREATE
PUBLICATION and ALTER PUBLICATION reference pages. Also add anchors for
the ALTER PUBLICATION parameter list, matching the style already used by
CREATE PUBLICATION.

Author: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CAHut+Ptekz+TO4ui8-fiBm4Y+O2v=HQnkK_cW4G=w9ep8654EA@mail.gmail.com
Previously, if a sequence synchronization batch contained both a sequence
that had been dropped on the publisher and another for which the
replication role lacked SELECT privilege, the latter was reported
twice: once as a permission failure and again as missing on the
publisher.

This happened because the permission-denied sequence was not marked as
found on the publisher. As a result, when another sequence in the batch
was genuinely missing, the later missing-sequence check incorrectly
classified the permission-denied sequence as missing as well.

Fix this by marking the permission-denied sequence as found before
reporting the permission failure, so it is not later reported as
missing.

Reported-by: Noah Misch <noah@leadboat.com>
Author: Vignesh C <vignesh21@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CALDaNm3LsUjW7PahuCsbYAxajSF+S328tw5E9rF0erdh7dKOXw@mail.gmail.com
Backpatch-through: 19
MasaoFujii and others added 10 commits July 25, 2026 19:08
pg_database_size() allows access to users who have either CONNECT
privilege on the target database or privileges of the pg_read_all_stats
role. However, previously, psql's \l+ checked only for CONNECT,
so users with privileges of pg_read_all_stats still saw "No Access" for
databases they could not connect to.

Fix this by making \l+ also check
pg_has_role('pg_read_all_stats', 'USAGE'), matching
pg_database_size()'s permission rules.

For back branches, emit the pg_read_all_stats check only when
connected to PostgreSQL 10 or later, since earlier releases do not have
that predefined role.

Backpatch to all supported versions.

Author: Christoph Berg <myon@debian.org>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/amCo6qRmnfPVk4-V@msg.df7cb.de
Backpatch-through: 14
Commit f9b7fc6 fixed a race when predicate-locking completely empty
btrees: without a buffer lock held, a matching key could be inserted
between _bt_search and the PredicateLockRelation call, so the scan would
miss concurrently inserted tuples while the writer wouldn't see the
reader's predicate lock.  That commit only fixed _bt_first's _bt_search
path, though.  Scans without useful insertion scan keys return early
from _bt_first via _bt_endpoint, which still didn't recheck if the
relation was empty.

To fix, add handling to _bt_endpoint that is analogous to the handling
added to _bt_search by commit f9b7fc6.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-WzkNoTn3yXY0iGkSuavJ+sL8EROf+kitW+_2v2tJVWuKmA@mail.gmail.com
Backpatch-through: 14
Oversight in commit fb23cc7.

Reported-by: Anton Voloshin <a.voloshin@postgrespro.ru>
Discussion: https://postgr.es/m/ad5d772e-09d9-4248-97a4-0011afab9e71@postgrespro.ru
Add coverage for predicate locking of completely empty nbtree indexes,
where we must predicate lock the entire relation (instead of some
individual leaf page).  Both paths that can find the index empty (and
must consider whether it's still empty after PredicateLockRelation
returns) are covered by a new isolation test that uses injection points.

Catalog relation scans skip the injection points.  The waiting session
runs catalog queries of its own after arming the (session-local) points,
and could otherwise suspend itself with nothing lined up to wake it.

Follow-up to bugfix commits ce3f19e (the _bt_endpoint fix) and f9b7fc6
(the _bt_first/_bt_search fix).

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-WzkNoTn3yXY0iGkSuavJ+sL8EROf+kitW+_2v2tJVWuKmA@mail.gmail.com
Backwards scans have unique concurrency rules: rather than unreservedly
trusting a saved left link, the scan optimistically rechecks its
pointed-to leaf page's right link (i.e. whether it still points back to
the page that _bt_readpage just read).  Usually, the left sibling of the
just-read page won't have changed, in which case the scan can proceed
with reading the left sibling as planned.  But it's possible that the
key space that the scan needs to read next is no longer covered by the
original left sibling page due to concurrent page splits and/or page
deletions.  When that happens, the scan must recover by relocating the
new/current left sibling of the just-read page.

Test coverage for backwards scans was limited to the happy path.  Add an
isolation test (and associated injection points) that test the recovery
path.  This covers several distinct recovery scenarios (concurrent page
splits, concurrent page deletions, and minor variants thereof).

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/CAH2-WzmD+jUBOpFS2jrnqqrdPSAjoxqyL9FPKaE1BtnY=8Nntg@mail.gmail.com
Add pg_regress tests that exercise the row compare logic that commit
7d9cd2d added to _bt_set_startikey.  Also add tests that exercise the
_bt_set_startikey SAOP array path.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-Wz=KjQsD2W2a=b51uH905=0mF6Le4evhWkN2FL1+uRPhUg@mail.gmail.com
Backpatch-through: 19
This replaces an O(N) hash_seq_search() loop by an O(1) lookup, removing
a TODO item, making the invalidation callback faster when dealing with
many relations.  This can work because LogicalRepPartMap is keyed by a
partition OID, and a relmapentry's localreloid matches with it.

An assertion is added in logicalrep_partition_open() to enforce the fact
that localreloid matches with the hash key.

Author: DaeMyung Kang <charsyam@gmail.com>
Discussion: https://postgr.es/m/20260417174450.4158878-1-charsyam@gmail.com
Noticed while doing some routine work.

Oversight in e395fbd.
Commit 8d829f5 introduced the JSCTOR_JSON_ARRAY_QUERY constructor
type so that ruleutils.c could deparse JSON_ARRAY(subquery) using its
original syntax, storing the transformed subquery in a new orig_query
field.  However, the input FORMAT clause of JSON_ARRAY(subquery FORMAT
...) was not preserved for deparsing.  The format was recorded only in
the executable expression kept in the func field, which ruleutils.c
does not inspect, so it is silently dropped.

This is more than cosmetic, because FORMAT JSON changes the result:
without it a text value is treated as a string to be quoted, while
with it the value is treated as already-formatted JSON.

To fix, record the input FORMAT in a new deparse-only field of
JsonConstructorExpr, alongside orig_query, and emit it in ruleutils.c.

Bump catalog version.

Author: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Ewan Young <kdbase.hack@gmail.com>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/4C89B193-7D54-4705-9CF9-F0D484B9E099@gmail.com
Backpatch-through: 19
1. Stop a running sequence synchronization worker when
ALTER SUBSCRIPTION ... DISABLE is executed.  The worker did not reread its
subscription after starting a transaction, so it kept running with a stale
copy and missed the disable. It now calls maybe_reread_subscription()
after StartTransactionCommand(), matching the apply worker.

2. Restore the invariant that publisher-side synchronization slots are
dropped last during ALTER SUBSCRIPTION ... REFRESH PUBLICATION.  The
slot-drop loop now runs after the sequence-removal loop, so the
non-transactional slot drops happen only after all catalog changes that
could still be rolled back on error.

3. Restore psql tab completion for
ALTER SUBSCRIPTION ... REFRESH PUBLICATION WITH (.

4. Make pg_stat_subscription report NULL for the fields that do not apply
to a sequence synchronization worker, which does not stream from a
walsender, and update the documentation accordingly.

5. Update the pg_subscription_rel.srsublsn catalog documentation to
describe its semantics for sequence rows.

Reported-by: Noah Misch <noah@leadboat.com>
Author: vignesh C <vignesh21@gmail.com>
Reviewed-by: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Backpatch-through: 19, where it was introduced
Discussion: https://postgr.es/m/20260710045217.f0.noahmisch@microsoft.com
@reshke
reshke force-pushed the sa branch 7 times, most recently from 8d4ed68 to 82b6bd1 Compare July 29, 2026 14:13
x4m and others added 2 commits July 29, 2026 19:20
This adds a new archive_mode setting "shared" to prevent WAL history
loss during standby promotion in HA streaming replication setups.

In shared mode, the standby tracks which files have been archived by the
primary. The standby refrains from recycling files that the primary has not
yet archived, and at failover, the standby archives all those files too
from the old timeline. This prevents WAL from being recycled
before it's safely archived, addressing a possible gap in PITR continuity
during failover.

Primary sends last archived WAL segment every
`archive_status_report_interval` ms via new walsender protocol message.
Standby marks all segments <= reported segment as .done on current
timeline. For any ancestor timeline, standby marks all segments which
are in its history as .done.

Cascading replication on each standby coordinates with immediate
upstream. Standby relays its primary last archived WAL donwstream.

Implementaion based on Heikki Linnakangas's 2014 design & patсh, also we
grabbed some ideas & tests from Greenplum's production implementation[0],
and modernized the whole thing for current HEAD (PostgreSQL 20 at time).

Includes TAP tests covering basic synchronization, promotion,
cascading replication. Also includes additional test for checkpoint
wal recycling on standby logic in shared-archive mode.

Author: Andrey Borodin <x4mmm@yandex-team.ru>
Co-authored-by: Heikki Linnakangas <hlinnaka@iki.fi> (earlier versions)
Co-authored-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com> (earlier versions)
Reviewed-by: Grigory Smolkin <smallkeen@gmail.com>
Reviewed-by: Jaroslav Novikov <njrslv@yandex-team.ru>

[0] open-gpdb/gpdb@4f2db19
Comes in-handy for monitoring shared archive feature. TAP tests for
shared archive feature were extended to check for this column value.

Bumps catversion.
Kirill Reshke and others added 3 commits July 29, 2026 20:30
Add a case to 055_archive_shared.pl where the primary runs with
archive_mode=off while the standby runs in shared mode. Since the
primary never starts an archiver and never sends archival status
reports, the standby must keep received segments as .ready, must not
mark any segment as .done, and pg_stat_wal_receiver.primary_last_archived
must stay empty.
Add 057_archive_shared_cascade_on.pl covering a chain where the primary
and the intermediate standby both run archive_mode=on and only the
cascading standby runs archive_mode=shared.

The cascading standby connects successfully because its upstream has
archiving active, but the intermediate standby never requested archival
reports from the primary and therefore has nothing to relay. The test
pins down that the cascading standby keeps received segments as .ready,
marks nothing as .done via a report, and leaves
pg_stat_wal_receiver.primary_last_archived empty.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants