pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedsequencesync worker race with REFRESH SEQUENCES
82+ messages / 10 participants
[nested] [flat]
* sequencesync worker race with REFRESH SEQUENCES
@ 2026-07-10 04:52 Noah Misch <noah@leadboat.com>
2026-07-10 07:00 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-13 12:52 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 7 replies; 82+ messages in thread
From: Noah Misch @ 2026-07-10 04:52 UTC (permalink / raw)
To: amit.kapila16@gmail.com; vignesh21@gmail.com; +Cc: pgsql-hackers
A Fable 5 review of logical replication of sequences found a way to get
subscribed sequences into READY state despite the subscriber side having data
older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
I reviewed the test, and I think it identifies a genuine defect.
Fable 5 also wrote a lot more that neither it nor I confirmed by test case
construction. I'm attaching the report; feel free to disregard. Finding-2
about default_transaction_read_only=on looks worth fixing if true, as do
finding-7 assert failure and finding-17 about documenting a PG19+ publisher
requirement. Finding-6, publisher version guard, I'd NOT do or do at warning
level if an older publisher could plausibly work via an extension to provide
the needed function. The other findings are gray areas, perhaps.
commit fe331b9 (HEAD -> seq-refresh-sequences-race)
Author: Noah Misch <noah@leadboat.com>
AuthorDate: Thu Jul 9 19:39:45 2026 +0000
Commit: Noah Misch <noah@leadboat.com>
CommitDate: Thu Jul 9 19:42:17 2026 +0000
Add test revealing REFRESH SEQUENCES race with sequencesync worker.
ALTER SUBSCRIPTION ... REFRESH SEQUENCES resets all sequence entries in
pg_subscription_rel to INIT (AlterSubscription_refresh_seq) without
stopping or fencing an in-flight sequencesync worker. The worker
fetches publisher values before taking any local lock, and
copy_sequence() later applies them and marks each sequence READY
without rechecking the entry's state. A batch whose values were
fetched before the refresh therefore overwrites the INIT reset
afterwards: the sequences report READY while holding pre-refresh
publisher values, with no error, no warning, and no pending
resynchronization. A user following the documented pre-failover
procedure (REFRESH SEQUENCES, wait for READY) can then fail over to a
subscriber whose sequences are behind the publisher, yielding duplicate
sequence values.
The test constructs the schedule deterministically: it blocks the
worker's batch query on the publisher via AccessExclusiveLock on a
sequence, blocks local application via ShareRowExclusiveLock on the
sequences on the subscriber, and runs REFRESH SEQUENCES inside the
fetch-to-apply window.
The final two assertions check that sequences reporting READY reflect
every value published before REFRESH SEQUENCES was issued. They
currently fail, demonstrating the defect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKt6fYFvAnMUMyavJmmueE
---
src/test/subscription/meson.build | 1 +
.../subscription/t/039_sequences_refresh_race.pl | 184 +++++++++++++++++++++
2 files changed, 185 insertions(+)
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index e71e95c..067fca2 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -48,6 +48,7 @@ tests += {
't/036_sequences.pl',
't/037_except.pl',
't/038_walsnd_shutdown_timeout.pl',
+ 't/039_sequences_refresh_race.pl',
't/100_bugs.pl',
],
},
diff --git a/src/test/subscription/t/039_sequences_refresh_race.pl b/src/test/subscription/t/039_sequences_refresh_race.pl
new file mode 100644
index 0000000..5173231
--- /dev/null
+++ b/src/test/subscription/t/039_sequences_refresh_race.pl
@@ -0,0 +1,184 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that ALTER SUBSCRIPTION ... REFRESH SEQUENCES is not silently
+# overwritten by an in-flight sequencesync worker.
+#
+# The sequencesync worker fetches sequence values from the publisher and
+# only afterwards applies them and marks the sequences READY, without
+# rechecking pg_subscription_rel state. REFRESH SEQUENCES resets all
+# sequences to INIT without stopping or fencing a running worker. Hence a
+# batch whose values were fetched before the refresh can mark sequences
+# READY afterwards, leaving them holding pre-refresh values with no error,
+# no warning, and no pending resynchronization. A user following the
+# documented pre-failover procedure (run REFRESH SEQUENCES, wait for READY)
+# can then fail over to a subscriber whose sequences are behind the
+# publisher, producing duplicate sequence values.
+#
+# This test constructs that schedule deterministically:
+#
+# 1. Hold AccessExclusiveLock on a sequence on the publisher, so the
+# worker's remote batch query blocks inside pg_get_sequence_data()
+# after the worker has committed its scan of pg_subscription_rel.
+# 2. While the fetch is blocked, take ShareRowExclusiveLock on all
+# sequences on the subscriber, so that after the fetch completes the
+# worker blocks at its first try_table_open(), i.e. after fetching
+# values but before updating any pg_subscription_rel row.
+# 3. Release the publisher lock; the worker's fetch completes with the old
+# values and the worker blocks on the subscriber locks.
+# 4. Advance the sequences on the publisher, then run
+# ALTER SUBSCRIPTION ... REFRESH SEQUENCES; it resets both sequences to
+# INIT and commits, not blocked by the in-flight worker.
+# 5. Release the subscriber locks. The worker applies the stale values
+# and flips both sequences INIT -> READY.
+#
+# On an unpatched server the final assertions fail: all sequences report
+# READY but hold the values fetched before the refresh.
+#
+# Note for whoever fixes the race: this choreography assumes REFRESH
+# SEQUENCES continues to complete without waiting for an in-flight
+# sequencesync worker (as it does today). If the fix instead makes the
+# command block until a running worker exits, step 4 will wait behind the
+# sequence locks taken in step 2 and the test will hang there rather than
+# fail; the schedule would need reshaping for a fix of that shape.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init;
+$node_subscriber->start;
+
+# Identical sequence definitions on both nodes.
+my $ddl = qq(
+ CREATE SEQUENCE regress_seq1;
+ CREATE SEQUENCE regress_seq2;
+);
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+# Consume some sequence values on the publisher.
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT nextval('regress_seq1') FROM generate_series(1, 100);
+ SELECT nextval('regress_seq2') FROM generate_series(1, 100);
+));
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION regress_seq_pub FOR ALL SEQUENCES");
+
+# Create the subscription disabled, so the locks below can be positioned
+# before the sequencesync worker starts. The replication slot is created
+# here, which must precede the publisher-side open transaction below
+# because slot creation waits for concurrent transactions to finish.
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub WITH (enabled = false)"
+);
+
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubstate = 'i'");
+is($result, '2', 'both sequences start in INIT state');
+
+# Block the sequencesync worker's batch query on the publisher: an
+# uncommitted DROP SEQUENCE holds AccessExclusiveLock, on which the
+# pg_get_sequence_data() call in the batch query will wait.
+my $pub_session = $node_publisher->background_psql('postgres');
+$pub_session->query_safe(
+ qq(
+ BEGIN;
+ DROP SEQUENCE regress_seq1;
+));
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub ENABLE");
+
+# Wait until the worker's batch query is blocked on the publisher. At
+# this point the worker has committed the transaction that scanned
+# pg_subscription_rel and holds no locks on the subscriber.
+$node_publisher->poll_query_until(
+ 'postgres', qq(
+ SELECT EXISTS (
+ SELECT 1 FROM pg_locks
+ WHERE relation = 'regress_seq1'::regclass AND NOT granted);
+)) or die "timed out waiting for sequencesync worker to block on publisher";
+
+# Take locks conflicting with the worker's try_table_open() on both
+# sequences, so the worker will block after its fetch completes but before
+# it updates any pg_subscription_rel row, whatever order it processes the
+# sequences in. The ALTERs are rolled back later, so the definitions stay
+# identical to the publisher's.
+my $sub_session = $node_subscriber->background_psql('postgres');
+$sub_session->query_safe(
+ qq(
+ BEGIN;
+ ALTER SEQUENCE regress_seq1 MINVALUE 1;
+ ALTER SEQUENCE regress_seq2 MINVALUE 1;
+));
+
+# Release the publisher lock: the fetch completes with the current
+# publisher values, then the worker blocks on the subscriber locks.
+$pub_session->query_safe("ROLLBACK");
+$pub_session->quit;
+
+$node_subscriber->poll_query_until(
+ 'postgres', qq(
+ SELECT EXISTS (
+ SELECT 1 FROM pg_locks
+ WHERE relation IN ('regress_seq1'::regclass, 'regress_seq2'::regclass)
+ AND NOT granted);
+)) or die "timed out waiting for sequencesync worker to block on subscriber";
+
+# The values held by the blocked worker now become stale.
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT nextval('regress_seq1') FROM generate_series(1, 100);
+ SELECT nextval('regress_seq2') FROM generate_series(1, 100);
+));
+
+# Request resynchronization of all sequences. This resets both sequences
+# to INIT and commits; it does not wait for the in-flight worker.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubstate = 'i'");
+is($result, '2', 'REFRESH SEQUENCES reset both sequences to INIT');
+
+# Release the subscriber locks, letting the worker proceed.
+$sub_session->query_safe("ROLLBACK");
+$sub_session->quit;
+
+# Wait until the subscription reports all sequences synchronized.
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_subscription_rel WHERE srsubstate <> 'r'")
+ or die "timed out waiting for sequences to reach READY state";
+
+# All sequences report READY, so they must not predate the last REFRESH
+# SEQUENCES command: every value published before that command was issued
+# must be reflected on the subscriber. On an unpatched server these fail,
+# with the subscriber sequences left at the values fetched before the
+# refresh.
+my $pub_seq1 = $node_publisher->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq1");
+my $sub_seq1 = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq1");
+is($sub_seq1, $pub_seq1,
+ 'READY regress_seq1 reflects publisher values from before REFRESH SEQUENCES'
+);
+
+my $pub_seq2 = $node_publisher->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq2");
+my $sub_seq2 = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq2");
+is($sub_seq2, $pub_seq2,
+ 'READY regress_seq2 reflects publisher values from before REFRESH SEQUENCES'
+);
+
+done_testing();
# Logical replication of sequences: user-visible defects in master
**Scope:** defects introduced by commits f0b3573c3, 5509055d6, 55cefadde (and still present through follow-up fixes), verified against master @ a8c2547. All file:line references are to that tree.
## Executive summary
An adversarial audit of the PG19 sequence-replication feature identified 20 user-visible defects; two pairs share a single root cause and are merged below, leaving 18 items. Every item is **CONFIRMED** — the mechanism was traced end-to-end in source by an independent verifier — but none has been executed as a runtime repro, so the repro sketches should be run before relying on exact symptoms; there are no PLAUSIBLE-grade items in this report. The dominant pattern is missing coordination around the new shared sequencesync worker: no ALTER SUBSCRIPTION path stops or fences it, and it never rechecks subscription or relation state after startup, yielding (worst case) sequences silently marked READY with pre-refresh publisher values in the documented pre-failover `REFRESH SEQUENCES` workflow — i.e., duplicate sequence values after failover, the exact hazard the feature exists to prevent. A second pattern is single-object failures escalating to subscription-wide outages: because one worker serves all sequences and several failure paths bypass the per-sequence warning framework, one misconfigured sequence, a read-only default, a lock-table squeeze, or an older publisher produces an unbounded 5-second error/relaunch loop (or full auto-disable under `disable_on_error`). The remainder are internal XX000 errors reachable from ordinary DDL concurrency, misleading diagnostics, and monitoring/documentation never updated for the new worker type and relation kind.
## Index
| # | Finding | Severity | Confidence | Anchor |
|---|---------|----------|------------|--------|
| 1 | Refresh paths race the in-flight sequencesync worker: stale values marked READY / XX000 + auto-disable *(merged: 2 findings)* | High | CONFIRMED | src/backend/commands/subscriptioncmds.c:1392, :1336 |
| 2 | `default_transaction_read_only=on` permanently blocks sequence sync via setval() read-only check | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:407 |
| 3 | `run_as_owner=false`: un-SET-ROLE-able sequence owner kills the shared worker with an unattributed error | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:387 |
| 4 | Gathering scan holds RowExclusiveLock on every INIT relation in one unbounded transaction: blocking + lock-table exhaustion loop | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:718 |
| 5 | Sequencesync worker starved by tablesync workers; silent deferral of REFRESH SEQUENCES | Medium | CONFIRMED | src/backend/replication/logical/syncutils.c:127 |
| 6 | No publisher-version guard: pre-PG19 publisher yields perpetual cryptic protocol-violation loop | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:529 |
| 7 | `has_sequence_privilege()` NULL on concurrent publisher drop: Assert crash / misreported as permission failure | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:298 |
| 8 | Permission-denied sequences also listed as "missing on publisher" in mixed batches | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:657 |
| 9 | ALTER SUBSCRIPTION ... DISABLE never stops a running sequencesync worker | Low | CONFIRMED | src/backend/replication/logical/sequencesync.c:447 |
| 10 | REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE: internal XX000 errors | Low | CONFIRMED | src/backend/commands/subscriptioncmds.c:1387 |
| 11 | Sequence-removal loop placed after irreversible tablesync slot drops, violating stated invariant | Low | CONFIRMED | src/backend/commands/subscriptioncmds.c:1322 |
| 12 | Batch query mixes MVCC definition columns with live `last_value`: validation bypass under concurrent ALTER SEQUENCE | Low | CONFIRMED | src/backend/replication/logical/sequencesync.c:517 |
| 13 | REFRESH SEQUENCES emits "requested copy_data" warning for an option the command does not accept | Low | CONFIRMED | src/backend/commands/subscriptioncmds.c:3272 |
| 14 | psql tab-completion regression after `REFRESH PUBLICATION` (offers publication names / nothing instead of `WITH (`) | Low | CONFIRMED | src/bin/psql/tab-complete.in.c:2356 |
| 15 | pg_stat_subscription row for sequencesync worker: fabricated message times, NULL columns undocumented *(merged: 2 findings)* | Low | CONFIRMED | src/backend/replication/logical/worker.c:5980; doc/src/sgml/monitoring.sgml:2336 |
| 16 | catalogs.sgml `srsublsn` description wrong for sequence rows | Low | CONFIRMED | doc/src/sgml/catalogs.sgml:8894 |
| 17 | Docs never state sequence sync requires a PG19+ publisher; REFRESH SEQUENCES silently no-ops | Low | CONFIRMED | doc/src/sgml/logical-replication.sgml:2359 |
| 18 | Restrictions bullet still claims only tables can be replicated | Low | CONFIRMED | doc/src/sgml/logical-replication.sgml:2401 |
---
### 1. Refresh paths race the in-flight sequencesync worker (High)
**Merged finding.** Two audit findings are merged here because they share one root cause: no ALTER SUBSCRIPTION refresh path stops or fences a running sequencesync worker (the table path explicitly stops tablesync workers, subscriptioncmds.c:1257, and its removal path documents that its `pg_subscription_rel` lock exists to freeze rel states, :1333-1334), and the worker never re-validates a row between deciding to sync it and writing it.
**Defect.** (A, high) `ALTER SUBSCRIPTION ... REFRESH SEQUENCES` can be silently overwritten by the worker's in-flight batch: up to 100 sequences end up `srsubstate='r'` holding publisher values fetched *before* the refresh, with no error, no warning, and no pending resync. (B, medium) The PUBLICATION refresh forms (`REFRESH PUBLICATION`, `SET/ADD/DROP PUBLICATION`) remove de-published sequence rows out from under the worker, which then raises internal XX000 `subscription relation %u in subscription %u does not exist` after having already applied a non-transactional value overwrite; with `disable_on_error=true` the entire subscription (tables included) is disabled.
**Mechanism.** (A) `AlterSubscription_refresh_seq()` resets every sequence row to `SUBREL_STATE_INIT` (subscriptioncmds.c:1392) with no worker interlock. The worker fetches publisher values with `walrcv_exec` before holding any relevant lock (sequencesync.c:529), then `copy_sequence()` unconditionally applies `SetSequence()` (:407) and `UpdateSubscriptionRelState(..., SUBREL_STATE_READY, ...)` (:416) without rechecking `srsubstate`. `UpdateSubscriptionRelState`'s `LockSharedObject` waits out the ALTER and absorbs its invalidations (pg_subscription.c:405; lmgr.c:1102), so the worker *cleanly* flips the freshly-reset rows to READY with stale values; `FetchRelationStates` keys resync off non-READY rows, so the loss is permanent and invisible. (B) The removal loop calls `RemoveSubscriptionRel()` (subscriptioncmds.c:1336) without `logicalrep_worker_stop`; the worker's subsequent `UpdateSubscriptionRelState` hits `elog(ERROR, ...)` at pg_subscription.c:413-415 — after `SetSequence` (non-transactional, like setval) already overwrote the just-de-published local sequence. PG_CATCH at sequencesync.c:803-819 then either error-loops or runs `DisableSubscriptionAndExit`.
**Trigger.** (A) Any `REFRESH SEQUENCES` committing while a sequencesync batch is between its publisher fetch and its first catalog update — probabilistic at every batch boundary under continuous publisher `nextval()` traffic; deterministic with a lock stall. (B) `DROP PUBLICATION pub_seq` (default `refresh=true`) or `REFRESH PUBLICATION` while initial sequence sync is in flight.
**Repro sketch.** Two nodes, 101 sequences, `FOR ALL SEQUENCES` publication, subscription created. Subscriber session A after the worker's initial scan: `BEGIN; ALTER SEQUENCE s101 RESTART 1;` (ShareRowExclusiveLock stalls the worker at `try_table_open`, sequencesync.c:336, *after* it fetched batch-2 values). Publisher: advance `nextval('s101')` repeatedly. Subscriber session B: `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` (succeeds, all rows → 'i'). Session A: `ROLLBACK;` → worker marks s101 'r' with the pre-refresh value; failover now yields duplicate sequence values. For (B): 300 sequences + `disable_on_error=true`, then `ALTER SUBSCRIPTION sub DROP PUBLICATION pub_seq;` mid-sync → XX000 in the log and the subscription disabled.
**Fix direction.** Stop (or fence) sequencesync workers in all refresh paths, as the tablesync path does, and/or make `copy_sequence()` re-verify under lock that the row still exists in the expected pre-sync state before writing SetSequence/READY, discarding stale fetched values.
---
### 2. `default_transaction_read_only=on` permanently blocks sequence sync (Medium)
**Defect.** On a subscriber with `default_transaction_read_only=on` — the read-only-subscriber deployment logical-replication.sgml itself describes — tables copy and apply normally, but sequences never sync: the worker fails every `wal_retrieve_retry_interval` with the misleading `cannot execute setval() in a read-only transaction` (25006) for a setval() the user never ran; rows stay 'i' forever, `seq_sync_error_count` climbs, and `disable_on_error=true` disables the whole subscription.
**Mechanism.** `copy_sequence()` applies values via `SetSequence()` (sequencesync.c:407), which runs `PreventCommandIfReadOnly("setval()")` for permanent sequences (sequence.c:976-977). Batch transactions are plain `StartTransactionCommand` (sequencesync.c:462), inheriting `XactReadOnly = DefaultXactReadOnly` (xact.c:2172); worker GUC setup overrides only session_replication_role/search_path/synchronous_commit (worker.c:5798-5810). Every other subscriber write path bypasses read-only checks: tablesync calls `BeginCopyFrom`/`CopyFrom` directly (tablesync.c:1209-1213; the only COPY check lives in `DoCopy`, copy.c:365) and apply uses `ExecSimpleRelation*` with no check — so sequences are uniquely broken.
**Trigger.** Set `default_transaction_read_only=on` in subscriber postgresql.conf (or per-database); subscribe to a `FOR ALL TABLES, ALL SEQUENCES` publication.
**Repro sketch.** Publisher: table t + sequence s, `nextval('s')`, publication for both. Subscriber (GUC on; use a session-local `SET default_transaction_read_only=off` for the DDL): create matching objects, `CREATE SUBSCRIPTION`. Observe t replicating while the log repeats the setval error and s stays 'i'. Undocumented workaround exists (`ALTER ROLE <subscription owner> SET default_transaction_read_only = off`), but nothing hints at it.
**Fix direction.** Have the sequencesync worker's transactions clear `XactReadOnly` (or exempt this internal caller from the setval() read-only check), matching the implicit exemption tablesync/apply already enjoy.
---
### 3. `run_as_owner=false`: one un-SET-ROLE-able sequence owner stalls all sequence sync, unattributed (Medium)
**Defect.** With the default `run_as_owner=false`, one sequence whose owner the subscription owner cannot `SET ROLE` to makes the shared worker die with `role "X" cannot SET ROLE to "Y"` (42501) — an error that never names the sequence — permanently preventing *all* remaining sequences (including fully-compliant ones) from leaving state 'i'; `disable_on_error=true` disables the whole subscription.
**Mechanism.** `copy_sequence()` calls `SwitchToUntrustedUser(seqowner, ...)` (sequencesync.c:387) *before* the ACL check; `SwitchToUntrustedUser` raises ERROR on membership failure (usercontext.c:40-45). Unlike the `pg_class_aclcheck` failure two lines later — classified `COPYSEQ_SUBSCRIBER_INSUFFICIENT_PERM` (:396) and reported per-sequence by name via `report_sequence_errors()` (:201-224) while the batch continues — this ERROR is uncaught inside the batch loop (transaction spans :462-:647), rolls back batch-mates' READY updates, kills the worker before later batches, and repeats identically on every relaunch. No `ErrorContextCallback` exists in sequencesync.c, so the log never identifies the culprit. Aggravating: in default mode the graceful path checks the *owner's* privileges on their own sequence (essentially always true), so the warning framework is nearly dead code while the realistic failure takes the worker-killing path; 036_sequences.pl never tests this scenario.
**Trigger.** Subscription owned by non-superuser `bob`; any subscribed sequence owned by `alice` where `bob` lacks SET ROLE membership.
**Repro sketch.** Subscriber: sequences `s_ok` (owner bob) and `s_alice` (owner alice, bob not a member); publisher publishes both `FOR ALL SEQUENCES`; bob creates the subscription. Both sequences stay 'i' forever; log repeats the role error with no sequence name.
**Fix direction.** Classify the SET ROLE failure per-sequence like the other permission failures (new COPYSEQ_* code + warning naming the sequence) instead of letting it kill the worker, and add error context identifying the sequence being synced.
---
### 4. Gathering scan holds a lock on every INIT relation in one unbounded transaction (Medium)
**Defect.** The worker's initial catalog scan opens **every** `srsubstate='i'` relation — tables awaiting tablesync included — with `try_table_open(..., RowExclusiveLock)` *before* checking relkind, in a single transaction, retaining all locks until commit. Consequences: (a) one conflicting lock on any INIT relation stalls all sequence sync indefinitely while the worker retains everything already scanned; (b) with thousands of INIT sequences the transaction exhausts the shared lock table, producing a permanent `out of shared memory` / `increase max_locks_per_transaction` retry loop every 5 s with zero progress, transiently draining the lock pool for unrelated sessions each attempt.
**Mechanism.** `LogicalRepSyncSequences()`: one transaction (sequencesync.c:691-752); `try_table_open` at :718 precedes the relkind check at :725; both close paths use `NoLock` (:727, :745). `try_relation_open` blocks on the lock (relation.c:96-97 — "try" covers only nonexistence). An ERROR here precedes any READY update, so the INIT set never shrinks; relaunch every `wal_retrieve_retry_interval` (syncutils.c:127-147). The locks are demonstrably unnecessary — `FetchRelationStates` distinguishes sequences lock-free via `get_rel_relkind()` (syncutils.c:243) — and `copy_sequences()` already caps itself at 100 locks/transaction. Verifier corrections to the original claim: fresh CREATE SUBSCRIPTION cannot reach the exhaustion (the foreground command's own per-relation locking fails first); the reachable route is `ALTER SUBSCRIPTION ... REFRESH SEQUENCES` (flips all sequences to INIT with zero relation locks, subscriptioncmds.c:1360-1406) combined with a lock-pool squeeze, e.g. a subscriber later restarted with lower `max_connections` — and `max_locks_per_transaction` is PGC_POSTMASTER, so recovery requires a restart or dropping/disabling the subscription.
**Trigger.** Blocking: an open `BEGIN; ALTER SEQUENCE s1 RESTART ...;` (ShareRowExclusiveLock, sequence.c:450) when the scan runs. Exhaustion: ~5000 subscribed sequences, subscriber restarted with small `max_connections`, then `REFRESH SEQUENCES`.
**Repro sketch.** Blocking: 2 sequences subscribed and 'r'; session A `BEGIN; ALTER SEQUENCE s1 RESTART 100;` (leave open); `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` → worker blocks at `try_table_open(s1)`, s2 also never syncs. Exhaustion: 5000 sequences synced under `max_connections=100`; restart subscriber with `max_connections=20`; `REFRESH SEQUENCES` → perpetual out-of-shared-memory loop; `disable_on_error=true` disables the subscription on first failure.
**Fix direction.** Drop the relation locks from the gathering scan (filter by `get_rel_relkind()` as `FetchRelationStates` does), deferring per-sequence locking to the already-batched copy phase.
---
### 5. Sequencesync worker starved by tablesync workers; REFRESH SEQUENCES silently deferred (Medium)
**Defect.** While ≥ `max_sync_workers_per_subscription` tables are actively copying, the sequencesync worker is never launched — with no log message, no worker row, and no pacing state — so sequences stay 'i' for the full duration of a bulk table copy, even after an explicit `REFRESH SEQUENCES`. A DBA running the documented pre-failover `REFRESH SEQUENCES` during a large copy and then promoting gets never-synced sequences.
**Mechanism.** Both worker types share one budget (`logicalrep_sync_worker_count`, launcher.c:949). `ProcessSyncingRelations()` always runs tables before sequences (syncutils.c:173-175); the table loop launches immediately for any never-started table (`last_start_time==0`, tablesync.c:566-567), retaking every freed slot in the same pass; `launch_sync_worker()` then returns silently at the cap (syncutils.c:127). sequencesync.c:138 is the *only* launch site — the launcher never starts one. Verifier caveats: starvation ends when pending tables drop below the limit (not literally indefinite; failing tables' throttled relaunches leave windows), and logical-replication.sgml:1814-1819 does disclose the budget cap — but the strict tables-first priority is undocumented, and config.sgml:5695-5700's "one additional worker ... for sequence synchronization" implies provisioning +1 helps, which the greedy table loop defeats at any GUC value.
**Trigger.** Default `max_sync_workers_per_subscription=2`, ≥2 large tables mid-copy, then `REFRESH SEQUENCES`.
**Repro sketch.** Publisher: 3 tables × 10M rows + 1 advanced sequence, publications `FOR ALL TABLES` and `FOR ALL SEQUENCES`. Subscriber: subscribe to both; while ≥2 tablesync workers run, `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` and poll — sequence rows stay 'i' and `pg_stat_subscription` shows no `sequence synchronization` worker until the copy tail.
**Fix direction.** Reserve a slot for (or prioritize) sequence sync when INIT sequences exist — or at minimum log the deferral — and align config.sgml with the actual shared-budget behavior.
---
### 6. No publisher-version guard: pre-PG19 publisher causes a perpetual, cryptic error loop (Medium)
**Defect.** A subscription with registered sequences repointed at a pre-PG19 publisher (e.g. upgrade rollback via `ALTER SUBSCRIPTION ... CONNECTION`, or a DNS/VIP flip mid-sync) enters an endless 5-second error/relaunch loop: against PG18, `ERROR: invalid query response DETAIL: Expected 11 fields, got 10 fields.` (SQLSTATE 08P01, suggesting protocol corruption, not a version problem); against ≤PG17, `function pg_get_sequence_data(oid) does not exist`. Sequences stay 'i'; `disable_on_error=true` disables the whole subscription.
**Mechanism.** sequencesync.c has zero `walrcv_server_version()` checks (the only ≥190000 gates in the sync paths are tablesync.c:802, 1004); `copy_sequences()` unconditionally sends the batch query (:517-527) expecting REMOTE_SEQ_COL_COUNT=11 columns at :529. PG18's `pg_get_sequence_data()` exists but returns 2 output columns (page_lsn added by b93172c for PG19), so the query succeeds remotely with 10 fields and `libpqrcv_processTuples` errors (libpqwalreceiver.c:1083-1088). The broken state is reachable because sequence rows persist across `ALTER SUBSCRIPTION ... CONNECTION`, and `AlterSubscription_refresh_seq` *succeeds* against an old publisher (its origin check returns early for <190000, subscriptioncmds.c:3196-3198), reporting success and then unleashing the storm. `REFRESH PUBLICATION` incidentally recovers by removing the rows, but nothing in the error suggests that.
**Trigger.** PG19→PG19 subscription with synced sequences; repoint the connection at a PG18 host holding the slot; `REFRESH SEQUENCES`.
**Repro sketch.** Node A (PG19) and node B (PG18) with identical schema; subscribe to A (`FOR ALL SEQUENCES`), wait for 'r'; `ALTER SUBSCRIPTION sub1 CONNECTION 'host=B ...'; ALTER SUBSCRIPTION sub1 REFRESH SEQUENCES;` → 5-second loop of worker-start LOG + 08P01 ERROR.
**Fix direction.** Version-gate the sequencesync launch/copy path (and `REFRESH SEQUENCES`) with a clear "publisher (version N) does not support sequence synchronization" error or warning instead of retrying a query that can never succeed.
---
### 7. `has_sequence_privilege()` NULL on concurrent publisher DROP: Assert crash / wrong diagnosis (Medium)
**Defect.** A published sequence dropped on the publisher while the batch query runs yields a result row with NULL in the `has_sequence_privilege` column; assert-enabled subscribers hit `Assert(!isnull)` (sequencesync.c:297-298) — TRAP → SIGABRT → crash-restart of the sequencesync worker; production builds decode NULL as false and misreport the drop as `insufficient privileges on publisher sequence (...)` with a bogus GRANT SELECT hint. This re-introduces the concurrent-drop hazard 1ba3eee fixed for the data columns: d4a657b added the privilege column with an unconditional Assert.
**Mechanism.** The batch query (sequencesync.c:517-527) joins pg_class/pg_sequence under the query snapshot, but `has_sequence_privilege_id()` uses `get_rel_relkind()` on the current catalog snapshot and returns SQL NULL for a vanished relation (acl.c:2246-2248). The row is still emitted (old snapshot), and the LATERAL `pg_get_sequence_data()` — which locks the relation and absorbs the drop's invalidations first (lmgr.c:134-137; commit ordering xact.c:2475/2480) — makes the NULL deterministic, not a narrow race: an uncommitted DROP holds AccessExclusiveLock, the sync query blocks inside it, and COMMIT triggers the condition every time. With NULL data + false privilege, production takes the `COPYSEQ_PUBLISHER_INSUFFICIENT_PERM` path (:300-303) instead of the concurrent-drop `COPYSEQ_SKIPPED` path.
**Trigger.** Publisher-side `DROP SEQUENCE` committing while the batch SELECT executes.
**Repro sketch.** Two sequences subscribed and 'r'. Publisher session A: `BEGIN; DROP SEQUENCE s2;` (hold). Subscriber: `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` (batch query blocks on A's lock). Publisher: `COMMIT;` → assert build: TRAP at sequencesync.c:298; production: wrong privilege WARNING + sync-failure ERROR.
**Fix direction.** Treat a NULL privilege column as a concurrently-dropped sequence (fold into the existing `COPYSEQ_SKIPPED` handling) rather than asserting non-null.
---
### 8. Permission-denied sequences double-reported as "missing on publisher" (Medium)
**Defect.** When one batch contains both a genuinely missing sequence and a publisher-permission-denied sequence, the perm-denied sequence is listed in *both* warnings: `insufficient privileges on publisher sequence ("public.s2")` and `missing sequences on publisher ("public.s1", "public.s2")` — contradictory diagnoses for an object that exists (the worker even received a row for it). This is the residual gap in d4a657b, whose stated purpose was exactly to stop permission failures being reported as missing.
**Mechanism.** The perm-denied early return (sequencesync.c:300-303) fires before `found_on_pub = true` is set (:334), so perm-denied entries keep `found_on_pub=false` (palloc0 at :737). A genuinely missing sequence produces no row (inner joins, :517-527), making `batch_missing_count > 0` (:633-637), which enables the post-commit scan (:649-660) that appends *every* `!found_on_pub` entry to the missing list (:657) with no dedup in `report_sequence_errors()` (:226-251). d4a657b's own test (036_sequences.pl:228-262) recreated the dropped sequence before the permission test, so the mixed batch was never exercised.
**Trigger.** Same ≤100-sequence batch containing one sequence dropped on the publisher and one with SELECT revoked from the subscription's connection role, then `REFRESH SEQUENCES` (which never prunes publisher-dropped sequences).
**Repro sketch.** Both nodes: s1, s2; subscribe, wait for 'r'. Publisher: `REVOKE SELECT ON SEQUENCE s2 FROM repuser; DROP SEQUENCE s1;`. Subscriber: `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` → the contradictory warning pair repeats on every relaunch.
**Fix direction.** Set `found_on_pub` before the permission early-return (or exclude entries already classified perm-denied from the missing scan) so each sequence appears in exactly one list.
---
### 9. ALTER SUBSCRIPTION ... DISABLE never stops a running sequencesync worker (Low)
**Defect.** After `DISABLE`, the apply worker exits promptly but the sequencesync worker keeps starting new batch transactions, overwriting subscriber sequence values and flipping `pg_subscription_rel` rows to 'r' after the disable — for minutes normally, indefinitely if lock-blocked — contradicting alter_subscription.sgml's "stopping the logical replication worker" (:270-278). A `nextval()` run locally post-DISABLE can be clobbered back to an older publisher value.
**Mechanism.** The worker's entire lifetime (SequenceSyncWorkerMain → ... → `copy_sequences` loop, sequencesync.c:447-668) contains no `maybe_reread_subscription()` (call sites are only worker.c:740/2565/4183 and applyparallelworker.c:283) and never reads `MySubscriptionValid`/`enabled` after startup; DISABLE only writes `subenabled=false` and signals nothing (`logicalrep_worker_stop` is called only at subscriptioncmds.c:1257 and DropSubscription :2636); no exit path stops it (launcher.c:822-842 handles only parallel apply workers). Related to item 1's root cause (worker never revalidates anything), but the trigger is subscription-level state rather than per-relation rows, so reported separately; a batch-boundary recheck would address both.
**Trigger.** `DISABLE` while a sync of many sequences is in flight, or while the worker is blocked at `try_table_open` (:336/:718) behind an open `ALTER SEQUENCE`.
**Repro sketch.** 300 sequences; subscriber session A holds `BEGIN; ALTER SEQUENCE s150 RESTART 1;`; create subscription; once the worker blocks, `ALTER SUBSCRIPTION sub DISABLE;` (apply worker exits, sequencesync worker persists); `COMMIT;` in A → post-disable, sequences s101-s300 change values and READY count climbs to 300.
**Fix direction.** Recheck subscription validity/enabled at each batch boundary in `copy_sequences()` (and consider having DISABLE stop sync workers explicitly).
---
### 10. REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE: internal XX000 errors (Low)
**Defect.** Ordinary DDL concurrency — `REFRESH SEQUENCES` racing a subscriber-side `DROP SEQUENCE` — ends with one command failing on an internal elog: XX000 `subscription relation %u in subscription %u does not exist`, `tuple concurrently deleted`, or the DROP failing `tuple concurrently updated`; assert builds can additionally trip the relkind Assert in `GetSubscriptionRelations` (pg_subscription.c:666). Transient (retry succeeds) but corruption-looking.
**Mechanism.** `AlterSubscription_refresh_seq()` builds its list (subscriptioncmds.c:1387) holding no lock on the sequences and *not* taking the pg_subscription_rel AccessExclusiveLock its sibling removal paths take precisely to freeze rel states (:1333-1334); `DROP SEQUENCE` deletes the row via `heap_drop_with_catalog` → `RemoveSubscriptionRel` with only RowExclusiveLock on pg_subscription_rel (pg_subscription.c:505) and no subscription-object lock, so nothing serializes the commands until they collide on the catalog tuple (`UpdateSubscriptionRelState` syscache miss → elog at pg_subscription.c:413-415; heapam.c:4477/3176 for the wait-then-fail variants). The sync worker itself is protected against this race (it holds the relation open across its update, sequencesync.c:336/416); the refresh command is the unprotected outlier.
**Trigger.** Deterministic: `BEGIN; DROP SEQUENCE s1;` in one session, `REFRESH SEQUENCES` in another, then COMMIT (→ "tuple concurrently deleted"); reverse order inside `BEGIN` for "tuple concurrently updated" (REFRESH SEQUENCES is allowed in a transaction block).
**Repro sketch.** As trigger, on a subscription with s1/s2 already 'r'.
**Fix direction.** Take the same pg_subscription_rel AccessExclusiveLock (or per-sequence locks) in `AlterSubscription_refresh_seq`, and/or tolerate a concurrently-vanished row with a skip instead of elog.
---
### 11. Sequence-removal loop runs after irreversible tablesync slot drops (Low)
**Defect.** f0b3573c3 appended the sequence-removal loop (subscriptioncmds.c:1322-1344) *after* the `ReplicationSlotDropAtPubNode()` loop (:1295-1316), violating the explicit invariant at :1290-1294 that slot drops must come last because they cannot be rolled back. An error or cancel during sequence removal (each `RemoveSubscriptionRel` seqscans pg_subscription_rel with CFI, pg_subscription.c:497-567) aborts the transaction after publisher-side sync slots were dropped; a rolled-back-to-`FINISHEDCOPY` ('f') table then reuses its now-nonexistent slot without recreating it (tablesync.c:1342-1361) and error-loops with `replication slot "pg_%u_sync_%u_..." does not exist`.
**Skeptical caveats (verifier).** Re-running the failed ALTER self-heals (the slot drop is `missing_ok`); the wedge persists only if the publication change is abandoned. And with ≥2 removed tables a cancel between slot drops could already cause this pre-f0b3573c3 — the new loop violates the stated invariant, substantially widens the window, and newly exposes the single-removed-table case.
**Trigger.** `SET statement_timeout='2s'; ALTER SUBSCRIPTION sub SET PUBLICATION pub_new;` where the old set contained a mid-catchup ('f') table plus many sequences, timeout landing in the sequence loop.
**Repro sketch.** Large table held at 'f' via an AccessExclusiveLock on the subscriber; subscription also covers a sequence; ALTER SET PUBLICATION to a set containing neither, cancelled during sequence removal (gdb breakpoint at :1322 + `pg_cancel_backend` for determinism) → t1 wedged at 'f' with its slot gone.
**Fix direction.** Move the sequence-removal loop above the slot-drop loop, restoring the "slot drops last" invariant.
---
### 12. Batch query mixes MVCC definition columns with live `last_value`: validation bypass (Low)
**Defect.** A publisher `ALTER SEQUENCE` + `nextval()` committing while the batch query runs can produce an internally inconsistent row (old seqmin/seqmax/seqcycle, new last_value) that passes definition validation, yielding either a confusing `setval: value N is out of bounds for sequence` ERROR (22003) from a worker that never ran setval (subscription disabled under `disable_on_error`), or — CYCLE added upstream — a *silently* synced wrapped value marked READY despite a CYCLE/NO CYCLE mismatch the documented validation promises to catch.
**Mechanism.** The query (sequencesync.c:517-527) reads pg_sequence under the statement snapshot but `pg_get_sequence_data()` is volatile and reads the live buffer tuple (sequence.c:1831-1836); `ALTER SEQUENCE` takes only ShareRowExclusiveLock (sequence.c:450), not conflicting with the LATERAL's transient AccessShareLock. `get_and_validate_seq_info()` compares only the stale definition columns (:352-358); `SetSequence()`'s local bounds check (sequence.c:989-994) then errors, or the wrapped value fits and is marked READY (:416) with no warning. Same query-consistency family as item 7 (1ba3eee fixed the drop case; the ALTER case remains). Window is one publisher-side query execution — narrow; deterministic only with a paused walsender. The error variant self-corrects into the documented mismatch report on the next relaunch.
**Trigger.** `REFRESH SEQUENCES` concurrent with publisher `ALTER SEQUENCE s MAXVALUE 200; SELECT nextval('s')...` (error variant) or `ALTER SEQUENCE s CYCLE; SELECT nextval('s')` at max (silent variant).
**Repro sketch.** Both nodes `CREATE SEQUENCE s MAXVALUE 100`; subscribe; pause the publisher walsender in `pg_get_sequence_data` (gdb), apply the ALTER+nextval, resume.
**Fix direction.** Return the definition columns from `pg_get_sequence_data()` itself (one consistent read of the live tuple), or re-validate the received `last_value` against the local definition and classify violations as a retryable mismatch rather than letting setval's error surface.
---
### 13. REFRESH SEQUENCES warning misattributes a `copy_data` request the command cannot express (Low)
**Defect.** Plain `ALTER SUBSCRIPTION sub REFRESH SEQUENCES` on an `origin=none` subscription in a bidirectional setup emits `WARNING: subscription "sub" requested copy_data with origin = NONE but might copy data that had a different origin` — but REFRESH SEQUENCES accepts no `WITH` options at all (gram.y:11611-11619; no options parsed at subscriptioncmds.c:1614-1654), so the user "requested" nothing; they will hunt for a nonexistent knob.
**Mechanism.** `AlterSubscription_refresh_seq()` calls `check_publications_origin_sequences(..., true /* copydata hardcoded */, ...)` (subscriptioncmds.c:1383-1384), which reuses the CREATE SUBSCRIPTION/REFRESH PUBLICATION message at :3270-3277 — accurate at those call sites (which pass the user's actual copy_data), inaccurate here. The warning's substance (sequence data will be copied and may have a different origin) and hint remain correct; only the attribution is wrong.
**Trigger/repro sketch.** Two-node bidirectional `origin=none` sequence subscriptions; run `REFRESH SEQUENCES` on either side.
**Fix direction.** Use a REFRESH SEQUENCES-specific message ("... will copy sequence data that might have a different origin") instead of the "requested copy_data" wording.
---
### 14. psql tab-completion regression after `REFRESH PUBLICATION` (Low)
**Defect.** In PG18, `ALTER SUBSCRIPTION sub REFRESH PUBLICATION <TAB>` completed `WITH (`; in master it offers the subscriber's *local* publication names (syntactically invalid if accepted — the command takes no name, gram.y:11601) or nothing when no local publication exists.
**Mechanism.** f0b3573c3 deleted the rule `Matches("ALTER","SUBSCRIPTION",MatchAny,MatchAnyN,"REFRESH","PUBLICATION") → COMPLETE_WITH("WITH (")` (REL_18_0 tab-complete.in.c:2306), replacing it with a REFRESH-terminal rule (master :2355-2356) and never re-adding the follow-up; no remaining pattern matches, so control falls to the `words_after_create` fallback, where prev_wd "PUBLICATION" hits `Query_for_list_of_publications` (:1335, :1212-1215). The surviving `REFRESH PUBLICATION WITH (` → copy_data rule (:2358) shows the path is still intended.
**Trigger/repro sketch.** Subscriber with a local publication (bidirectional setup); type the command and press Tab.
**Fix direction.** Restore the deleted `..."REFRESH","PUBLICATION"` → `COMPLETE_WITH("WITH (")` rule.
---
### 15. pg_stat_subscription row for the sequencesync worker: fabricated times, undocumented NULLs (Low)
**Merged finding.** Two audit findings merged: both stem from adding the new worker type to `pg_stat_subscription` (55cefadde touched only the `worker_type` row in monitoring.sgml) without adjusting either the column semantics in code or the per-column NULL documentation.
**Defect.** For its entire lifetime, a sequencesync worker's row shows `last_msg_send_time`/`last_msg_receipt_time`/`latest_end_time` frozen at the worker's *start* time — masquerading as WAL-sender message times for a worker type that never receives WAL-sender messages — while `received_lsn`/`latest_end_lsn`/`relid`/`leader_pid` are always NULL. monitoring.sgml (:2325-2327, :2336-2337, :2346-2347, :2376-2377) enumerates NULL cases that omit this worker type entirely (e.g. relid "NULL for the leader apply worker and parallel apply workers"; time/LSN columns "NULL for parallel apply workers"). A DBA watching a long sync sees frozen "message" timestamps and an inconsistent row (`latest_end_time` set, `latest_end_lsn` NULL) suggesting a hung connection.
**Mechanism.** `SetupApplyOrSyncWorker()` initializes the three timestamps to `GetCurrentTimestamp()` (worker.c:5979-5981); the only updater, `UpdateWorkerStats()` (worker.c:3987), is called solely from `LogicalRepApplyLoop`, which this worker never enters; `pg_stat_get_subscription()` NULLs timestamps only when exactly 0 (launcher.c:1671-1683), a convention only parallel apply workers satisfy (applyparallelworker.c:958-959), and emits `relid` only for tablesync workers (launcher.c:1656-1659; sequencesync launches with InvalidOid relid, sequencesync.c:138).
**Trigger/repro sketch.** Poll `SELECT worker_type, relid, leader_pid, received_lsn, latest_end_lsn, last_msg_send_time FROM pg_stat_subscription WHERE worker_type='sequence synchronization'` during a sync of a few thousand sequences.
**Fix direction.** Zero the time fields for sequencesync workers so they render NULL (matching the parallel-apply convention), and extend monitoring.sgml's per-column NULL enumerations to cover the new worker type.
---
### 16. catalogs.sgml `srsublsn` description wrong for sequence rows (Low)
**Defect.** catalogs.sgml:8893-8896 still describes `srsublsn` solely as "Remote LSN of the state change ... when in s or r states, otherwise null". For a sequence row it actually holds the publisher sequence's *page LSN* from `pg_get_sequence_data()` — possibly far older than the sync, and precisely the value logical-replication.sgml:1844-1853 tells DBAs to compare in the out-of-sync procedure — and `copy_data=false` creates 'r'-state sequence rows with `srsublsn` NULL, contradicting "otherwise null".
**Mechanism.** `copy_sequence()` writes `UpdateSubscriptionRelState(..., SUBREL_STATE_READY, seqinfo->page_lsn, ...)` (sequencesync.c:416-417), page_lsn being `PageGetLSN()` of the publisher's sequence page (sequence.c:1836); copy_data=false inserts sequences directly READY with InvalidXLogRecPtr → SQL NULL (subscriptioncmds.c:985, :1203-1205; pg_subscription.c:353-356). Caveat: the ('r', NULL) shape has existed for tables since PG10; the feature-specific gap is the undocumented page-LSN meaning, which the sequences docs actively rely on.
**Trigger/repro sketch.** `CREATE SUBSCRIPTION ... WITH (copy_data=false)` → ('r', NULL) rows; after `REFRESH SEQUENCES`, `srsublsn` equals the publisher's `pg_get_sequence_data(...).page_lsn`.
**Fix direction.** Amend the `srsublsn` entry to state its sequence-row meaning (publisher page LSN used for out-of-sync comparison) and the copy_data=false NULL case.
---
### 17. Docs omit the PG19+ publisher requirement for sequence sync; REFRESH SEQUENCES silently no-ops (Low)
**Defect.** The failover-preparation restriction (logical-replication.sgml:2359-2372) recommends `ALTER SUBSCRIPTION ... REFRESH SEQUENCES` with no stated publisher-version precondition, and no SGML file anywhere mentions one; against a pre-PG19 publisher, sequences are silently never registered (`fetch_relation_list` gates the sequence UNION on `server_version >= 190000`, subscriptioncmds.c:3467) and `REFRESH SEQUENCES` succeeds as a fully silent no-op (origin check returns early, :3196-3198; empty relation list, empty loop). A DBA on the standard staged-upgrade topology (PG19 subscriber ← PG18 publisher) follows remedy #1, sees success, and discovers the gap as duplicate keys after failover — when remedies #2/#3 (manual update) would have worked.
**Skeptical caveat.** Severity capped low: `FOR ALL SEQUENCES` cannot be created on a pre-PG19 publisher, so a from-scratch setup fails loudly at step one; the exposed audience is existing cross-version subscriptions. Related to item 6 (same version dependency) but distinct: item 6 is the error loop when sequence rows *do* exist; this is silent success when they don't. In-tree precedent documents comparable cross-version caveats (binary pre-16, row filters pre-15) and `retain_dead_tuples` even errors on old publishers (:3304-3307).
**Trigger/repro sketch.** PG18 publisher `FOR ALL TABLES` with a serial column advancing to ~1000; PG19 subscriber follows the restrictions text: `REFRESH SEQUENCES` succeeds, `last_value` stays 1; post-failover insert → duplicate key.
**Fix direction.** State the PG19+ publisher requirement in the sequences documentation (and consider a NOTICE/WARNING from `REFRESH SEQUENCES` when the subscription has no subscribed sequences or the publisher predates support).
---
### 18. Restrictions bullet still claims only tables can be replicated (Low)
**Defect.** logical-replication.sgml:2399-2405 (unchanged since 17b9e7f) states "Replication is only supported by tables ... Attempts to replicate other types of relations, such as views, materialized views, or foreign tables, will result in an error" — contradicted three sections earlier by "Replicating Sequences" (:1772) and by the adjacent sequence-restrictions bullet (:2357-2372) added by 55cefadde.
**Mechanism.** Sequences are publishable (`is_publishable_class` accepts RELKIND_SEQUENCE, pg_publication.c:163-172) and valid subscription targets (`CheckSubscriptionRelkind`, execReplication.c:1140-1166); `FOR ALL SEQUENCES` grammar exists (gram.y:11422); 036_sequences.pl demonstrates successful sequence sync with no error. The only defense — reading "Replication" as strictly incremental streaming — fails against the bullet's categorical second sentence and the docs' own "Replicating Sequences" terminology.
**Trigger/repro sketch.** Read the Restrictions list, then observe `CREATE PUBLICATION p FOR ALL SEQUENCES` + subscription syncing values without error.
**Fix direction.** Reword the bullet to say tables and sequences are supported (sequences synchronized on request rather than streamed), keeping the error claim accurate for views/matviews/foreign tables.
---
## Appendix A: findings dropped unverified (verifier cap of 23)
These 5 ranked below the cap and were **not** adversarially verified; treat as leads only.
- ProcessSequencesForSync's only_running=true check allows two concurrent sequencesync workers for one subscription, causing "tuple concurrently updated" errors and disable_on_error trips
- Unlogged subscriber sequence reaches durable READY state while its synced value is lost on crash, permanently stale and reported in-sync
- create_subscription.sgml claims the origin parameter "has no effect for sequences", yet origin=none triggers sequence-specific origin warnings and sequence values of foreign origin are copied regardless, which is documented nowhere
- Docs recommend ALTER SUBSCRIPTION ... REFRESH SEQUENCES to update sequences at failover, but the command cannot run once the publisher is down and the duplicate-value consequence of lagging sequences is never stated
- Connect-failure message uses internal jargon "sequencesync worker", inconsistent with the same worker's other messages and the tablesync counterpart
## Appendix B: findings refuted by adversarial verification
- **REFRESH SEQUENCES is allowed inside a transaction block and can deadlock with the sequencesync worker when combined with ALTER SEQUENCE**
- Refutation: The individual lock sites are all real (verified in master: no PreventInTransactionBlock in the ALTER_SUBSCRIPTION_REFRESH_SEQUENCES case at src/backend/commands/subscriptioncmds.c:2286-2297; AccessExclusiveLock on the subscription object at subscriptioncmds.c:1714 for ALL ALTER SUBSCRIPTION forms; AccessShareLock on the subscription object in UpdateSubscriptionRelState at src/backend/catalog/pg_subscription.c:405 held to batch commit; try_table_open(RowExclusiveLock) at src/backend/replication/logical/sequencesync.c:336; ShareRowExclusiveLock for ALTER SEQUENCE at src/backend/commands/sequence.c:449-453). But the finding fails on four counts. (1) Its trigger is unreachable: copy_sequence -> UpdateSubscriptionRelState is called ONLY on COPYSEQ_SUCCESS (sequencesync.c:554-556, 416), and per-batch successes are committed (line 647) before report_sequence_errors raises the mismatch ERROR (line 671), so in the claimed 'worker retries every 5s due to a definition mismatch' steady state the retry batch contains only failing INIT sequences, which never acquire the subscription-object lock -- no cycle can form in the documented mismatch-repair workflow; a worker parked on the user's ALTERed sequence holds nothing the user's REFRESH SEQUENCES needs. (2) The causal premise is wrong: the other refresh forms forbid transaction blocks because AlterSubscription_refresh irreversibly drops table-synchronization slots on the publisher (per alter_subscription.sgml), not to avoid deadlocks; AlterSubscription_refresh_seq (subscriptioncmds.c:1360-1406) performs only read-only publisher queries and rollbackable local catalog updates, and the docs' explicit list of forms that 'cannot be executed inside a transaction block' intentionally omits REFRESH SEQUENCES -- code and documentation agree, so this is designed behavior. (3) Adding PreventInTransactionBlock to REFRESH SEQUENCES would not eliminate the deadlock: line 1714 is reached by every ALTER SUBSCRIPTION form, and ENABLE/DISABLE/SET/SKIP are deliberately allowed in transaction blocks (ENABLE docs even say the worker starts 'at the end of the transaction'), so BEGIN; ALTER SEQUENCE s; ALTER SUBSCRIPTION sub DISABLE; forms the identical cycle, as does BEGIN; ALTER SEQUENCE s1; ALTER SEQUENCE s2; against a worker retaining RowExclusiveLock on s2 from earlier in the batch (table_close NoLock, line 625). (4) The deadlock class is pre-existing and accepted: tablesync has held the target-table RowExclusiveLock across COPY (tablesync.c:1401) and then taken the subscription-object AccessShareLock via UpdateSubscriptionRelState (tablesync.c:1503) in one transaction since commit cb9079c (2017), giving the same detector-resolved cycle against transaction-block ALTER SUBSCRIPTION forms; and the comment at sequencesync.c:485-509 shows the developers explicitly analyzed worker-vs-ALTER SEQUENCE deadlocks and guarded only against the undetectable cross-node variant, accepting local detectable ones. The only constructible outcome (requires a multi-sequence batch with at least one success before the contended sequence, i.e. mid initial-sync or mid full re-sync, plus a deliberately open transaction mixing sequence DDL with ALTER SUBSCRIPTION) is a transient 'ERROR: deadlock detected' that the deadlock detector resolves and the respawned worker recovers from -- standard, accepted PostgreSQL DDL concurrency behavior, not a user-visible defect of the sequence-replication feature.
- **Sequencesync batches accumulate RowExclusiveLock on up to 100 sequences until commit, creating deadlocks with ordinary multi-sequence DDL or ALTER SUBSCRIPTION in the same transaction**
- Refutation: The mechanism is factually accurate but describes ordinary, deliberate, detected-and-resolved PostgreSQL locking behavior, not a defect. Verified: sequencesync batches take try_table_open(seq, RowExclusiveLock) per row (sequencesync.c:336), retain locks via table_close(NoLock) (line 625) until CommitTransactionCommand (line 647) for up to 100 sequences (line 441); ALTER SEQUENCE takes conflicting ShareRowExclusiveLock (sequence.c:449-450); UpdateSubscriptionRelState takes AccessShareLock on the subscription held to commit (pg_subscription.c:405); ALTER SUBSCRIPTION takes AccessExclusiveLock (subscriptioncmds.c:1714). So the claimed lock cycles can occur — but both parties are ordinary backends on heavyweight locks, so the local deadlock detector resolves the cycle after deadlock_timeout with a clean, retryable "deadlock detected" error. That disqualifies it as a defect on four grounds: (1) The trigger requires the user's own transaction to acquire conflicting locks on multiple objects in inconsistent order — the canonical application-responsibility scenario per the docs (mvcc.sgml "Deadlocks"); two plain user sessions doing the same two ALTER SEQUENCEs in opposite order deadlock identically with no replication involved. (2) The finding's novelty claim ("unlike tablesync ... this wait-while-holding pattern is new") is wrong: the apply worker has accumulated RowExclusiveLocks on many relations per applied transaction, in remote-determined order the local user cannot predict, since PG10 (worker.c:2673/2730, 2834/2918, 3057/3117 open with RowExclusiveLock, close with NoLock), and applyparallelworker.c:62-67 explicitly states such deadlocks "can happen even without parallel mode when there are concurrent operations on the subscriber" — the project's design bar is detectability, not prevention. (3) sequencesync.c deliberately implements that doctrine: the header comment (lines 42-44) documents batch-duration lock retention, and the comment at 485-509 orders the remote fetch (walrcv_exec, line 529) before any local sequence locks so the worker never waits on the network while holding local locks, eliminating undetectable cross-node deadlocks and knowingly accepting locally detectable ones. (4) The outcome is transient, atomic, and self-healing: a victim worker's batch aborts atomically (SetSequence and READY-state updates roll back together, sequences stay INIT, no inconsistent catalog state), start_sequence_sync reports stats and rethrows, and the apply worker relaunches a sequencesync worker after wal_retrieve_retry_interval (syncutils.c:117-148, 175), which then succeeds; disable_on_error disabling the subscription on any error, including deadlocks, is that option's documented contract and applies equally to apply-worker deadlocks today. There is also no actionable fix consistent with the design: the lock protecting the WAL-logged SetSequence update cannot be released before commit, batching is a documented performance choice, and no ordering discipline can be consistent with arbitrary user DDL order. Not covered by any already-fixed commit (git log on sequencesync.c shows no locking changes). A report of this would be answered "working as intended; retry on deadlock", so confirming it would waste committer time.
- **A failed or interrupted sync batch leaves subscriber sequence values silently overwritten (non-transactionally) while pg_subscription_rel still says INIT**
- Refutation: Every code citation in the finding is accurate — I traced them all in master: copy_sequences() batches up to 100 sequences per transaction (src/backend/replication/logical/sequencesync.c:441, StartTransactionCommand :462, CommitTransactionCommand :647), accumulates RowExclusiveLocks to commit (:336, table_close(NoLock) :625), copy_sequence() calls SetSequence at :407 followed by transactional UpdateSubscriptionRelState(SUBREL_STATE_READY) at :416-417, and SetSequence (src/backend/commands/sequence.c:946-1043) updates the sequence page in place in a critical section with XLOG_SEQ_LOG (:1011-1038) — no relfilenode swap, so the change is immediately visible (frozen-xmin tuple) and survives transaction abort. A pg_terminate_backend hitting CHECK_FOR_INTERRUPTS (:544) mid-batch does leave earlier sequences' values applied while their catalog rows revert to 'i' with NULL srsublsn (REFRESH SEQUENCES writes InvalidXLogRecPtr, subscriptioncmds.c:1392-1393). Nothing in git history changed this. However, the behavior is not a defect: (1) SetSequence IS setval()'s implementation, and setval's non-transactionality is explicitly documented (doc/src/sgml/func/func-sequence.sgml:199-201: changes "are immediately visible to other transactions, and are not undone if the calling transaction rolls back") — the feature inherits documented sequence semantics; (2) "failed sync => no persisted mutation" was never this feature's contract even absent aborts: report_sequence_errors() raises the overall failure ERROR only AFTER all batches have committed (sequencesync.c:670-673), so a sync failing on one mismatched/missing/no-permission sequence has already permanently committed publisher values AND READY state for every other sequence — the finding's within-batch-abort case (values applied, state 'i') is a strictly milder variant of designed behavior (values applied, state 'r', command reports failure); (3) the leftover 'i' state is the conservative, correct-by-contract state: the apply worker respawns the sequencesync worker (ProcessSequencesForSync, sequencesync.c:96-140; syncutils.c wal_retrieve_retry_interval throttling) and re-copy via SetSequence is idempotent, a retry loop the docs explicitly describe (logical-replication.sgml:1824-1829); the ordering (non-rollbackable value first, transactional catalog second) is the only crash-safe ordering for a non-MVCC object — the reverse would produce READY-without-copy, a real data-loss bug; (4) the claimed harm ("regressing a locally-advanced sequence" to the publisher value) is exactly what the user commanded — a successful REFRESH SEQUENCES installs the same value — and no wrong final value can persist while the subscription exists; (5) no documentation promises atomicity or rollback of sequence values on sync failure, so there is no documented-vs-actual divergence, and pg_subscription_rel showing 'i' is accurate per its meaning ("needs synchronization"; resync is pending and harmless). The tablesync comparison establishes no contract: heap data is MVCC-rollbackable, sequence data fundamentally is not. A report of this to pgsql-hackers would be closed as works-as-intended/inherent setval semantics. The mechanism is real but it is intended, documented-primitive, self-healing behavior — not a user-visible defect.
## Appendix C: provenance
Produced by a 45-agent workflow (20 finder lenses over master @ a8c2547 -> dedup/rank of 63 raw findings -> 23 adversarial verifiers -> synthesis). All verification is static code tracing; no repro was executed. Per-agent transcripts: ~/.claude/projects/-home-nm-src-pg-postgresql/3a1250f2-41e8-46fd-afe1-08ff8e4dace0/subagents/workflows/wf_e3d25a7b-189/journal.jsonl
Attachments:
[text/plain] test-refresh-race-sequencesync-v0.patch (10.4K, ../../20260710045217.f0.noahmisch@microsoft.com/2-test-refresh-race-sequencesync-v0.patch)
download | inline diff:
commit fe331b9 (HEAD -> seq-refresh-sequences-race)
Author: Noah Misch <noah@leadboat.com>
AuthorDate: Thu Jul 9 19:39:45 2026 +0000
Commit: Noah Misch <noah@leadboat.com>
CommitDate: Thu Jul 9 19:42:17 2026 +0000
Add test revealing REFRESH SEQUENCES race with sequencesync worker.
ALTER SUBSCRIPTION ... REFRESH SEQUENCES resets all sequence entries in
pg_subscription_rel to INIT (AlterSubscription_refresh_seq) without
stopping or fencing an in-flight sequencesync worker. The worker
fetches publisher values before taking any local lock, and
copy_sequence() later applies them and marks each sequence READY
without rechecking the entry's state. A batch whose values were
fetched before the refresh therefore overwrites the INIT reset
afterwards: the sequences report READY while holding pre-refresh
publisher values, with no error, no warning, and no pending
resynchronization. A user following the documented pre-failover
procedure (REFRESH SEQUENCES, wait for READY) can then fail over to a
subscriber whose sequences are behind the publisher, yielding duplicate
sequence values.
The test constructs the schedule deterministically: it blocks the
worker's batch query on the publisher via AccessExclusiveLock on a
sequence, blocks local application via ShareRowExclusiveLock on the
sequences on the subscriber, and runs REFRESH SEQUENCES inside the
fetch-to-apply window.
The final two assertions check that sequences reporting READY reflect
every value published before REFRESH SEQUENCES was issued. They
currently fail, demonstrating the defect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKt6fYFvAnMUMyavJmmueE
---
src/test/subscription/meson.build | 1 +
.../subscription/t/039_sequences_refresh_race.pl | 184 +++++++++++++++++++++
2 files changed, 185 insertions(+)
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index e71e95c..067fca2 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -48,6 +48,7 @@ tests += {
't/036_sequences.pl',
't/037_except.pl',
't/038_walsnd_shutdown_timeout.pl',
+ 't/039_sequences_refresh_race.pl',
't/100_bugs.pl',
],
},
diff --git a/src/test/subscription/t/039_sequences_refresh_race.pl b/src/test/subscription/t/039_sequences_refresh_race.pl
new file mode 100644
index 0000000..5173231
--- /dev/null
+++ b/src/test/subscription/t/039_sequences_refresh_race.pl
@@ -0,0 +1,184 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that ALTER SUBSCRIPTION ... REFRESH SEQUENCES is not silently
+# overwritten by an in-flight sequencesync worker.
+#
+# The sequencesync worker fetches sequence values from the publisher and
+# only afterwards applies them and marks the sequences READY, without
+# rechecking pg_subscription_rel state. REFRESH SEQUENCES resets all
+# sequences to INIT without stopping or fencing a running worker. Hence a
+# batch whose values were fetched before the refresh can mark sequences
+# READY afterwards, leaving them holding pre-refresh values with no error,
+# no warning, and no pending resynchronization. A user following the
+# documented pre-failover procedure (run REFRESH SEQUENCES, wait for READY)
+# can then fail over to a subscriber whose sequences are behind the
+# publisher, producing duplicate sequence values.
+#
+# This test constructs that schedule deterministically:
+#
+# 1. Hold AccessExclusiveLock on a sequence on the publisher, so the
+# worker's remote batch query blocks inside pg_get_sequence_data()
+# after the worker has committed its scan of pg_subscription_rel.
+# 2. While the fetch is blocked, take ShareRowExclusiveLock on all
+# sequences on the subscriber, so that after the fetch completes the
+# worker blocks at its first try_table_open(), i.e. after fetching
+# values but before updating any pg_subscription_rel row.
+# 3. Release the publisher lock; the worker's fetch completes with the old
+# values and the worker blocks on the subscriber locks.
+# 4. Advance the sequences on the publisher, then run
+# ALTER SUBSCRIPTION ... REFRESH SEQUENCES; it resets both sequences to
+# INIT and commits, not blocked by the in-flight worker.
+# 5. Release the subscriber locks. The worker applies the stale values
+# and flips both sequences INIT -> READY.
+#
+# On an unpatched server the final assertions fail: all sequences report
+# READY but hold the values fetched before the refresh.
+#
+# Note for whoever fixes the race: this choreography assumes REFRESH
+# SEQUENCES continues to complete without waiting for an in-flight
+# sequencesync worker (as it does today). If the fix instead makes the
+# command block until a running worker exits, step 4 will wait behind the
+# sequence locks taken in step 2 and the test will hang there rather than
+# fail; the schedule would need reshaping for a fix of that shape.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init;
+$node_subscriber->start;
+
+# Identical sequence definitions on both nodes.
+my $ddl = qq(
+ CREATE SEQUENCE regress_seq1;
+ CREATE SEQUENCE regress_seq2;
+);
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+# Consume some sequence values on the publisher.
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT nextval('regress_seq1') FROM generate_series(1, 100);
+ SELECT nextval('regress_seq2') FROM generate_series(1, 100);
+));
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION regress_seq_pub FOR ALL SEQUENCES");
+
+# Create the subscription disabled, so the locks below can be positioned
+# before the sequencesync worker starts. The replication slot is created
+# here, which must precede the publisher-side open transaction below
+# because slot creation waits for concurrent transactions to finish.
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub WITH (enabled = false)"
+);
+
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubstate = 'i'");
+is($result, '2', 'both sequences start in INIT state');
+
+# Block the sequencesync worker's batch query on the publisher: an
+# uncommitted DROP SEQUENCE holds AccessExclusiveLock, on which the
+# pg_get_sequence_data() call in the batch query will wait.
+my $pub_session = $node_publisher->background_psql('postgres');
+$pub_session->query_safe(
+ qq(
+ BEGIN;
+ DROP SEQUENCE regress_seq1;
+));
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub ENABLE");
+
+# Wait until the worker's batch query is blocked on the publisher. At
+# this point the worker has committed the transaction that scanned
+# pg_subscription_rel and holds no locks on the subscriber.
+$node_publisher->poll_query_until(
+ 'postgres', qq(
+ SELECT EXISTS (
+ SELECT 1 FROM pg_locks
+ WHERE relation = 'regress_seq1'::regclass AND NOT granted);
+)) or die "timed out waiting for sequencesync worker to block on publisher";
+
+# Take locks conflicting with the worker's try_table_open() on both
+# sequences, so the worker will block after its fetch completes but before
+# it updates any pg_subscription_rel row, whatever order it processes the
+# sequences in. The ALTERs are rolled back later, so the definitions stay
+# identical to the publisher's.
+my $sub_session = $node_subscriber->background_psql('postgres');
+$sub_session->query_safe(
+ qq(
+ BEGIN;
+ ALTER SEQUENCE regress_seq1 MINVALUE 1;
+ ALTER SEQUENCE regress_seq2 MINVALUE 1;
+));
+
+# Release the publisher lock: the fetch completes with the current
+# publisher values, then the worker blocks on the subscriber locks.
+$pub_session->query_safe("ROLLBACK");
+$pub_session->quit;
+
+$node_subscriber->poll_query_until(
+ 'postgres', qq(
+ SELECT EXISTS (
+ SELECT 1 FROM pg_locks
+ WHERE relation IN ('regress_seq1'::regclass, 'regress_seq2'::regclass)
+ AND NOT granted);
+)) or die "timed out waiting for sequencesync worker to block on subscriber";
+
+# The values held by the blocked worker now become stale.
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ SELECT nextval('regress_seq1') FROM generate_series(1, 100);
+ SELECT nextval('regress_seq2') FROM generate_series(1, 100);
+));
+
+# Request resynchronization of all sequences. This resets both sequences
+# to INIT and commits; it does not wait for the in-flight worker.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubstate = 'i'");
+is($result, '2', 'REFRESH SEQUENCES reset both sequences to INIT');
+
+# Release the subscriber locks, letting the worker proceed.
+$sub_session->query_safe("ROLLBACK");
+$sub_session->quit;
+
+# Wait until the subscription reports all sequences synchronized.
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_subscription_rel WHERE srsubstate <> 'r'")
+ or die "timed out waiting for sequences to reach READY state";
+
+# All sequences report READY, so they must not predate the last REFRESH
+# SEQUENCES command: every value published before that command was issued
+# must be reflected on the subscriber. On an unpatched server these fail,
+# with the subscriber sequences left at the values fetched before the
+# refresh.
+my $pub_seq1 = $node_publisher->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq1");
+my $sub_seq1 = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq1");
+is($sub_seq1, $pub_seq1,
+ 'READY regress_seq1 reflects publisher values from before REFRESH SEQUENCES'
+);
+
+my $pub_seq2 = $node_publisher->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq2");
+my $sub_seq2 = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq2");
+is($sub_seq2, $pub_seq2,
+ 'READY regress_seq2 reflects publisher values from before REFRESH SEQUENCES'
+);
+
+done_testing();
[text/plain] sequence-logrep-defects.md (52.6K, ../../20260710045217.f0.noahmisch@microsoft.com/3-sequence-logrep-defects.md)
download | inline:
# Logical replication of sequences: user-visible defects in master
**Scope:** defects introduced by commits f0b3573c3, 5509055d6, 55cefadde (and still present through follow-up fixes), verified against master @ a8c2547. All file:line references are to that tree.
## Executive summary
An adversarial audit of the PG19 sequence-replication feature identified 20 user-visible defects; two pairs share a single root cause and are merged below, leaving 18 items. Every item is **CONFIRMED** — the mechanism was traced end-to-end in source by an independent verifier — but none has been executed as a runtime repro, so the repro sketches should be run before relying on exact symptoms; there are no PLAUSIBLE-grade items in this report. The dominant pattern is missing coordination around the new shared sequencesync worker: no ALTER SUBSCRIPTION path stops or fences it, and it never rechecks subscription or relation state after startup, yielding (worst case) sequences silently marked READY with pre-refresh publisher values in the documented pre-failover `REFRESH SEQUENCES` workflow — i.e., duplicate sequence values after failover, the exact hazard the feature exists to prevent. A second pattern is single-object failures escalating to subscription-wide outages: because one worker serves all sequences and several failure paths bypass the per-sequence warning framework, one misconfigured sequence, a read-only default, a lock-table squeeze, or an older publisher produces an unbounded 5-second error/relaunch loop (or full auto-disable under `disable_on_error`). The remainder are internal XX000 errors reachable from ordinary DDL concurrency, misleading diagnostics, and monitoring/documentation never updated for the new worker type and relation kind.
## Index
| # | Finding | Severity | Confidence | Anchor |
|---|---------|----------|------------|--------|
| 1 | Refresh paths race the in-flight sequencesync worker: stale values marked READY / XX000 + auto-disable *(merged: 2 findings)* | High | CONFIRMED | src/backend/commands/subscriptioncmds.c:1392, :1336 |
| 2 | `default_transaction_read_only=on` permanently blocks sequence sync via setval() read-only check | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:407 |
| 3 | `run_as_owner=false`: un-SET-ROLE-able sequence owner kills the shared worker with an unattributed error | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:387 |
| 4 | Gathering scan holds RowExclusiveLock on every INIT relation in one unbounded transaction: blocking + lock-table exhaustion loop | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:718 |
| 5 | Sequencesync worker starved by tablesync workers; silent deferral of REFRESH SEQUENCES | Medium | CONFIRMED | src/backend/replication/logical/syncutils.c:127 |
| 6 | No publisher-version guard: pre-PG19 publisher yields perpetual cryptic protocol-violation loop | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:529 |
| 7 | `has_sequence_privilege()` NULL on concurrent publisher drop: Assert crash / misreported as permission failure | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:298 |
| 8 | Permission-denied sequences also listed as "missing on publisher" in mixed batches | Medium | CONFIRMED | src/backend/replication/logical/sequencesync.c:657 |
| 9 | ALTER SUBSCRIPTION ... DISABLE never stops a running sequencesync worker | Low | CONFIRMED | src/backend/replication/logical/sequencesync.c:447 |
| 10 | REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE: internal XX000 errors | Low | CONFIRMED | src/backend/commands/subscriptioncmds.c:1387 |
| 11 | Sequence-removal loop placed after irreversible tablesync slot drops, violating stated invariant | Low | CONFIRMED | src/backend/commands/subscriptioncmds.c:1322 |
| 12 | Batch query mixes MVCC definition columns with live `last_value`: validation bypass under concurrent ALTER SEQUENCE | Low | CONFIRMED | src/backend/replication/logical/sequencesync.c:517 |
| 13 | REFRESH SEQUENCES emits "requested copy_data" warning for an option the command does not accept | Low | CONFIRMED | src/backend/commands/subscriptioncmds.c:3272 |
| 14 | psql tab-completion regression after `REFRESH PUBLICATION` (offers publication names / nothing instead of `WITH (`) | Low | CONFIRMED | src/bin/psql/tab-complete.in.c:2356 |
| 15 | pg_stat_subscription row for sequencesync worker: fabricated message times, NULL columns undocumented *(merged: 2 findings)* | Low | CONFIRMED | src/backend/replication/logical/worker.c:5980; doc/src/sgml/monitoring.sgml:2336 |
| 16 | catalogs.sgml `srsublsn` description wrong for sequence rows | Low | CONFIRMED | doc/src/sgml/catalogs.sgml:8894 |
| 17 | Docs never state sequence sync requires a PG19+ publisher; REFRESH SEQUENCES silently no-ops | Low | CONFIRMED | doc/src/sgml/logical-replication.sgml:2359 |
| 18 | Restrictions bullet still claims only tables can be replicated | Low | CONFIRMED | doc/src/sgml/logical-replication.sgml:2401 |
---
### 1. Refresh paths race the in-flight sequencesync worker (High)
**Merged finding.** Two audit findings are merged here because they share one root cause: no ALTER SUBSCRIPTION refresh path stops or fences a running sequencesync worker (the table path explicitly stops tablesync workers, subscriptioncmds.c:1257, and its removal path documents that its `pg_subscription_rel` lock exists to freeze rel states, :1333-1334), and the worker never re-validates a row between deciding to sync it and writing it.
**Defect.** (A, high) `ALTER SUBSCRIPTION ... REFRESH SEQUENCES` can be silently overwritten by the worker's in-flight batch: up to 100 sequences end up `srsubstate='r'` holding publisher values fetched *before* the refresh, with no error, no warning, and no pending resync. (B, medium) The PUBLICATION refresh forms (`REFRESH PUBLICATION`, `SET/ADD/DROP PUBLICATION`) remove de-published sequence rows out from under the worker, which then raises internal XX000 `subscription relation %u in subscription %u does not exist` after having already applied a non-transactional value overwrite; with `disable_on_error=true` the entire subscription (tables included) is disabled.
**Mechanism.** (A) `AlterSubscription_refresh_seq()` resets every sequence row to `SUBREL_STATE_INIT` (subscriptioncmds.c:1392) with no worker interlock. The worker fetches publisher values with `walrcv_exec` before holding any relevant lock (sequencesync.c:529), then `copy_sequence()` unconditionally applies `SetSequence()` (:407) and `UpdateSubscriptionRelState(..., SUBREL_STATE_READY, ...)` (:416) without rechecking `srsubstate`. `UpdateSubscriptionRelState`'s `LockSharedObject` waits out the ALTER and absorbs its invalidations (pg_subscription.c:405; lmgr.c:1102), so the worker *cleanly* flips the freshly-reset rows to READY with stale values; `FetchRelationStates` keys resync off non-READY rows, so the loss is permanent and invisible. (B) The removal loop calls `RemoveSubscriptionRel()` (subscriptioncmds.c:1336) without `logicalrep_worker_stop`; the worker's subsequent `UpdateSubscriptionRelState` hits `elog(ERROR, ...)` at pg_subscription.c:413-415 — after `SetSequence` (non-transactional, like setval) already overwrote the just-de-published local sequence. PG_CATCH at sequencesync.c:803-819 then either error-loops or runs `DisableSubscriptionAndExit`.
**Trigger.** (A) Any `REFRESH SEQUENCES` committing while a sequencesync batch is between its publisher fetch and its first catalog update — probabilistic at every batch boundary under continuous publisher `nextval()` traffic; deterministic with a lock stall. (B) `DROP PUBLICATION pub_seq` (default `refresh=true`) or `REFRESH PUBLICATION` while initial sequence sync is in flight.
**Repro sketch.** Two nodes, 101 sequences, `FOR ALL SEQUENCES` publication, subscription created. Subscriber session A after the worker's initial scan: `BEGIN; ALTER SEQUENCE s101 RESTART 1;` (ShareRowExclusiveLock stalls the worker at `try_table_open`, sequencesync.c:336, *after* it fetched batch-2 values). Publisher: advance `nextval('s101')` repeatedly. Subscriber session B: `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` (succeeds, all rows → 'i'). Session A: `ROLLBACK;` → worker marks s101 'r' with the pre-refresh value; failover now yields duplicate sequence values. For (B): 300 sequences + `disable_on_error=true`, then `ALTER SUBSCRIPTION sub DROP PUBLICATION pub_seq;` mid-sync → XX000 in the log and the subscription disabled.
**Fix direction.** Stop (or fence) sequencesync workers in all refresh paths, as the tablesync path does, and/or make `copy_sequence()` re-verify under lock that the row still exists in the expected pre-sync state before writing SetSequence/READY, discarding stale fetched values.
---
### 2. `default_transaction_read_only=on` permanently blocks sequence sync (Medium)
**Defect.** On a subscriber with `default_transaction_read_only=on` — the read-only-subscriber deployment logical-replication.sgml itself describes — tables copy and apply normally, but sequences never sync: the worker fails every `wal_retrieve_retry_interval` with the misleading `cannot execute setval() in a read-only transaction` (25006) for a setval() the user never ran; rows stay 'i' forever, `seq_sync_error_count` climbs, and `disable_on_error=true` disables the whole subscription.
**Mechanism.** `copy_sequence()` applies values via `SetSequence()` (sequencesync.c:407), which runs `PreventCommandIfReadOnly("setval()")` for permanent sequences (sequence.c:976-977). Batch transactions are plain `StartTransactionCommand` (sequencesync.c:462), inheriting `XactReadOnly = DefaultXactReadOnly` (xact.c:2172); worker GUC setup overrides only session_replication_role/search_path/synchronous_commit (worker.c:5798-5810). Every other subscriber write path bypasses read-only checks: tablesync calls `BeginCopyFrom`/`CopyFrom` directly (tablesync.c:1209-1213; the only COPY check lives in `DoCopy`, copy.c:365) and apply uses `ExecSimpleRelation*` with no check — so sequences are uniquely broken.
**Trigger.** Set `default_transaction_read_only=on` in subscriber postgresql.conf (or per-database); subscribe to a `FOR ALL TABLES, ALL SEQUENCES` publication.
**Repro sketch.** Publisher: table t + sequence s, `nextval('s')`, publication for both. Subscriber (GUC on; use a session-local `SET default_transaction_read_only=off` for the DDL): create matching objects, `CREATE SUBSCRIPTION`. Observe t replicating while the log repeats the setval error and s stays 'i'. Undocumented workaround exists (`ALTER ROLE <subscription owner> SET default_transaction_read_only = off`), but nothing hints at it.
**Fix direction.** Have the sequencesync worker's transactions clear `XactReadOnly` (or exempt this internal caller from the setval() read-only check), matching the implicit exemption tablesync/apply already enjoy.
---
### 3. `run_as_owner=false`: one un-SET-ROLE-able sequence owner stalls all sequence sync, unattributed (Medium)
**Defect.** With the default `run_as_owner=false`, one sequence whose owner the subscription owner cannot `SET ROLE` to makes the shared worker die with `role "X" cannot SET ROLE to "Y"` (42501) — an error that never names the sequence — permanently preventing *all* remaining sequences (including fully-compliant ones) from leaving state 'i'; `disable_on_error=true` disables the whole subscription.
**Mechanism.** `copy_sequence()` calls `SwitchToUntrustedUser(seqowner, ...)` (sequencesync.c:387) *before* the ACL check; `SwitchToUntrustedUser` raises ERROR on membership failure (usercontext.c:40-45). Unlike the `pg_class_aclcheck` failure two lines later — classified `COPYSEQ_SUBSCRIBER_INSUFFICIENT_PERM` (:396) and reported per-sequence by name via `report_sequence_errors()` (:201-224) while the batch continues — this ERROR is uncaught inside the batch loop (transaction spans :462-:647), rolls back batch-mates' READY updates, kills the worker before later batches, and repeats identically on every relaunch. No `ErrorContextCallback` exists in sequencesync.c, so the log never identifies the culprit. Aggravating: in default mode the graceful path checks the *owner's* privileges on their own sequence (essentially always true), so the warning framework is nearly dead code while the realistic failure takes the worker-killing path; 036_sequences.pl never tests this scenario.
**Trigger.** Subscription owned by non-superuser `bob`; any subscribed sequence owned by `alice` where `bob` lacks SET ROLE membership.
**Repro sketch.** Subscriber: sequences `s_ok` (owner bob) and `s_alice` (owner alice, bob not a member); publisher publishes both `FOR ALL SEQUENCES`; bob creates the subscription. Both sequences stay 'i' forever; log repeats the role error with no sequence name.
**Fix direction.** Classify the SET ROLE failure per-sequence like the other permission failures (new COPYSEQ_* code + warning naming the sequence) instead of letting it kill the worker, and add error context identifying the sequence being synced.
---
### 4. Gathering scan holds a lock on every INIT relation in one unbounded transaction (Medium)
**Defect.** The worker's initial catalog scan opens **every** `srsubstate='i'` relation — tables awaiting tablesync included — with `try_table_open(..., RowExclusiveLock)` *before* checking relkind, in a single transaction, retaining all locks until commit. Consequences: (a) one conflicting lock on any INIT relation stalls all sequence sync indefinitely while the worker retains everything already scanned; (b) with thousands of INIT sequences the transaction exhausts the shared lock table, producing a permanent `out of shared memory` / `increase max_locks_per_transaction` retry loop every 5 s with zero progress, transiently draining the lock pool for unrelated sessions each attempt.
**Mechanism.** `LogicalRepSyncSequences()`: one transaction (sequencesync.c:691-752); `try_table_open` at :718 precedes the relkind check at :725; both close paths use `NoLock` (:727, :745). `try_relation_open` blocks on the lock (relation.c:96-97 — "try" covers only nonexistence). An ERROR here precedes any READY update, so the INIT set never shrinks; relaunch every `wal_retrieve_retry_interval` (syncutils.c:127-147). The locks are demonstrably unnecessary — `FetchRelationStates` distinguishes sequences lock-free via `get_rel_relkind()` (syncutils.c:243) — and `copy_sequences()` already caps itself at 100 locks/transaction. Verifier corrections to the original claim: fresh CREATE SUBSCRIPTION cannot reach the exhaustion (the foreground command's own per-relation locking fails first); the reachable route is `ALTER SUBSCRIPTION ... REFRESH SEQUENCES` (flips all sequences to INIT with zero relation locks, subscriptioncmds.c:1360-1406) combined with a lock-pool squeeze, e.g. a subscriber later restarted with lower `max_connections` — and `max_locks_per_transaction` is PGC_POSTMASTER, so recovery requires a restart or dropping/disabling the subscription.
**Trigger.** Blocking: an open `BEGIN; ALTER SEQUENCE s1 RESTART ...;` (ShareRowExclusiveLock, sequence.c:450) when the scan runs. Exhaustion: ~5000 subscribed sequences, subscriber restarted with small `max_connections`, then `REFRESH SEQUENCES`.
**Repro sketch.** Blocking: 2 sequences subscribed and 'r'; session A `BEGIN; ALTER SEQUENCE s1 RESTART 100;` (leave open); `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` → worker blocks at `try_table_open(s1)`, s2 also never syncs. Exhaustion: 5000 sequences synced under `max_connections=100`; restart subscriber with `max_connections=20`; `REFRESH SEQUENCES` → perpetual out-of-shared-memory loop; `disable_on_error=true` disables the subscription on first failure.
**Fix direction.** Drop the relation locks from the gathering scan (filter by `get_rel_relkind()` as `FetchRelationStates` does), deferring per-sequence locking to the already-batched copy phase.
---
### 5. Sequencesync worker starved by tablesync workers; REFRESH SEQUENCES silently deferred (Medium)
**Defect.** While ≥ `max_sync_workers_per_subscription` tables are actively copying, the sequencesync worker is never launched — with no log message, no worker row, and no pacing state — so sequences stay 'i' for the full duration of a bulk table copy, even after an explicit `REFRESH SEQUENCES`. A DBA running the documented pre-failover `REFRESH SEQUENCES` during a large copy and then promoting gets never-synced sequences.
**Mechanism.** Both worker types share one budget (`logicalrep_sync_worker_count`, launcher.c:949). `ProcessSyncingRelations()` always runs tables before sequences (syncutils.c:173-175); the table loop launches immediately for any never-started table (`last_start_time==0`, tablesync.c:566-567), retaking every freed slot in the same pass; `launch_sync_worker()` then returns silently at the cap (syncutils.c:127). sequencesync.c:138 is the *only* launch site — the launcher never starts one. Verifier caveats: starvation ends when pending tables drop below the limit (not literally indefinite; failing tables' throttled relaunches leave windows), and logical-replication.sgml:1814-1819 does disclose the budget cap — but the strict tables-first priority is undocumented, and config.sgml:5695-5700's "one additional worker ... for sequence synchronization" implies provisioning +1 helps, which the greedy table loop defeats at any GUC value.
**Trigger.** Default `max_sync_workers_per_subscription=2`, ≥2 large tables mid-copy, then `REFRESH SEQUENCES`.
**Repro sketch.** Publisher: 3 tables × 10M rows + 1 advanced sequence, publications `FOR ALL TABLES` and `FOR ALL SEQUENCES`. Subscriber: subscribe to both; while ≥2 tablesync workers run, `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` and poll — sequence rows stay 'i' and `pg_stat_subscription` shows no `sequence synchronization` worker until the copy tail.
**Fix direction.** Reserve a slot for (or prioritize) sequence sync when INIT sequences exist — or at minimum log the deferral — and align config.sgml with the actual shared-budget behavior.
---
### 6. No publisher-version guard: pre-PG19 publisher causes a perpetual, cryptic error loop (Medium)
**Defect.** A subscription with registered sequences repointed at a pre-PG19 publisher (e.g. upgrade rollback via `ALTER SUBSCRIPTION ... CONNECTION`, or a DNS/VIP flip mid-sync) enters an endless 5-second error/relaunch loop: against PG18, `ERROR: invalid query response DETAIL: Expected 11 fields, got 10 fields.` (SQLSTATE 08P01, suggesting protocol corruption, not a version problem); against ≤PG17, `function pg_get_sequence_data(oid) does not exist`. Sequences stay 'i'; `disable_on_error=true` disables the whole subscription.
**Mechanism.** sequencesync.c has zero `walrcv_server_version()` checks (the only ≥190000 gates in the sync paths are tablesync.c:802, 1004); `copy_sequences()` unconditionally sends the batch query (:517-527) expecting REMOTE_SEQ_COL_COUNT=11 columns at :529. PG18's `pg_get_sequence_data()` exists but returns 2 output columns (page_lsn added by b93172c for PG19), so the query succeeds remotely with 10 fields and `libpqrcv_processTuples` errors (libpqwalreceiver.c:1083-1088). The broken state is reachable because sequence rows persist across `ALTER SUBSCRIPTION ... CONNECTION`, and `AlterSubscription_refresh_seq` *succeeds* against an old publisher (its origin check returns early for <190000, subscriptioncmds.c:3196-3198), reporting success and then unleashing the storm. `REFRESH PUBLICATION` incidentally recovers by removing the rows, but nothing in the error suggests that.
**Trigger.** PG19→PG19 subscription with synced sequences; repoint the connection at a PG18 host holding the slot; `REFRESH SEQUENCES`.
**Repro sketch.** Node A (PG19) and node B (PG18) with identical schema; subscribe to A (`FOR ALL SEQUENCES`), wait for 'r'; `ALTER SUBSCRIPTION sub1 CONNECTION 'host=B ...'; ALTER SUBSCRIPTION sub1 REFRESH SEQUENCES;` → 5-second loop of worker-start LOG + 08P01 ERROR.
**Fix direction.** Version-gate the sequencesync launch/copy path (and `REFRESH SEQUENCES`) with a clear "publisher (version N) does not support sequence synchronization" error or warning instead of retrying a query that can never succeed.
---
### 7. `has_sequence_privilege()` NULL on concurrent publisher DROP: Assert crash / wrong diagnosis (Medium)
**Defect.** A published sequence dropped on the publisher while the batch query runs yields a result row with NULL in the `has_sequence_privilege` column; assert-enabled subscribers hit `Assert(!isnull)` (sequencesync.c:297-298) — TRAP → SIGABRT → crash-restart of the sequencesync worker; production builds decode NULL as false and misreport the drop as `insufficient privileges on publisher sequence (...)` with a bogus GRANT SELECT hint. This re-introduces the concurrent-drop hazard 1ba3eee fixed for the data columns: d4a657b added the privilege column with an unconditional Assert.
**Mechanism.** The batch query (sequencesync.c:517-527) joins pg_class/pg_sequence under the query snapshot, but `has_sequence_privilege_id()` uses `get_rel_relkind()` on the current catalog snapshot and returns SQL NULL for a vanished relation (acl.c:2246-2248). The row is still emitted (old snapshot), and the LATERAL `pg_get_sequence_data()` — which locks the relation and absorbs the drop's invalidations first (lmgr.c:134-137; commit ordering xact.c:2475/2480) — makes the NULL deterministic, not a narrow race: an uncommitted DROP holds AccessExclusiveLock, the sync query blocks inside it, and COMMIT triggers the condition every time. With NULL data + false privilege, production takes the `COPYSEQ_PUBLISHER_INSUFFICIENT_PERM` path (:300-303) instead of the concurrent-drop `COPYSEQ_SKIPPED` path.
**Trigger.** Publisher-side `DROP SEQUENCE` committing while the batch SELECT executes.
**Repro sketch.** Two sequences subscribed and 'r'. Publisher session A: `BEGIN; DROP SEQUENCE s2;` (hold). Subscriber: `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` (batch query blocks on A's lock). Publisher: `COMMIT;` → assert build: TRAP at sequencesync.c:298; production: wrong privilege WARNING + sync-failure ERROR.
**Fix direction.** Treat a NULL privilege column as a concurrently-dropped sequence (fold into the existing `COPYSEQ_SKIPPED` handling) rather than asserting non-null.
---
### 8. Permission-denied sequences double-reported as "missing on publisher" (Medium)
**Defect.** When one batch contains both a genuinely missing sequence and a publisher-permission-denied sequence, the perm-denied sequence is listed in *both* warnings: `insufficient privileges on publisher sequence ("public.s2")` and `missing sequences on publisher ("public.s1", "public.s2")` — contradictory diagnoses for an object that exists (the worker even received a row for it). This is the residual gap in d4a657b, whose stated purpose was exactly to stop permission failures being reported as missing.
**Mechanism.** The perm-denied early return (sequencesync.c:300-303) fires before `found_on_pub = true` is set (:334), so perm-denied entries keep `found_on_pub=false` (palloc0 at :737). A genuinely missing sequence produces no row (inner joins, :517-527), making `batch_missing_count > 0` (:633-637), which enables the post-commit scan (:649-660) that appends *every* `!found_on_pub` entry to the missing list (:657) with no dedup in `report_sequence_errors()` (:226-251). d4a657b's own test (036_sequences.pl:228-262) recreated the dropped sequence before the permission test, so the mixed batch was never exercised.
**Trigger.** Same ≤100-sequence batch containing one sequence dropped on the publisher and one with SELECT revoked from the subscription's connection role, then `REFRESH SEQUENCES` (which never prunes publisher-dropped sequences).
**Repro sketch.** Both nodes: s1, s2; subscribe, wait for 'r'. Publisher: `REVOKE SELECT ON SEQUENCE s2 FROM repuser; DROP SEQUENCE s1;`. Subscriber: `ALTER SUBSCRIPTION sub REFRESH SEQUENCES;` → the contradictory warning pair repeats on every relaunch.
**Fix direction.** Set `found_on_pub` before the permission early-return (or exclude entries already classified perm-denied from the missing scan) so each sequence appears in exactly one list.
---
### 9. ALTER SUBSCRIPTION ... DISABLE never stops a running sequencesync worker (Low)
**Defect.** After `DISABLE`, the apply worker exits promptly but the sequencesync worker keeps starting new batch transactions, overwriting subscriber sequence values and flipping `pg_subscription_rel` rows to 'r' after the disable — for minutes normally, indefinitely if lock-blocked — contradicting alter_subscription.sgml's "stopping the logical replication worker" (:270-278). A `nextval()` run locally post-DISABLE can be clobbered back to an older publisher value.
**Mechanism.** The worker's entire lifetime (SequenceSyncWorkerMain → ... → `copy_sequences` loop, sequencesync.c:447-668) contains no `maybe_reread_subscription()` (call sites are only worker.c:740/2565/4183 and applyparallelworker.c:283) and never reads `MySubscriptionValid`/`enabled` after startup; DISABLE only writes `subenabled=false` and signals nothing (`logicalrep_worker_stop` is called only at subscriptioncmds.c:1257 and DropSubscription :2636); no exit path stops it (launcher.c:822-842 handles only parallel apply workers). Related to item 1's root cause (worker never revalidates anything), but the trigger is subscription-level state rather than per-relation rows, so reported separately; a batch-boundary recheck would address both.
**Trigger.** `DISABLE` while a sync of many sequences is in flight, or while the worker is blocked at `try_table_open` (:336/:718) behind an open `ALTER SEQUENCE`.
**Repro sketch.** 300 sequences; subscriber session A holds `BEGIN; ALTER SEQUENCE s150 RESTART 1;`; create subscription; once the worker blocks, `ALTER SUBSCRIPTION sub DISABLE;` (apply worker exits, sequencesync worker persists); `COMMIT;` in A → post-disable, sequences s101-s300 change values and READY count climbs to 300.
**Fix direction.** Recheck subscription validity/enabled at each batch boundary in `copy_sequences()` (and consider having DISABLE stop sync workers explicitly).
---
### 10. REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE: internal XX000 errors (Low)
**Defect.** Ordinary DDL concurrency — `REFRESH SEQUENCES` racing a subscriber-side `DROP SEQUENCE` — ends with one command failing on an internal elog: XX000 `subscription relation %u in subscription %u does not exist`, `tuple concurrently deleted`, or the DROP failing `tuple concurrently updated`; assert builds can additionally trip the relkind Assert in `GetSubscriptionRelations` (pg_subscription.c:666). Transient (retry succeeds) but corruption-looking.
**Mechanism.** `AlterSubscription_refresh_seq()` builds its list (subscriptioncmds.c:1387) holding no lock on the sequences and *not* taking the pg_subscription_rel AccessExclusiveLock its sibling removal paths take precisely to freeze rel states (:1333-1334); `DROP SEQUENCE` deletes the row via `heap_drop_with_catalog` → `RemoveSubscriptionRel` with only RowExclusiveLock on pg_subscription_rel (pg_subscription.c:505) and no subscription-object lock, so nothing serializes the commands until they collide on the catalog tuple (`UpdateSubscriptionRelState` syscache miss → elog at pg_subscription.c:413-415; heapam.c:4477/3176 for the wait-then-fail variants). The sync worker itself is protected against this race (it holds the relation open across its update, sequencesync.c:336/416); the refresh command is the unprotected outlier.
**Trigger.** Deterministic: `BEGIN; DROP SEQUENCE s1;` in one session, `REFRESH SEQUENCES` in another, then COMMIT (→ "tuple concurrently deleted"); reverse order inside `BEGIN` for "tuple concurrently updated" (REFRESH SEQUENCES is allowed in a transaction block).
**Repro sketch.** As trigger, on a subscription with s1/s2 already 'r'.
**Fix direction.** Take the same pg_subscription_rel AccessExclusiveLock (or per-sequence locks) in `AlterSubscription_refresh_seq`, and/or tolerate a concurrently-vanished row with a skip instead of elog.
---
### 11. Sequence-removal loop runs after irreversible tablesync slot drops (Low)
**Defect.** f0b3573c3 appended the sequence-removal loop (subscriptioncmds.c:1322-1344) *after* the `ReplicationSlotDropAtPubNode()` loop (:1295-1316), violating the explicit invariant at :1290-1294 that slot drops must come last because they cannot be rolled back. An error or cancel during sequence removal (each `RemoveSubscriptionRel` seqscans pg_subscription_rel with CFI, pg_subscription.c:497-567) aborts the transaction after publisher-side sync slots were dropped; a rolled-back-to-`FINISHEDCOPY` ('f') table then reuses its now-nonexistent slot without recreating it (tablesync.c:1342-1361) and error-loops with `replication slot "pg_%u_sync_%u_..." does not exist`.
**Skeptical caveats (verifier).** Re-running the failed ALTER self-heals (the slot drop is `missing_ok`); the wedge persists only if the publication change is abandoned. And with ≥2 removed tables a cancel between slot drops could already cause this pre-f0b3573c3 — the new loop violates the stated invariant, substantially widens the window, and newly exposes the single-removed-table case.
**Trigger.** `SET statement_timeout='2s'; ALTER SUBSCRIPTION sub SET PUBLICATION pub_new;` where the old set contained a mid-catchup ('f') table plus many sequences, timeout landing in the sequence loop.
**Repro sketch.** Large table held at 'f' via an AccessExclusiveLock on the subscriber; subscription also covers a sequence; ALTER SET PUBLICATION to a set containing neither, cancelled during sequence removal (gdb breakpoint at :1322 + `pg_cancel_backend` for determinism) → t1 wedged at 'f' with its slot gone.
**Fix direction.** Move the sequence-removal loop above the slot-drop loop, restoring the "slot drops last" invariant.
---
### 12. Batch query mixes MVCC definition columns with live `last_value`: validation bypass (Low)
**Defect.** A publisher `ALTER SEQUENCE` + `nextval()` committing while the batch query runs can produce an internally inconsistent row (old seqmin/seqmax/seqcycle, new last_value) that passes definition validation, yielding either a confusing `setval: value N is out of bounds for sequence` ERROR (22003) from a worker that never ran setval (subscription disabled under `disable_on_error`), or — CYCLE added upstream — a *silently* synced wrapped value marked READY despite a CYCLE/NO CYCLE mismatch the documented validation promises to catch.
**Mechanism.** The query (sequencesync.c:517-527) reads pg_sequence under the statement snapshot but `pg_get_sequence_data()` is volatile and reads the live buffer tuple (sequence.c:1831-1836); `ALTER SEQUENCE` takes only ShareRowExclusiveLock (sequence.c:450), not conflicting with the LATERAL's transient AccessShareLock. `get_and_validate_seq_info()` compares only the stale definition columns (:352-358); `SetSequence()`'s local bounds check (sequence.c:989-994) then errors, or the wrapped value fits and is marked READY (:416) with no warning. Same query-consistency family as item 7 (1ba3eee fixed the drop case; the ALTER case remains). Window is one publisher-side query execution — narrow; deterministic only with a paused walsender. The error variant self-corrects into the documented mismatch report on the next relaunch.
**Trigger.** `REFRESH SEQUENCES` concurrent with publisher `ALTER SEQUENCE s MAXVALUE 200; SELECT nextval('s')...` (error variant) or `ALTER SEQUENCE s CYCLE; SELECT nextval('s')` at max (silent variant).
**Repro sketch.** Both nodes `CREATE SEQUENCE s MAXVALUE 100`; subscribe; pause the publisher walsender in `pg_get_sequence_data` (gdb), apply the ALTER+nextval, resume.
**Fix direction.** Return the definition columns from `pg_get_sequence_data()` itself (one consistent read of the live tuple), or re-validate the received `last_value` against the local definition and classify violations as a retryable mismatch rather than letting setval's error surface.
---
### 13. REFRESH SEQUENCES warning misattributes a `copy_data` request the command cannot express (Low)
**Defect.** Plain `ALTER SUBSCRIPTION sub REFRESH SEQUENCES` on an `origin=none` subscription in a bidirectional setup emits `WARNING: subscription "sub" requested copy_data with origin = NONE but might copy data that had a different origin` — but REFRESH SEQUENCES accepts no `WITH` options at all (gram.y:11611-11619; no options parsed at subscriptioncmds.c:1614-1654), so the user "requested" nothing; they will hunt for a nonexistent knob.
**Mechanism.** `AlterSubscription_refresh_seq()` calls `check_publications_origin_sequences(..., true /* copydata hardcoded */, ...)` (subscriptioncmds.c:1383-1384), which reuses the CREATE SUBSCRIPTION/REFRESH PUBLICATION message at :3270-3277 — accurate at those call sites (which pass the user's actual copy_data), inaccurate here. The warning's substance (sequence data will be copied and may have a different origin) and hint remain correct; only the attribution is wrong.
**Trigger/repro sketch.** Two-node bidirectional `origin=none` sequence subscriptions; run `REFRESH SEQUENCES` on either side.
**Fix direction.** Use a REFRESH SEQUENCES-specific message ("... will copy sequence data that might have a different origin") instead of the "requested copy_data" wording.
---
### 14. psql tab-completion regression after `REFRESH PUBLICATION` (Low)
**Defect.** In PG18, `ALTER SUBSCRIPTION sub REFRESH PUBLICATION <TAB>` completed `WITH (`; in master it offers the subscriber's *local* publication names (syntactically invalid if accepted — the command takes no name, gram.y:11601) or nothing when no local publication exists.
**Mechanism.** f0b3573c3 deleted the rule `Matches("ALTER","SUBSCRIPTION",MatchAny,MatchAnyN,"REFRESH","PUBLICATION") → COMPLETE_WITH("WITH (")` (REL_18_0 tab-complete.in.c:2306), replacing it with a REFRESH-terminal rule (master :2355-2356) and never re-adding the follow-up; no remaining pattern matches, so control falls to the `words_after_create` fallback, where prev_wd "PUBLICATION" hits `Query_for_list_of_publications` (:1335, :1212-1215). The surviving `REFRESH PUBLICATION WITH (` → copy_data rule (:2358) shows the path is still intended.
**Trigger/repro sketch.** Subscriber with a local publication (bidirectional setup); type the command and press Tab.
**Fix direction.** Restore the deleted `..."REFRESH","PUBLICATION"` → `COMPLETE_WITH("WITH (")` rule.
---
### 15. pg_stat_subscription row for the sequencesync worker: fabricated times, undocumented NULLs (Low)
**Merged finding.** Two audit findings merged: both stem from adding the new worker type to `pg_stat_subscription` (55cefadde touched only the `worker_type` row in monitoring.sgml) without adjusting either the column semantics in code or the per-column NULL documentation.
**Defect.** For its entire lifetime, a sequencesync worker's row shows `last_msg_send_time`/`last_msg_receipt_time`/`latest_end_time` frozen at the worker's *start* time — masquerading as WAL-sender message times for a worker type that never receives WAL-sender messages — while `received_lsn`/`latest_end_lsn`/`relid`/`leader_pid` are always NULL. monitoring.sgml (:2325-2327, :2336-2337, :2346-2347, :2376-2377) enumerates NULL cases that omit this worker type entirely (e.g. relid "NULL for the leader apply worker and parallel apply workers"; time/LSN columns "NULL for parallel apply workers"). A DBA watching a long sync sees frozen "message" timestamps and an inconsistent row (`latest_end_time` set, `latest_end_lsn` NULL) suggesting a hung connection.
**Mechanism.** `SetupApplyOrSyncWorker()` initializes the three timestamps to `GetCurrentTimestamp()` (worker.c:5979-5981); the only updater, `UpdateWorkerStats()` (worker.c:3987), is called solely from `LogicalRepApplyLoop`, which this worker never enters; `pg_stat_get_subscription()` NULLs timestamps only when exactly 0 (launcher.c:1671-1683), a convention only parallel apply workers satisfy (applyparallelworker.c:958-959), and emits `relid` only for tablesync workers (launcher.c:1656-1659; sequencesync launches with InvalidOid relid, sequencesync.c:138).
**Trigger/repro sketch.** Poll `SELECT worker_type, relid, leader_pid, received_lsn, latest_end_lsn, last_msg_send_time FROM pg_stat_subscription WHERE worker_type='sequence synchronization'` during a sync of a few thousand sequences.
**Fix direction.** Zero the time fields for sequencesync workers so they render NULL (matching the parallel-apply convention), and extend monitoring.sgml's per-column NULL enumerations to cover the new worker type.
---
### 16. catalogs.sgml `srsublsn` description wrong for sequence rows (Low)
**Defect.** catalogs.sgml:8893-8896 still describes `srsublsn` solely as "Remote LSN of the state change ... when in s or r states, otherwise null". For a sequence row it actually holds the publisher sequence's *page LSN* from `pg_get_sequence_data()` — possibly far older than the sync, and precisely the value logical-replication.sgml:1844-1853 tells DBAs to compare in the out-of-sync procedure — and `copy_data=false` creates 'r'-state sequence rows with `srsublsn` NULL, contradicting "otherwise null".
**Mechanism.** `copy_sequence()` writes `UpdateSubscriptionRelState(..., SUBREL_STATE_READY, seqinfo->page_lsn, ...)` (sequencesync.c:416-417), page_lsn being `PageGetLSN()` of the publisher's sequence page (sequence.c:1836); copy_data=false inserts sequences directly READY with InvalidXLogRecPtr → SQL NULL (subscriptioncmds.c:985, :1203-1205; pg_subscription.c:353-356). Caveat: the ('r', NULL) shape has existed for tables since PG10; the feature-specific gap is the undocumented page-LSN meaning, which the sequences docs actively rely on.
**Trigger/repro sketch.** `CREATE SUBSCRIPTION ... WITH (copy_data=false)` → ('r', NULL) rows; after `REFRESH SEQUENCES`, `srsublsn` equals the publisher's `pg_get_sequence_data(...).page_lsn`.
**Fix direction.** Amend the `srsublsn` entry to state its sequence-row meaning (publisher page LSN used for out-of-sync comparison) and the copy_data=false NULL case.
---
### 17. Docs omit the PG19+ publisher requirement for sequence sync; REFRESH SEQUENCES silently no-ops (Low)
**Defect.** The failover-preparation restriction (logical-replication.sgml:2359-2372) recommends `ALTER SUBSCRIPTION ... REFRESH SEQUENCES` with no stated publisher-version precondition, and no SGML file anywhere mentions one; against a pre-PG19 publisher, sequences are silently never registered (`fetch_relation_list` gates the sequence UNION on `server_version >= 190000`, subscriptioncmds.c:3467) and `REFRESH SEQUENCES` succeeds as a fully silent no-op (origin check returns early, :3196-3198; empty relation list, empty loop). A DBA on the standard staged-upgrade topology (PG19 subscriber ← PG18 publisher) follows remedy #1, sees success, and discovers the gap as duplicate keys after failover — when remedies #2/#3 (manual update) would have worked.
**Skeptical caveat.** Severity capped low: `FOR ALL SEQUENCES` cannot be created on a pre-PG19 publisher, so a from-scratch setup fails loudly at step one; the exposed audience is existing cross-version subscriptions. Related to item 6 (same version dependency) but distinct: item 6 is the error loop when sequence rows *do* exist; this is silent success when they don't. In-tree precedent documents comparable cross-version caveats (binary pre-16, row filters pre-15) and `retain_dead_tuples` even errors on old publishers (:3304-3307).
**Trigger/repro sketch.** PG18 publisher `FOR ALL TABLES` with a serial column advancing to ~1000; PG19 subscriber follows the restrictions text: `REFRESH SEQUENCES` succeeds, `last_value` stays 1; post-failover insert → duplicate key.
**Fix direction.** State the PG19+ publisher requirement in the sequences documentation (and consider a NOTICE/WARNING from `REFRESH SEQUENCES` when the subscription has no subscribed sequences or the publisher predates support).
---
### 18. Restrictions bullet still claims only tables can be replicated (Low)
**Defect.** logical-replication.sgml:2399-2405 (unchanged since 17b9e7f) states "Replication is only supported by tables ... Attempts to replicate other types of relations, such as views, materialized views, or foreign tables, will result in an error" — contradicted three sections earlier by "Replicating Sequences" (:1772) and by the adjacent sequence-restrictions bullet (:2357-2372) added by 55cefadde.
**Mechanism.** Sequences are publishable (`is_publishable_class` accepts RELKIND_SEQUENCE, pg_publication.c:163-172) and valid subscription targets (`CheckSubscriptionRelkind`, execReplication.c:1140-1166); `FOR ALL SEQUENCES` grammar exists (gram.y:11422); 036_sequences.pl demonstrates successful sequence sync with no error. The only defense — reading "Replication" as strictly incremental streaming — fails against the bullet's categorical second sentence and the docs' own "Replicating Sequences" terminology.
**Trigger/repro sketch.** Read the Restrictions list, then observe `CREATE PUBLICATION p FOR ALL SEQUENCES` + subscription syncing values without error.
**Fix direction.** Reword the bullet to say tables and sequences are supported (sequences synchronized on request rather than streamed), keeping the error claim accurate for views/matviews/foreign tables.
---
## Appendix A: findings dropped unverified (verifier cap of 23)
These 5 ranked below the cap and were **not** adversarially verified; treat as leads only.
- ProcessSequencesForSync's only_running=true check allows two concurrent sequencesync workers for one subscription, causing "tuple concurrently updated" errors and disable_on_error trips
- Unlogged subscriber sequence reaches durable READY state while its synced value is lost on crash, permanently stale and reported in-sync
- create_subscription.sgml claims the origin parameter "has no effect for sequences", yet origin=none triggers sequence-specific origin warnings and sequence values of foreign origin are copied regardless, which is documented nowhere
- Docs recommend ALTER SUBSCRIPTION ... REFRESH SEQUENCES to update sequences at failover, but the command cannot run once the publisher is down and the duplicate-value consequence of lagging sequences is never stated
- Connect-failure message uses internal jargon "sequencesync worker", inconsistent with the same worker's other messages and the tablesync counterpart
## Appendix B: findings refuted by adversarial verification
- **REFRESH SEQUENCES is allowed inside a transaction block and can deadlock with the sequencesync worker when combined with ALTER SEQUENCE**
- Refutation: The individual lock sites are all real (verified in master: no PreventInTransactionBlock in the ALTER_SUBSCRIPTION_REFRESH_SEQUENCES case at src/backend/commands/subscriptioncmds.c:2286-2297; AccessExclusiveLock on the subscription object at subscriptioncmds.c:1714 for ALL ALTER SUBSCRIPTION forms; AccessShareLock on the subscription object in UpdateSubscriptionRelState at src/backend/catalog/pg_subscription.c:405 held to batch commit; try_table_open(RowExclusiveLock) at src/backend/replication/logical/sequencesync.c:336; ShareRowExclusiveLock for ALTER SEQUENCE at src/backend/commands/sequence.c:449-453). But the finding fails on four counts. (1) Its trigger is unreachable: copy_sequence -> UpdateSubscriptionRelState is called ONLY on COPYSEQ_SUCCESS (sequencesync.c:554-556, 416), and per-batch successes are committed (line 647) before report_sequence_errors raises the mismatch ERROR (line 671), so in the claimed 'worker retries every 5s due to a definition mismatch' steady state the retry batch contains only failing INIT sequences, which never acquire the subscription-object lock -- no cycle can form in the documented mismatch-repair workflow; a worker parked on the user's ALTERed sequence holds nothing the user's REFRESH SEQUENCES needs. (2) The causal premise is wrong: the other refresh forms forbid transaction blocks because AlterSubscription_refresh irreversibly drops table-synchronization slots on the publisher (per alter_subscription.sgml), not to avoid deadlocks; AlterSubscription_refresh_seq (subscriptioncmds.c:1360-1406) performs only read-only publisher queries and rollbackable local catalog updates, and the docs' explicit list of forms that 'cannot be executed inside a transaction block' intentionally omits REFRESH SEQUENCES -- code and documentation agree, so this is designed behavior. (3) Adding PreventInTransactionBlock to REFRESH SEQUENCES would not eliminate the deadlock: line 1714 is reached by every ALTER SUBSCRIPTION form, and ENABLE/DISABLE/SET/SKIP are deliberately allowed in transaction blocks (ENABLE docs even say the worker starts 'at the end of the transaction'), so BEGIN; ALTER SEQUENCE s; ALTER SUBSCRIPTION sub DISABLE; forms the identical cycle, as does BEGIN; ALTER SEQUENCE s1; ALTER SEQUENCE s2; against a worker retaining RowExclusiveLock on s2 from earlier in the batch (table_close NoLock, line 625). (4) The deadlock class is pre-existing and accepted: tablesync has held the target-table RowExclusiveLock across COPY (tablesync.c:1401) and then taken the subscription-object AccessShareLock via UpdateSubscriptionRelState (tablesync.c:1503) in one transaction since commit cb9079c (2017), giving the same detector-resolved cycle against transaction-block ALTER SUBSCRIPTION forms; and the comment at sequencesync.c:485-509 shows the developers explicitly analyzed worker-vs-ALTER SEQUENCE deadlocks and guarded only against the undetectable cross-node variant, accepting local detectable ones. The only constructible outcome (requires a multi-sequence batch with at least one success before the contended sequence, i.e. mid initial-sync or mid full re-sync, plus a deliberately open transaction mixing sequence DDL with ALTER SUBSCRIPTION) is a transient 'ERROR: deadlock detected' that the deadlock detector resolves and the respawned worker recovers from -- standard, accepted PostgreSQL DDL concurrency behavior, not a user-visible defect of the sequence-replication feature.
- **Sequencesync batches accumulate RowExclusiveLock on up to 100 sequences until commit, creating deadlocks with ordinary multi-sequence DDL or ALTER SUBSCRIPTION in the same transaction**
- Refutation: The mechanism is factually accurate but describes ordinary, deliberate, detected-and-resolved PostgreSQL locking behavior, not a defect. Verified: sequencesync batches take try_table_open(seq, RowExclusiveLock) per row (sequencesync.c:336), retain locks via table_close(NoLock) (line 625) until CommitTransactionCommand (line 647) for up to 100 sequences (line 441); ALTER SEQUENCE takes conflicting ShareRowExclusiveLock (sequence.c:449-450); UpdateSubscriptionRelState takes AccessShareLock on the subscription held to commit (pg_subscription.c:405); ALTER SUBSCRIPTION takes AccessExclusiveLock (subscriptioncmds.c:1714). So the claimed lock cycles can occur — but both parties are ordinary backends on heavyweight locks, so the local deadlock detector resolves the cycle after deadlock_timeout with a clean, retryable "deadlock detected" error. That disqualifies it as a defect on four grounds: (1) The trigger requires the user's own transaction to acquire conflicting locks on multiple objects in inconsistent order — the canonical application-responsibility scenario per the docs (mvcc.sgml "Deadlocks"); two plain user sessions doing the same two ALTER SEQUENCEs in opposite order deadlock identically with no replication involved. (2) The finding's novelty claim ("unlike tablesync ... this wait-while-holding pattern is new") is wrong: the apply worker has accumulated RowExclusiveLocks on many relations per applied transaction, in remote-determined order the local user cannot predict, since PG10 (worker.c:2673/2730, 2834/2918, 3057/3117 open with RowExclusiveLock, close with NoLock), and applyparallelworker.c:62-67 explicitly states such deadlocks "can happen even without parallel mode when there are concurrent operations on the subscriber" — the project's design bar is detectability, not prevention. (3) sequencesync.c deliberately implements that doctrine: the header comment (lines 42-44) documents batch-duration lock retention, and the comment at 485-509 orders the remote fetch (walrcv_exec, line 529) before any local sequence locks so the worker never waits on the network while holding local locks, eliminating undetectable cross-node deadlocks and knowingly accepting locally detectable ones. (4) The outcome is transient, atomic, and self-healing: a victim worker's batch aborts atomically (SetSequence and READY-state updates roll back together, sequences stay INIT, no inconsistent catalog state), start_sequence_sync reports stats and rethrows, and the apply worker relaunches a sequencesync worker after wal_retrieve_retry_interval (syncutils.c:117-148, 175), which then succeeds; disable_on_error disabling the subscription on any error, including deadlocks, is that option's documented contract and applies equally to apply-worker deadlocks today. There is also no actionable fix consistent with the design: the lock protecting the WAL-logged SetSequence update cannot be released before commit, batching is a documented performance choice, and no ordering discipline can be consistent with arbitrary user DDL order. Not covered by any already-fixed commit (git log on sequencesync.c shows no locking changes). A report of this would be answered "working as intended; retry on deadlock", so confirming it would waste committer time.
- **A failed or interrupted sync batch leaves subscriber sequence values silently overwritten (non-transactionally) while pg_subscription_rel still says INIT**
- Refutation: Every code citation in the finding is accurate — I traced them all in master: copy_sequences() batches up to 100 sequences per transaction (src/backend/replication/logical/sequencesync.c:441, StartTransactionCommand :462, CommitTransactionCommand :647), accumulates RowExclusiveLocks to commit (:336, table_close(NoLock) :625), copy_sequence() calls SetSequence at :407 followed by transactional UpdateSubscriptionRelState(SUBREL_STATE_READY) at :416-417, and SetSequence (src/backend/commands/sequence.c:946-1043) updates the sequence page in place in a critical section with XLOG_SEQ_LOG (:1011-1038) — no relfilenode swap, so the change is immediately visible (frozen-xmin tuple) and survives transaction abort. A pg_terminate_backend hitting CHECK_FOR_INTERRUPTS (:544) mid-batch does leave earlier sequences' values applied while their catalog rows revert to 'i' with NULL srsublsn (REFRESH SEQUENCES writes InvalidXLogRecPtr, subscriptioncmds.c:1392-1393). Nothing in git history changed this. However, the behavior is not a defect: (1) SetSequence IS setval()'s implementation, and setval's non-transactionality is explicitly documented (doc/src/sgml/func/func-sequence.sgml:199-201: changes "are immediately visible to other transactions, and are not undone if the calling transaction rolls back") — the feature inherits documented sequence semantics; (2) "failed sync => no persisted mutation" was never this feature's contract even absent aborts: report_sequence_errors() raises the overall failure ERROR only AFTER all batches have committed (sequencesync.c:670-673), so a sync failing on one mismatched/missing/no-permission sequence has already permanently committed publisher values AND READY state for every other sequence — the finding's within-batch-abort case (values applied, state 'i') is a strictly milder variant of designed behavior (values applied, state 'r', command reports failure); (3) the leftover 'i' state is the conservative, correct-by-contract state: the apply worker respawns the sequencesync worker (ProcessSequencesForSync, sequencesync.c:96-140; syncutils.c wal_retrieve_retry_interval throttling) and re-copy via SetSequence is idempotent, a retry loop the docs explicitly describe (logical-replication.sgml:1824-1829); the ordering (non-rollbackable value first, transactional catalog second) is the only crash-safe ordering for a non-MVCC object — the reverse would produce READY-without-copy, a real data-loss bug; (4) the claimed harm ("regressing a locally-advanced sequence" to the publisher value) is exactly what the user commanded — a successful REFRESH SEQUENCES installs the same value — and no wrong final value can persist while the subscription exists; (5) no documentation promises atomicity or rollback of sequence values on sync failure, so there is no documented-vs-actual divergence, and pg_subscription_rel showing 'i' is accurate per its meaning ("needs synchronization"; resync is pending and harmless). The tablesync comparison establishes no contract: heap data is MVCC-rollbackable, sequence data fundamentally is not. A report of this to pgsql-hackers would be closed as works-as-intended/inherent setval semantics. The mechanism is real but it is intended, documented-primitive, self-healing behavior — not a user-visible defect.
## Appendix C: provenance
Produced by a 45-agent workflow (20 finder lenses over master @ a8c2547 -> dedup/rank of 63 raw findings -> 23 adversarial verifiers -> synthesis). All verification is static code tracing; no repro was executed. Per-agent transcripts: ~/.claude/projects/-home-nm-src-pg-postgresql/3a1250f2-41e8-46fd-afe1-08ff8e4dace0/subagents/workflows/wf_e3d25a7b-189/journal.jsonl
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-10 07:00 ` vignesh C <vignesh21@gmail.com>
2026-07-10 08:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-10 08:40 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
6 siblings, 2 replies; 82+ messages in thread
From: vignesh C @ 2026-07-10 07:00 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com; pgsql-hackers
On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
>
> A Fable 5 review of logical replication of sequences found a way to get
> subscribed sequences into READY state despite the subscriber side having data
> older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> I reviewed the test, and I think it identifies a genuine defect.
Thanks for reporting this. The test case reproduces this issue. This
issue is not limited to lock contention. It can also occur if the
sequence synchronization worker is simply slow. For example, the
worker may fetch the sequence value from the publisher, after which
the publisher sequence advances and ALTER SUBSCRIPTION ... REFRESH
SEQUENCES is executed. When the worker eventually updates the
subscriber, it uses the stale value that it fetched earlier,
overwriting the sequence with an out-of-date value.
How about raising a warning for the second REFRESH SEQUENCES
indicating that the sequence is already being synchronized and
skipping it? The warning could also include a hint to rerun ALTER
SUBSCRIPTION ... REFRESH SEQUENCES after the current synchronization
completes. This approach avoids making ALTER SUBSCRIPTION ... REFRESH
SEQUENCES wait for the sequence synchronization worker, which could
itself be blocked waiting for a lock held by a long-running
transaction on the sequence.
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-10 07:00 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-10 08:18 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
1 sibling, 0 replies; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-10 08:18 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com <amit.kapila16@gmail.com>; pgsql-hackers
Dear Vignesh, Noah,
Agree the basic approach that we avoid the re-initializing the catalog.
My primitive idea is to follow what slotsync does. SlotSyncCtxStruct::syncing is
checked from the SQL function and the slotsync worker, and either of them can
continue. So we may be able to have LogicalRepCtxStruct::synching_seqs or
something to indicate the status.
But there might be other ways, needs to be investigated.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-10 07:00 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-10 08:40 ` shveta malik <shveta.malik@gmail.com>
2026-07-10 12:36 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-10 08:40 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com, pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Fri, Jul 10, 2026 at 12:30 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
> >
> > A Fable 5 review of logical replication of sequences found a way to get
> > subscribed sequences into READY state despite the subscriber side having data
> > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > I reviewed the test, and I think it identifies a genuine defect.
>
> Thanks for reporting this. The test case reproduces this issue. This
> issue is not limited to lock contention. It can also occur if the
> sequence synchronization worker is simply slow. For example, the
> worker may fetch the sequence value from the publisher, after which
> the publisher sequence advances and ALTER SUBSCRIPTION ... REFRESH
> SEQUENCES is executed. When the worker eventually updates the
> subscriber, it uses the stale value that it fetched earlier,
> overwriting the sequence with an out-of-date value.
I have the same opinion here.
> How about raising a warning for the second REFRESH SEQUENCES
> indicating that the sequence is already being synchronized and
> skipping it? The warning could also include a hint to rerun ALTER
> SUBSCRIPTION ... REFRESH SEQUENCES after the current synchronization
> completes.
I agree. This is the simplest solution here.
~~
> Agree the basic approach that we avoid the re-initializing the catalog.
> My primitive idea is to follow what slotsync does. SlotSyncCtxStruct::syncing is
> checked from the SQL function and the slotsync worker, and either of them can
> continue. So we may be able to have LogicalRepCtxStruct::synching_seqs or
> something to indicate the status.
> But there might be other ways, needs to be investigated.
If this approach is accepted, I would prefer implementing it similarly
to how ALTER SUBSCRIPTION ... SET (two_phase) is handled. The command
simply fails (with an appropriate hint) if the apply worker for the
subscription is still running, by checking for the presence of the
apply worker.
IMO, a mechanism similar to SlotSync is not needed here because there
is currently no scenario in which continuous incremental
synchronization and one-time manual synchronization can run
concurrently. The worker itself is started only by the manual
synchronization operation and is not a continuously running process.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-10 07:00 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-10 08:40 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-10 12:36 ` vignesh C <vignesh21@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: vignesh C @ 2026-07-10 12:36 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com; pgsql-hackers
On Fri, 10 Jul 2026 at 14:10, shveta malik <shveta.malik@gmail.com> wrote:
>
> On Fri, Jul 10, 2026 at 12:30 PM vignesh C <vignesh21@gmail.com> wrote:
> >
> > On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
> > >
> > > A Fable 5 review of logical replication of sequences found a way to get
> > > subscribed sequences into READY state despite the subscriber side having data
> > > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > > I reviewed the test, and I think it identifies a genuine defect.
> >
> > Thanks for reporting this. The test case reproduces this issue. This
> > issue is not limited to lock contention. It can also occur if the
> > sequence synchronization worker is simply slow. For example, the
> > worker may fetch the sequence value from the publisher, after which
> > the publisher sequence advances and ALTER SUBSCRIPTION ... REFRESH
> > SEQUENCES is executed. When the worker eventually updates the
> > subscriber, it uses the stale value that it fetched earlier,
> > overwriting the sequence with an out-of-date value.
>
> I have the same opinion here.
>
> > How about raising a warning for the second REFRESH SEQUENCES
> > indicating that the sequence is already being synchronized and
> > skipping it? The warning could also include a hint to rerun ALTER
> > SUBSCRIPTION ... REFRESH SEQUENCES after the current synchronization
> > completes.
>
> I agree. This is the simplest solution here.
The attached v1-0001 patch avoids resetting the synchronization state
of sequences that are already being synchronized. Instead, it emits a
warning informing the user that the sequence is already being
synchronized and suggests rerunning ALTER SUBSCRIPTION ... REFRESH
SEQUENCES after the current synchronization completes. I'm not sure
whether a test is necessary for this change. Nevertheless, the
attached v1-0002 patch adds a regression test that uses an injection
point to deterministically reproduce the race condition and verifies
that the expected warning is emitted when ALTER SUBSCRIPTION ...
REFRESH SEQUENCES is invoked while a sequence synchronization is
already in progress.
Regards,
Vignesh
Attachments:
[application/octet-stream] v1-0001-Skip-refreshing-sequences-that-are-already-being-.patch (2.6K, ../../CALDaNm0VQVE8Ju+TtQwH0o3_2j19nByoFdrBUwCLtDjV4wieJg@mail.gmail.com/2-v1-0001-Skip-refreshing-sequences-that-are-already-being-.patch)
download | inline diff:
From 2d274c48ecd8b262df260b91f111bbdbfd9f9eb7 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 10 Jul 2026 18:02:05 +0530
Subject: [PATCH v1 1/2] Skip refreshing sequences that are already being
synchronized
'ALTER SUBSCRIPTION ... REFRESH SEQUENCES' resets the synchronization
state of each subscribed sequence to 'INIT', allowing the sequence sync
worker to synchronize its value from the publisher.
However, if a sequence is already in the 'INIT' state, it indicates that
a previous 'REFRESH SEQUENCES' has not yet been processed by the sequence
sync worker. Resetting it again can race with the worker if it has
already fetched the publisher's sequence value but has not yet applied it
to the subscriber. In that case, the worker may later overwrite the
subscriber sequence with the stale value it previously fetched and mark
the sequence as 'READY', causing the subsequent refresh request to be
lost.
Avoid this race by skipping sequences that are already in the 'INIT'
state. Emit a warning informing the user that the sequence is already
being synchronized and suggesting that 'ALTER SUBSCRIPTION ... REFRESH
SEQUENCES' be rerun after the current synchronization completes.
---
src/backend/commands/subscriptioncmds.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..6569cebb142 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1389,6 +1389,26 @@ AlterSubscription_refresh_seq(Subscription *sub)
{
Oid relid = subrel->relid;
+ /*
+ * If the sequence is already marked INIT, a previous REFRESH
+ * SEQUENCES has not yet been picked up by the sequencesync
+ * worker. Resetting it again here would race with that worker
+ * applying the values it already fetched from the publisher,
+ * possibly leaving the sequence marked READY with stale data.
+ * Skip it and let the user know instead.
+ */
+ if (subrel->state == SUBREL_STATE_INIT)
+ {
+ ereport(WARNING,
+ errmsg("sequence \"%s.%s\" of subscription \"%s\" is already being synchronized, skipping",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid),
+ sub->name),
+ errhint("Rerun %s after the current synchronization completes.",
+ "ALTER SUBSCRIPTION ... REFRESH SEQUENCES"));
+ continue;
+ }
+
UpdateSubscriptionRelState(sub->oid, relid, SUBREL_STATE_INIT,
InvalidXLogRecPtr, false);
ereport(DEBUG1,
--
2.50.1 (Apple Git-155)
[application/octet-stream] v1-0002-Add-a-test-for-concurrent-REFRESH-SEQUENCES.patch (7.9K, ../../CALDaNm0VQVE8Ju+TtQwH0o3_2j19nByoFdrBUwCLtDjV4wieJg@mail.gmail.com/3-v1-0002-Add-a-test-for-concurrent-REFRESH-SEQUENCES.patch)
download | inline diff:
From fc4a0d32f6bf41db8132555278b507d8a1e35fa2 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 10 Jul 2026 18:03:10 +0530
Subject: [PATCH v1 2/2] Add a test for concurrent REFRESH SEQUENCES
Add a TAP test covering the case where 'ALTER SUBSCRIPTION ...
REFRESH SEQUENCES' is executed while a sequence synchronization is
already in progress.
The test uses an injection point to pause the sequence synchronization
worker immediately before it applies the fetched sequence value to the
subscriber. While the worker is paused, a second 'ALTER SUBSCRIPTION ...
REFRESH SEQUENCES' command is issued and verified to emit the expected
warning indicating that the sequence is already being synchronized.
This test exercises the race condition and verifies that concurrent
refresh requests are handled safely without resetting the synchronization
state of sequences that are already being processed.
---
.../replication/logical/sequencesync.c | 5 +
src/test/subscription/meson.build | 1 +
.../t/039_sequences_refresh_warning.pl | 127 ++++++++++++++++++
3 files changed, 133 insertions(+)
create mode 100644 src/test/subscription/t/039_sequences_refresh_warning.pl
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 770fa5de10b..631b9c4bf88 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -68,6 +68,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/injection_point.h"
#include "utils/pg_lsn.h"
#include "utils/syscache.h"
#include "utils/usercontext.h"
@@ -552,8 +553,12 @@ copy_sequences(WalReceiverConn *conn)
sync_status = get_and_validate_seq_info(slot, &sequence_rel,
&seqinfo, &seqidx);
if (sync_status == COPYSEQ_SUCCESS)
+ {
+ INJECTION_POINT("sequencesync-before-copy-sequence", NULL);
+
sync_status = copy_sequence(seqinfo,
sequence_rel->rd_rel->relowner);
+ }
switch (sync_status)
{
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index e71e95c6297..79f7e5a6405 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -48,6 +48,7 @@ tests += {
't/036_sequences.pl',
't/037_except.pl',
't/038_walsnd_shutdown_timeout.pl',
+ 't/039_sequences_refresh_warning.pl',
't/100_bugs.pl',
],
},
diff --git a/src/test/subscription/t/039_sequences_refresh_warning.pl b/src/test/subscription/t/039_sequences_refresh_warning.pl
new file mode 100644
index 00000000000..23508ef3780
--- /dev/null
+++ b/src/test/subscription/t/039_sequences_refresh_warning.pl
@@ -0,0 +1,127 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that ALTER SUBSCRIPTION ... REFRESH SEQUENCES emits a WARNING and
+# skips resetting a sequence that is already in INIT state, i.e. one whose
+# previous REFRESH SEQUENCES has not yet been picked up by the
+# sequencesync worker.
+#
+# This is exercised deterministically using an injection point placed just
+# before copy_sequence() is called in the sequencesync worker's main loop:
+#
+# 1. Run a first REFRESH SEQUENCES, which puts the sequence into INIT state
+# and lets the sequencesync worker start.
+# 2. The worker blocks at the injection point after fetching the sequence's
+# remote values but before applying them / marking the row READY.
+# 3. Run a second REFRESH SEQUENCES while the worker is still blocked. Since
+# the sequence is still in INIT state, the command must emit a WARNING
+# (with a HINT to retry later) and must not disturb the row.
+# 4. Release the injection point and let the worker finish; the sequence
+# should reach READY normally.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init;
+$node_subscriber->start;
+
+# This test depends on an injection point to block the sequencesync worker
+# right before it applies a sequence's fetched values.
+my $injection_points_supported =
+ $node_subscriber->check_extension('injection_points');
+
+if (!$injection_points_supported)
+{
+ plan skip_all => 'Server does not support injection points.';
+}
+
+$node_subscriber->append_conf('postgresql.conf',
+ "shared_preload_libraries = 'injection_points'");
+$node_subscriber->restart;
+
+$node_subscriber->safe_psql('postgres', "CREATE EXTENSION injection_points;");
+
+# Identical sequence definitions on both nodes.
+my $ddl = qq(CREATE SEQUENCE regress_seq1;);
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION regress_seq_pub FOR ALL SEQUENCES");
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub"
+);
+
+# Wait for the initial sync to complete before testing REFRESH SEQUENCES.
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_subscription_rel WHERE srsubstate <> 'r'")
+ or die "timed out waiting for initial sequence sync";
+
+# Attach the injection point that will block the sequencesync worker just
+# before it applies a sequence's fetched values.
+$node_subscriber->safe_psql('postgres',
+ "SELECT injection_points_attach('sequencesync-before-copy-sequence', 'wait');"
+);
+
+# First REFRESH SEQUENCES: puts the sequence into INIT and starts the
+# sequencesync worker.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+# Wait until the worker is blocked at the injection point, i.e. after
+# fetching the remote values but before applying them.
+$node_subscriber->wait_for_event('logical replication sequencesync worker',
+ 'sequencesync-before-copy-sequence');
+
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_seq1'::regclass"
+);
+is($result, 'i',
+ 'sequence is still in INIT state while the worker is blocked');
+
+# Second REFRESH SEQUENCES while the sequence is still in INIT: it must
+# not disturb the row, and must emit a WARNING with a HINT instead.
+my ($cmdret, $stdout, $stderr) = $node_subscriber->psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+like(
+ $stderr,
+ qr/WARNING:.*sequence "public.regress_seq1" of subscription "regress_seq_sub" is already being synchronized, skipping/,
+ 'second REFRESH SEQUENCES warns that the sequence is already being synchronized'
+);
+like(
+ $stderr,
+ qr/HINT:.*Rerun ALTER SUBSCRIPTION \.\.\. REFRESH SEQUENCES after the current synchronization completes\./,
+ 'second REFRESH SEQUENCES hints to retry later'
+);
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_seq1'::regclass"
+);
+is($result, 'i',
+ 'sequence remains in INIT state after the second REFRESH SEQUENCES');
+
+# Release the worker and let it finish normally.
+$node_subscriber->safe_psql('postgres',
+ "SELECT injection_points_wakeup('sequencesync-before-copy-sequence');
+ SELECT injection_points_detach('sequencesync-before-copy-sequence');"
+);
+
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_subscription_rel WHERE srsubstate <> 'r'")
+ or die "timed out waiting for sequence to reach READY state";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq1");
+is($result, '1|f', 'sequence reaches READY with expected values');
+
+done_testing();
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-11 05:01 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-11 20:14 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
6 siblings, 2 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-11 05:01 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
>
> A Fable 5 review of logical replication of sequences found a way to get
> subscribed sequences into READY state despite the subscriber side having data
> older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> I reviewed the test, and I think it identifies a genuine defect.
>
Good catch. We have following ways to fix: (a) As mentioned by
Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
sequencesync worker is in progress, we can either make the command
wait till the sequencesync is finished, return ERROR suggesting
sequence sync already in-progress, or first stop the sequencesync
worker and then complete the command and let the worker restart after
REFRESH command is finished; (b) raise a WARNING+HINT for sequences
that are not in ready state as proposed by Vignesh. Shall we
additionally add a Note for user to ensure seuencesync worker is not
in-progress before REFRESH SEQUENCES command?
Do you have any preference? I think WARNING+HINT should be sufficient
for users as this shouldn't be a common scenario but going the other
way is also fine.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-11 07:15 ` vignesh C <vignesh21@gmail.com>
2026-07-13 04:05 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
1 sibling, 2 replies; 82+ messages in thread
From: vignesh C @ 2026-07-11 07:15 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; pgsql-hackers
On Sat, 11 Jul 2026 at 10:31, Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> >
> > A Fable 5 review of logical replication of sequences found a way to get
> > subscribed sequences into READY state despite the subscriber side having data
> > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > I reviewed the test, and I think it identifies a genuine defect.
> >
>
> Good catch. We have following ways to fix: (a) As mentioned by
> Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> sequencesync worker is in progress, we can either make the command
> wait till the sequencesync is finished, return ERROR suggesting
> sequence sync already in-progress, or first stop the sequencesync
> worker and then complete the command and let the worker restart after
> REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> that are not in ready state as proposed by Vignesh. Shall we
> additionally add a Note for user to ensure seuencesync worker is not
> in-progress before REFRESH SEQUENCES command?
>
> Do you have any preference? I think WARNING+HINT should be sufficient
> for users as this shouldn't be a common scenario but going the other
> way is also fine.
Both approaches seem reasonable to me. One downside of the WARNING
approach is that if a subscription contains many sequences and the
user immediately reruns ALTER SUBSCRIPTION ... REFRESH SEQUENCES, they
could receive a large number of warnings one for each sequence that is
already being synchronized which may be noisy and not particularly
useful.
Here is a patch implementing approach (a), which detects whether a
sequence synchronization worker is already running for the
subscription. If a synchronization is already in progress, ALTER
SUBSCRIPTION ... REFRESH SEQUENCES reports an error and asks the user
to rerun the command after the current synchronization completes.
Regards,
Vignesh
Attachments:
[application/octet-stream] 0001-Reject-concurrent-sequence-refreshes.patch (3.1K, ../../CALDaNm3ROpNN_MPnejzMNb7aVj9=LAehvyuQS+WRH4cwMO6hoA@mail.gmail.com/2-0001-Reject-concurrent-sequence-refreshes.patch)
download | inline diff:
From df748108d10f3435410a524fc12ad6c7d06b4659 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sat, 11 Jul 2026 12:02:17 +0530
Subject: [PATCH 1/2] Reject concurrent sequence refreshes
`ALTER SUBSCRIPTION ... REFRESH SEQUENCES` can race with an already
running sequence synchronization worker. If a second refresh request
resets the synchronization state while the worker has already fetched
sequence values from the publisher but has not yet applied them to the
subscriber, the worker can overwrite the subscriber with stale values
and mark the synchronization as complete.
Avoid this race by rejecting `ALTER SUBSCRIPTION ... REFRESH SEQUENCES`
when a sequence synchronization worker is already running for the
subscription. The command reports an error asking the user to rerun it
after the current synchronization completes.
---
src/backend/commands/subscriptioncmds.c | 23 +++++++++++++++++++++++
src/test/subscription/t/036_sequences.pl | 4 ++++
2 files changed, 27 insertions(+)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..1a1c550cc14 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1363,6 +1363,29 @@ AlterSubscription_refresh_seq(Subscription *sub)
WalReceiverConn *wrconn;
bool must_use_password;
+ /*
+ * Disallow a concurrent REFRESH SEQUENCES while a sequencesync worker
+ * for this subscription is still synchronizing the sequences from a
+ * previous REFRESH SEQUENCES. Without this check, this command would
+ * reset the sequences' state back to INIT while the running worker is
+ * midway through applying the values it already fetched from the
+ * publisher, which could leave the sequences marked READY with stale
+ * data.
+ */
+ LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+ if (logicalrep_worker_find(WORKERTYPE_SEQUENCESYNC, sub->oid, InvalidOid,
+ true))
+ {
+ LWLockRelease(LogicalRepWorkerLock);
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot execute %s while a sequence synchronization worker for subscription \"%s\" is still running",
+ "ALTER SUBSCRIPTION ... REFRESH SEQUENCES",
+ sub->name),
+ errhint("Try again after the current synchronization completes."));
+ }
+ LWLockRelease(LogicalRepWorkerLock);
+
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 2a0819aaf01..8b02b24a7e9 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -232,6 +232,10 @@ $node_publisher->safe_psql(
CREATE SEQUENCE regress_s4 START 10 INCREMENT 2;
));
+# Wait for the missing sequence added to be synced
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
##########
# Ensure that insufficient privileges on the publisher for a sequence
# are reported correctly as a permission issue, not as a missing sequence.
--
2.50.1 (Apple Git-155)
[application/octet-stream] 0002-Add-a-test-for-concurrent-REFRESH-SEQUENCES.patch (7.9K, ../../CALDaNm3ROpNN_MPnejzMNb7aVj9=LAehvyuQS+WRH4cwMO6hoA@mail.gmail.com/3-0002-Add-a-test-for-concurrent-REFRESH-SEQUENCES.patch)
download | inline diff:
From b7ce39fc7248b39bd482ed610928658e59a3a1d4 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 10 Jul 2026 18:03:10 +0530
Subject: [PATCH 2/2] Add a test for concurrent REFRESH SEQUENCES
Add a TAP test covering the case where 'ALTER SUBSCRIPTION ...
REFRESH SEQUENCES' is executed while a sequence synchronization is
already in progress.
The test uses an injection point to pause the sequence synchronization
worker immediately before it applies the fetched sequence value to the
subscriber. While the worker is paused, a second 'ALTER SUBSCRIPTION ...
REFRESH SEQUENCES' command is issued and verified to thrown an error
indicating that the sequence is already being synchronized.
This test exercises the race condition and verifies that concurrent
refresh requests are handled safely without resetting the synchronization
state of sequences that are already being processed.
---
.../replication/logical/sequencesync.c | 5 +
src/test/subscription/meson.build | 1 +
.../t/039_sequences_refresh_error.pl | 129 ++++++++++++++++++
3 files changed, 135 insertions(+)
create mode 100644 src/test/subscription/t/039_sequences_refresh_error.pl
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 770fa5de10b..631b9c4bf88 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -68,6 +68,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/injection_point.h"
#include "utils/pg_lsn.h"
#include "utils/syscache.h"
#include "utils/usercontext.h"
@@ -552,8 +553,12 @@ copy_sequences(WalReceiverConn *conn)
sync_status = get_and_validate_seq_info(slot, &sequence_rel,
&seqinfo, &seqidx);
if (sync_status == COPYSEQ_SUCCESS)
+ {
+ INJECTION_POINT("sequencesync-before-copy-sequence", NULL);
+
sync_status = copy_sequence(seqinfo,
sequence_rel->rd_rel->relowner);
+ }
switch (sync_status)
{
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index e71e95c6297..42a6706e531 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -48,6 +48,7 @@ tests += {
't/036_sequences.pl',
't/037_except.pl',
't/038_walsnd_shutdown_timeout.pl',
+ 't/039_sequences_refresh_error.pl',
't/100_bugs.pl',
],
},
diff --git a/src/test/subscription/t/039_sequences_refresh_error.pl b/src/test/subscription/t/039_sequences_refresh_error.pl
new file mode 100644
index 00000000000..21011786f93
--- /dev/null
+++ b/src/test/subscription/t/039_sequences_refresh_error.pl
@@ -0,0 +1,129 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that ALTER SUBSCRIPTION ... REFRESH SEQUENCES emits a WARNING and
+# skips resetting a sequence that is already in INIT state, i.e. one whose
+# previous REFRESH SEQUENCES has not yet been picked up by the
+# sequencesync worker.
+#
+# This is exercised deterministically using an injection point placed just
+# before copy_sequence() is called in the sequencesync worker's main loop:
+#
+# 1. Run a first REFRESH SEQUENCES, which puts the sequence into INIT state
+# and lets the sequencesync worker start.
+# 2. The worker blocks at the injection point after fetching the sequence's
+# remote values but before applying them / marking the row READY.
+# 3. Run a second REFRESH SEQUENCES while the worker is still blocked. Since
+# the sequence is still in INIT state, the command must emit a WARNING
+# (with a HINT to retry later) and must not disturb the row.
+# 4. Release the injection point and let the worker finish; the sequence
+# should reach READY normally.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init;
+$node_subscriber->start;
+
+# This test depends on an injection point to block the sequencesync worker
+# right before it applies a sequence's fetched values.
+my $injection_points_supported =
+ $node_subscriber->check_extension('injection_points');
+
+if (!$injection_points_supported)
+{
+ plan skip_all => 'Server does not support injection points.';
+}
+
+$node_subscriber->append_conf('postgresql.conf',
+ "shared_preload_libraries = 'injection_points'");
+$node_subscriber->restart;
+
+$node_subscriber->safe_psql('postgres', "CREATE EXTENSION injection_points;");
+
+# Identical sequence definitions on both nodes.
+my $ddl = qq(CREATE SEQUENCE regress_seq1;);
+$node_publisher->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION regress_seq_pub FOR ALL SEQUENCES");
+
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub"
+);
+
+# Wait for the initial sync to complete before testing REFRESH SEQUENCES.
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_subscription_rel WHERE srsubstate <> 'r'")
+ or die "timed out waiting for initial sequence sync";
+
+# Attach the injection point that will block the sequencesync worker just
+# before it applies a sequence's fetched values.
+$node_subscriber->safe_psql('postgres',
+ "SELECT injection_points_attach('sequencesync-before-copy-sequence', 'wait');"
+);
+
+# First REFRESH SEQUENCES: puts the sequence into INIT and starts the
+# sequencesync worker.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+# Wait until the worker is blocked at the injection point, i.e. after
+# fetching the remote values but before applying them.
+$node_subscriber->wait_for_event('logical replication sequencesync worker',
+ 'sequencesync-before-copy-sequence');
+
+my $result = $node_subscriber->safe_psql('postgres',
+ "SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_seq1'::regclass"
+);
+is($result, 'i',
+ 'sequence is still in INIT state while the worker is blocked');
+
+# Second REFRESH SEQUENCES while the sequence is still in INIT: it must
+# not disturb the row, and must emit a WARNING with a HINT instead.
+my ($cmdret, $stdout, $stderr) = $node_subscriber->psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+isnt($cmdret, 0,
+ 'second REFRESH SEQUENCES fails while a sequencesync worker is running'
+);
+like(
+ $stderr,
+ qr/ERROR:.*cannot execute ALTER SUBSCRIPTION \.\.\. REFRESH SEQUENCES while a sequence synchronization worker for subscription "regress_seq_sub" is still running/,
+ 'second REFRESH SEQUENCES reports the sequencesync worker is still running'
+);
+like(
+ $stderr,
+ qr/HINT:.*Try again after the current synchronization completes\./,
+ 'second REFRESH SEQUENCES hints to retry later');
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_seq1'::regclass"
+);
+is($result, 'i',
+ 'sequence remains in INIT state after the second REFRESH SEQUENCES');
+
+# Release the worker and let it finish normally.
+$node_subscriber->safe_psql('postgres',
+ "SELECT injection_points_wakeup('sequencesync-before-copy-sequence');
+ SELECT injection_points_detach('sequencesync-before-copy-sequence');"
+);
+
+$node_subscriber->poll_query_until('postgres',
+ "SELECT count(*) = 0 FROM pg_subscription_rel WHERE srsubstate <> 'r'")
+ or die "timed out waiting for sequence to reach READY state";
+
+$result = $node_subscriber->safe_psql('postgres',
+ "SELECT last_value, is_called FROM regress_seq1");
+is($result, '1|f', 'sequence reaches READY with expected values');
+
+done_testing();
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-13 04:05 ` shveta malik <shveta.malik@gmail.com>
2026-07-13 04:43 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-13 04:05 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
' every
On Sat, Jul 11, 2026 at 12:46 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Sat, 11 Jul 2026 at 10:31, Amit Kapila <amit.kapila16@gmail.com> wrote:
> >
> > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > >
> > > A Fable 5 review of logical replication of sequences found a way to get
> > > subscribed sequences into READY state despite the subscriber side having data
> > > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > > I reviewed the test, and I think it identifies a genuine defect.
> > >
> >
> > Good catch. We have following ways to fix: (a) As mentioned by
> > Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> > sequencesync worker is in progress, we can either make the command
> > wait till the sequencesync is finished, return ERROR suggesting
> > sequence sync already in-progress, or first stop the sequencesync
> > worker and then complete the command and let the worker restart after
> > REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> > that are not in ready state as proposed by Vignesh. Shall we
> > additionally add a Note for user to ensure seuencesync worker is not
> > in-progress before REFRESH SEQUENCES command?
> >
> > Do you have any preference? I think WARNING+HINT should be sufficient
> > for users as this shouldn't be a common scenario but going the other
> > way is also fine.
>
> Both approaches seem reasonable to me. One downside of the WARNING
> approach is that if a subscription contains many sequences and the
> user immediately reruns ALTER SUBSCRIPTION ... REFRESH SEQUENCES, they
> could receive a large number of warnings one for each sequence that is
> already being synchronized which may be noisy and not particularly
> useful.
Right.
> Here is a patch implementing approach (a), which detects whether a
> sequence synchronization worker is already running for the
> subscription. If a synchronization is already in progress, ALTER
> SUBSCRIPTION ... REFRESH SEQUENCES reports an error and asks the user
> to rerun the command after the current synchronization completes.
>
As I suggested earlier, I think this approach is preferable.
One potential issue I anticipate is the following: if sequence
synchronization repeatedly fails due to an error that the user is not
concerned about (for example, some sequences are missing on the
publisher), the worker may keep getting started and exiting every few
seconds. If the user happens to retry ALTER SUBSCRIPTION ... REFRESH
SEQUENCES while those worker retries are in progress, they may
repeatedly fail with the new error because the worker keeps getting
restarted. Thus, the user may not easily get the sequences that matter
to sync again. It's difficult to predict how likely this scenario is,
but if the overall situation itself is rare, I think the current
suggested solution should be acceptable.
Another potential solution, slightly more complex than the previous
one, but which could completely avoid such race scenarios is by
introducing a new DATASYNC state. Before starting sequence
synchronization, the worker would move the affected sequences to
DATASYNC. If ALTER SUBSCRIPTION ... REFRESH SEQUENCES is executed
concurrently, it would reset their state back to INIT. Before marking
a sequence as READY, the worker would recheck its current state; if it
finds that the state has been reset to INIT, it would simply skip
synchronizing that sequence. Since the sequence remains in the INIT
state, it will automatically be picked up during the next
synchronization cycle. This avoids applying stale sequence values
while ensuring that fresh values are fetched from the publisher during
the subsequent synchronization attempt.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 04:05 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-13 04:43 ` Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-13 04:43 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Mon, Jul 13, 2026 at 9:35 AM shveta malik <shveta.malik@gmail.com> wrote:
>
> On Sat, Jul 11, 2026 at 12:46 PM vignesh C <vignesh21@gmail.com> wrote:
> >
>
> > Here is a patch implementing approach (a), which detects whether a
> > sequence synchronization worker is already running for the
> > subscription. If a synchronization is already in progress, ALTER
> > SUBSCRIPTION ... REFRESH SEQUENCES reports an error and asks the user
> > to rerun the command after the current synchronization completes.
> >
>
> As I suggested earlier, I think this approach is preferable.
>
> One potential issue I anticipate is the following: if sequence
> synchronization repeatedly fails due to an error that the user is not
> concerned about (for example, some sequences are missing on the
> publisher), the worker may keep getting started and exiting every few
> seconds. If the user happens to retry ALTER SUBSCRIPTION ... REFRESH
> SEQUENCES while those worker retries are in progress, they may
> repeatedly fail with the new error because the worker keeps getting
> restarted. Thus, the user may not easily get the sequences that matter
> to sync again. It's difficult to predict how likely this scenario is,
> but if the overall situation itself is rare, I think the current
> suggested solution should be acceptable.
>
Yeah, I think this is no different than other ERRORs, one can get
during apply like a constraint violation. The way to break the
repeated restart loop is that users need to figure out that by
checking LOGs or may be conflict related information if we ever detect
this as a conflict. So, I agree that even in this situation giving
ERROR on REFRESH SEQUENCES is reasonable.
> Another potential solution, slightly more complex than the previous
> one, but which could completely avoid such race scenarios is by
> introducing a new DATASYNC state. Before starting sequence
> synchronization, the worker would move the affected sequences to
> DATASYNC. If ALTER SUBSCRIPTION ... REFRESH SEQUENCES is executed
> concurrently, it would reset their state back to INIT. Before marking
> a sequence as READY, the worker would recheck its current state; if it
> finds that the state has been reset to INIT, it would simply skip
> synchronizing that sequence. Since the sequence remains in the INIT
> state, it will automatically be picked up during the next
> synchronization cycle. This avoids applying stale sequence values
> while ensuring that fresh values are fetched from the publisher during
> the subsequent synchronization attempt.
>
Hmm, yeah, this is another version (with a bit of additional
complexity) of auto sync as discussed in my response. I think if we
later decide to go in this direction, we may want to achieve it via
some generation_number kind of concept where the command will indicate
somewhere in shared memory that one sync cycle is required.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-13 05:37 ` shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-13 05:37 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Sat, Jul 11, 2026 at 12:46 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Sat, 11 Jul 2026 at 10:31, Amit Kapila <amit.kapila16@gmail.com> wrote:
> >
> > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > >
> > > A Fable 5 review of logical replication of sequences found a way to get
> > > subscribed sequences into READY state despite the subscriber side having data
> > > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > > I reviewed the test, and I think it identifies a genuine defect.
> > >
> >
> > Good catch. We have following ways to fix: (a) As mentioned by
> > Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> > sequencesync worker is in progress, we can either make the command
> > wait till the sequencesync is finished, return ERROR suggesting
> > sequence sync already in-progress, or first stop the sequencesync
> > worker and then complete the command and let the worker restart after
> > REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> > that are not in ready state as proposed by Vignesh. Shall we
> > additionally add a Note for user to ensure seuencesync worker is not
> > in-progress before REFRESH SEQUENCES command?
> >
> > Do you have any preference? I think WARNING+HINT should be sufficient
> > for users as this shouldn't be a common scenario but going the other
> > way is also fine.
>
> Both approaches seem reasonable to me. One downside of the WARNING
> approach is that if a subscription contains many sequences and the
> user immediately reruns ALTER SUBSCRIPTION ... REFRESH SEQUENCES, they
> could receive a large number of warnings one for each sequence that is
> already being synchronized which may be noisy and not particularly
> useful.
>
> Here is a patch implementing approach (a), which detects whether a
> sequence synchronization worker is already running for the
> subscription. If a synchronization is already in progress, ALTER
> SUBSCRIPTION ... REFRESH SEQUENCES reports an error and asks the user
> to rerun the command after the current synchronization completes.
>
Please find my comments:
1)
+ /*
+ * Disallow a concurrent REFRESH SEQUENCES while a sequencesync worker
+ * for this subscription is still synchronizing the sequences from a
+ * previous REFRESH SEQUENCES. Without this check, this command would
+ * reset the sequences' state back to INIT while the running worker is
+ * midway through applying the values it already fetched from the
+ * publisher, which could leave the sequences marked READY with stale
+ * data.
+ */
The wording "reset the sequences' state back to INIT" is incorrect.
There will be no issue if REFRESH resets the state back to INIT (from
READY) as those will then be picked up in the next cycle. The problem
is that there is no reset haappening. I think we can improve this
comment.
Suggestion:
/*
* Disallow a concurrent REFRESH SEQUENCES while a sequence sync worker
* for this subscription is still running. This avoids a race where the
* publisher's sequence advances after the current worker has fetched its
* value but before it marks the sequence READY. A user may then issue
* another REFRESH SEQUENCES to synchronize the updated value. Since the
* affected sequences are already in the INIT state, the running worker
* has no indication that a new synchronization has been requested. It
* would then apply the stale value it already fetched and mark the
* sequence READY, causing the new synchronization request to be lost and
* preventing the updated publisher values from being synchronized.
*/
2)
I am unsure if a testcase is really needed here as it is a very simple
fix. But I'd like to see what others think here.
If a test is needed, I can review it, currently I have skipped it.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-13 11:08 ` vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 03:24 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 04:54 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 3 replies; 82+ messages in thread
From: vignesh C @ 2026-07-13 11:08 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Mon, 13 Jul 2026 at 11:08, shveta malik <shveta.malik@gmail.com> wrote:
>
> On Sat, Jul 11, 2026 at 12:46 PM vignesh C <vignesh21@gmail.com> wrote:
> >
> > On Sat, 11 Jul 2026 at 10:31, Amit Kapila <amit.kapila16@gmail.com> wrote:
> > >
> > > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > > >
> > > > A Fable 5 review of logical replication of sequences found a way to get
> > > > subscribed sequences into READY state despite the subscriber side having data
> > > > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > > > I reviewed the test, and I think it identifies a genuine defect.
> > > >
> > >
> > > Good catch. We have following ways to fix: (a) As mentioned by
> > > Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> > > sequencesync worker is in progress, we can either make the command
> > > wait till the sequencesync is finished, return ERROR suggesting
> > > sequence sync already in-progress, or first stop the sequencesync
> > > worker and then complete the command and let the worker restart after
> > > REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> > > that are not in ready state as proposed by Vignesh. Shall we
> > > additionally add a Note for user to ensure seuencesync worker is not
> > > in-progress before REFRESH SEQUENCES command?
> > >
> > > Do you have any preference? I think WARNING+HINT should be sufficient
> > > for users as this shouldn't be a common scenario but going the other
> > > way is also fine.
> >
> > Both approaches seem reasonable to me. One downside of the WARNING
> > approach is that if a subscription contains many sequences and the
> > user immediately reruns ALTER SUBSCRIPTION ... REFRESH SEQUENCES, they
> > could receive a large number of warnings one for each sequence that is
> > already being synchronized which may be noisy and not particularly
> > useful.
> >
> > Here is a patch implementing approach (a), which detects whether a
> > sequence synchronization worker is already running for the
> > subscription. If a synchronization is already in progress, ALTER
> > SUBSCRIPTION ... REFRESH SEQUENCES reports an error and asks the user
> > to rerun the command after the current synchronization completes.
> >
>
> Please find my comments:
>
> 1)
> + /*
> + * Disallow a concurrent REFRESH SEQUENCES while a sequencesync worker
> + * for this subscription is still synchronizing the sequences from a
> + * previous REFRESH SEQUENCES. Without this check, this command would
> + * reset the sequences' state back to INIT while the running worker is
> + * midway through applying the values it already fetched from the
> + * publisher, which could leave the sequences marked READY with stale
> + * data.
> + */
>
> The wording "reset the sequences' state back to INIT" is incorrect.
> There will be no issue if REFRESH resets the state back to INIT (from
> READY) as those will then be picked up in the next cycle. The problem
> is that there is no reset haappening. I think we can improve this
> comment.
>
> Suggestion:
>
> /*
> * Disallow a concurrent REFRESH SEQUENCES while a sequence sync worker
> * for this subscription is still running. This avoids a race where the
> * publisher's sequence advances after the current worker has fetched its
> * value but before it marks the sequence READY. A user may then issue
> * another REFRESH SEQUENCES to synchronize the updated value. Since the
> * affected sequences are already in the INIT state, the running worker
> * has no indication that a new synchronization has been requested. It
> * would then apply the stale value it already fetched and mark the
> * sequence READY, causing the new synchronization request to be lost and
> * preventing the updated publisher values from being synchronized.
> */
Modified
> 2)
> I am unsure if a testcase is really needed here as it is a very simple
> fix. But I'd like to see what others think here.
> If a test is needed, I can review it, currently I have skipped it.
Even I feel a test case is not needed for this.
The attached v2 version patch has the changes for the same.
In addition, it includes a fix for Finding 2
(default_transaction_read_only) reported by Noah at [1], following the
approach suggested by Amit at [2].
This version also addresses Findings 6 and 17 by reporting an error
when ALTER SUBSCRIPTION ... REFRESH SEQUENCES is executed against
subscriptions created prior to PostgreSQL 19, and documents this
behavior accordingly.
[1] - https://www.postgresql.org/message-id/20260710045217.f0.noahmisch%40microsoft.com
[2] - https://www.postgresql.org/message-id/CAA4eK1K8LD243UzHgVNCm4skJZ4UCjR3vowDhKp%3DcWnK5oBT-Q%40mail.g...
Regards,
Vignesh
Attachments:
[application/octet-stream] v2-0002-Reject-concurrent-sequence-refreshes.patch (4.2K, ../../CALDaNm3QMQAk-ndJ_t9MCHDtUjh8jL6Jrxcz_Ui6K++2u=wM-w@mail.gmail.com/2-v2-0002-Reject-concurrent-sequence-refreshes.patch)
download | inline diff:
From 0f3b0256d7cf09d318f12465fe521bb4918a531f Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sat, 11 Jul 2026 12:02:17 +0530
Subject: [PATCH v2 2/3] Reject concurrent sequence refreshes
`ALTER SUBSCRIPTION ... REFRESH SEQUENCES` can race with an already
running sequence synchronization worker. If a second refresh request
resets the synchronization state while the worker has already fetched
sequence values from the publisher but has not yet applied them to the
subscriber, the worker can overwrite the subscriber with stale values
and mark the synchronization as complete.
Avoid this race by rejecting `ALTER SUBSCRIPTION ... REFRESH SEQUENCES`
when a sequence synchronization worker is already running for the
subscription. The command reports an error asking the user to rerun it
after the current synchronization completes.
---
src/backend/commands/subscriptioncmds.c | 34 +++++++++++++++++++++---
src/test/subscription/t/036_sequences.pl | 4 +++
2 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index e2a1abfb103..ea8950c70db 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1363,6 +1363,32 @@ AlterSubscription_refresh_seq(Subscription *sub)
WalReceiverConn *wrconn;
bool must_use_password;
+ /*
+ * Disallow a concurrent REFRESH SEQUENCES while a sequence sync worker
+ * for this subscription is still running. This avoids a race where the
+ * publisher's sequence advances after the current worker has fetched its
+ * value but before it marks the sequence READY. A user may then issue
+ * another REFRESH SEQUENCES to synchronize the updated value. Since the
+ * affected sequences are already in the INIT state, the running worker
+ * has no indication that a new synchronization has been requested. It
+ * would then apply the stale value it already fetched and mark the
+ * sequence READY, causing the new synchronization request to be lost and
+ * preventing the updated publisher values from being synchronized.
+ */
+ LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+ if (logicalrep_worker_find(WORKERTYPE_SEQUENCESYNC, sub->oid, InvalidOid,
+ true))
+ {
+ LWLockRelease(LogicalRepWorkerLock);
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot execute %s while a sequence synchronization worker for subscription \"%s\" is still running",
+ "ALTER SUBSCRIPTION ... REFRESH SEQUENCES",
+ sub->name),
+ errhint("Try again after the current synchronization completes."));
+ }
+ LWLockRelease(LogicalRepWorkerLock);
+
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
@@ -1381,10 +1407,10 @@ AlterSubscription_refresh_seq(Subscription *sub)
List *subrel_states;
/*
- * Sequence synchronization relies on pg_get_sequence_data(), which
- * is only available since PostgreSQL 19. Fail immediately with a
- * clear diagnosis instead of resetting the sequences to INIT state
- * and letting a sequencesync worker repeatedly fail trying to fetch
+ * Sequence synchronization relies on pg_get_sequence_data(), which is
+ * only available since PostgreSQL 19. Fail immediately with a clear
+ * diagnosis instead of resetting the sequences to INIT state and
+ * letting a sequencesync worker repeatedly fail trying to fetch
* sequence data with a query that can never succeed against this
* publisher.
*/
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 2a0819aaf01..8b02b24a7e9 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -232,6 +232,10 @@ $node_publisher->safe_psql(
CREATE SEQUENCE regress_s4 START 10 INCREMENT 2;
));
+# Wait for the missing sequence added to be synced
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
##########
# Ensure that insufficient privileges on the publisher for a sequence
# are reported correctly as a permission issue, not as a missing sequence.
--
2.50.1 (Apple Git-155)
[application/octet-stream] v2-0001-Reject-sequence-synchronization-against-pre-PG19-.patch (5.9K, ../../CALDaNm3QMQAk-ndJ_t9MCHDtUjh8jL6Jrxcz_Ui6K++2u=wM-w@mail.gmail.com/3-v2-0001-Reject-sequence-synchronization-against-pre-PG19-.patch)
download | inline diff:
From 0753ae11f1fe2f9f5a1529ceeb74c071f47ea670 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 13 Jul 2026 14:49:20 +0530
Subject: [PATCH v2 1/3] Reject sequence synchronization against pre-PG19
publishers
Sequence synchronization relies on pg_get_sequence_data(), which was
added in PostgreSQL 19. Previously, requesting sequence sync against
an older publisher (via CREATE SUBSCRIPTION, ALTER SUBSCRIPTION ...
REFRESH SEQUENCES, or a sequencesync worker) would either reset the
affected sequences to INIT state and have the worker 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 with a clear diagnosis 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.
---
doc/src/sgml/logical-replication.sgml | 18 +++++++++++++++++-
doc/src/sgml/ref/alter_subscription.sgml | 6 ++++++
src/backend/commands/subscriptioncmds.c | 14 ++++++++++++++
src/backend/replication/logical/sequencesync.c | 13 +++++++++++++
4 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 690598bff98..274ba8b447a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1772,6 +1772,13 @@ Included in publications:
<sect1 id="logical-replication-sequences">
<title>Replicating Sequences</title>
+ <note>
+ <para>
+ Sequence synchronization requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
+
<para>
To synchronize sequences from a publisher to a subscriber, first publish
them using <link linkend="sql-createpublication-params-for-all-sequences">
@@ -2368,7 +2375,16 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
<command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link>
or by copying the current data from the publisher (perhaps using
<command>pg_dump</command>) or by determining a sufficiently high value
- from the tables themselves.
+ from the tables themselves. Note that
+ <link linkend="sql-altersubscription-params-refresh-sequences">
+ <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+ re-synchronizes sequences that are already known to the subscription
+ (see <xref linkend="logical-replication-sequences"/>); in particular, it
+ requires the publisher to be running <productname>PostgreSQL</productname>
+ 19 or later. Before relying on it to prepare for a switchover or
+ failover, confirm that the publisher's version supports sequence
+ replication and that the sequences of interest are already known to the
+ subscription.
</para>
</listitem>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8d64744375a..6fc3e07a2d5 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -245,6 +245,12 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
sequences are subscribed. Run <literal>REFRESH PUBLICATION</literal>
first if the publication's set of sequences has changed.
</para>
+ <note>
+ <para>
+ Sequence replication requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
<para>
See <xref linkend="sequence-definition-mismatches"/> for
recommendations on how to handle any warnings about sequence definition
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..e2a1abfb103 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1380,6 +1380,20 @@ AlterSubscription_refresh_seq(Subscription *sub)
{
List *subrel_states;
+ /*
+ * Sequence synchronization relies on pg_get_sequence_data(), which
+ * is only available since PostgreSQL 19. Fail immediately with a
+ * clear diagnosis instead of resetting the sequences to INIT state
+ * and letting a sequencesync worker repeatedly fail trying to fetch
+ * sequence data with a query that can never succeed against this
+ * publisher.
+ */
+ if (walrcv_server_version(wrconn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences for subscription \"%s\" because the publisher is running a version earlier than PostgreSQL 19",
+ sub->name));
+
check_publications_origin_sequences(wrconn, sub->publications, true,
sub->origin, NULL, 0, sub->name);
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 770fa5de10b..98a21b34ed7 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -435,6 +435,19 @@ copy_sequences(WalReceiverConn *conn)
StringInfoData cmd;
MemoryContext oldctx;
+ /*
+ * Sequence synchronization relies on pg_get_sequence_data(), which is
+ * only available since PostgreSQL 19. Fail with a clear diagnosis
+ * instead of sending a query that the publisher cannot execute (or
+ * cannot even parse), which would otherwise make this worker exit with
+ * a confusing "invalid query response" error.
+ */
+ if (walrcv_server_version(conn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot synchronize sequences for subscription \"%s\" because the publisher is running a version earlier than PostgreSQL 19",
+ MySubscription->name));
+
initStringInfo(&seqstr);
initStringInfo(&cmd);
--
2.50.1 (Apple Git-155)
[application/octet-stream] v2-0003-Allow-logical-replication-workers-to-ignore-defau.patch (4.0K, ../../CALDaNm3QMQAk-ndJ_t9MCHDtUjh8jL6Jrxcz_Ui6K++2u=wM-w@mail.gmail.com/4-v2-0003-Allow-logical-replication-workers-to-ignore-defau.patch)
download | inline diff:
From 40b192716cbbfac67363f44329860a8cec85b598 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 13 Jul 2026 16:17:39 +0530
Subject: [PATCH v2 3/3] Allow logical replication workers to ignore
default_transaction_read_only
Sequence synchronization updates sequence state via setval()/nextval(),
both of which explicitly call PreventCommandIfReadOnly(). If
default_transaction_read_only is enabled on the subscriber, this
causes sequencesync workers to fail with "cannot execute nextval()
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.
---
src/backend/replication/logical/worker.c | 8 ++++
src/test/subscription/t/036_sequences.pl | 51 ++++++++++++++++++++++++
2 files changed, 59 insertions(+)
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7799266c614..df9c2e76d09 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5809,6 +5809,14 @@ InitializeLogRepWorker(void)
*/
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+ /*
+ * Ignore default_transaction_read_only for logical replication workers,
+ * as they need to be able to modify subscriber-side state (e.g. apply
+ * changes, update sequences) regardless of that setting.
+ */
+ SetConfigOption("default_transaction_read_only", "off", PGC_SUSET,
+ PGC_S_OVERRIDE);
+
ApplyContext = AllocSetContextCreate(TopMemoryContext,
"ApplyContext",
ALLOCSET_DEFAULT_SIZES);
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 8b02b24a7e9..b72860add7f 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -188,6 +188,57 @@ is($result, '1|f',
'REFRESH PUBLICATION will not sync newly published sequence with copy_data as false'
);
+##########
+# Ensure that ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update
+# sequence values and mark the sequence as ready even when
+# default_transaction_read_only is enabled on the subscriber.
+##########
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = on;
+ SELECT pg_reload_conf();
+));
+
+# Update the existing sequence 'regress_s3' on the publisher
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,100);
+));
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ set default_transaction_read_only = off;
+ ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check - sequence value is updated despite default_transaction_read_only
+# being enabled on the subscriber
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT last_value, is_called FROM regress_s3;
+));
+is($result, '200|t',
+ 'REFRESH SEQUENCES updates sequence value with default_transaction_read_only enabled'
+);
+
+# Check - sequence is marked as ready ('r')
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass;
+));
+is($result, 'r',
+ 'sequence is marked as ready after REFRESH SEQUENCES with default_transaction_read_only enabled'
+);
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = off;
+ SELECT pg_reload_conf();
+));
+
##########
# ALTER SUBSCRIPTION ... REFRESH PUBLICATION should report an error when:
# a) sequence definitions differ between the publisher and subscriber, or
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-13 12:15 ` shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2 siblings, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-13 12:15 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Mon, Jul 13, 2026 at 4:38 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Mon, 13 Jul 2026 at 11:08, shveta malik <shveta.malik@gmail.com> wrote:
> >
> > On Sat, Jul 11, 2026 at 12:46 PM vignesh C <vignesh21@gmail.com> wrote:
> > >
> > > On Sat, 11 Jul 2026 at 10:31, Amit Kapila <amit.kapila16@gmail.com> wrote:
> > > >
> > > > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > > > >
> > > > > A Fable 5 review of logical replication of sequences found a way to get
> > > > > subscribed sequences into READY state despite the subscriber side having data
> > > > > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > > > > I reviewed the test, and I think it identifies a genuine defect.
> > > > >
> > > >
> > > > Good catch. We have following ways to fix: (a) As mentioned by
> > > > Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> > > > sequencesync worker is in progress, we can either make the command
> > > > wait till the sequencesync is finished, return ERROR suggesting
> > > > sequence sync already in-progress, or first stop the sequencesync
> > > > worker and then complete the command and let the worker restart after
> > > > REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> > > > that are not in ready state as proposed by Vignesh. Shall we
> > > > additionally add a Note for user to ensure seuencesync worker is not
> > > > in-progress before REFRESH SEQUENCES command?
> > > >
> > > > Do you have any preference? I think WARNING+HINT should be sufficient
> > > > for users as this shouldn't be a common scenario but going the other
> > > > way is also fine.
> > >
> > > Both approaches seem reasonable to me. One downside of the WARNING
> > > approach is that if a subscription contains many sequences and the
> > > user immediately reruns ALTER SUBSCRIPTION ... REFRESH SEQUENCES, they
> > > could receive a large number of warnings one for each sequence that is
> > > already being synchronized which may be noisy and not particularly
> > > useful.
> > >
> > > Here is a patch implementing approach (a), which detects whether a
> > > sequence synchronization worker is already running for the
> > > subscription. If a synchronization is already in progress, ALTER
> > > SUBSCRIPTION ... REFRESH SEQUENCES reports an error and asks the user
> > > to rerun the command after the current synchronization completes.
> > >
> >
> > Please find my comments:
> >
> > 1)
> > + /*
> > + * Disallow a concurrent REFRESH SEQUENCES while a sequencesync worker
> > + * for this subscription is still synchronizing the sequences from a
> > + * previous REFRESH SEQUENCES. Without this check, this command would
> > + * reset the sequences' state back to INIT while the running worker is
> > + * midway through applying the values it already fetched from the
> > + * publisher, which could leave the sequences marked READY with stale
> > + * data.
> > + */
> >
> > The wording "reset the sequences' state back to INIT" is incorrect.
> > There will be no issue if REFRESH resets the state back to INIT (from
> > READY) as those will then be picked up in the next cycle. The problem
> > is that there is no reset haappening. I think we can improve this
> > comment.
> >
> > Suggestion:
> >
> > /*
> > * Disallow a concurrent REFRESH SEQUENCES while a sequence sync worker
> > * for this subscription is still running. This avoids a race where the
> > * publisher's sequence advances after the current worker has fetched its
> > * value but before it marks the sequence READY. A user may then issue
> > * another REFRESH SEQUENCES to synchronize the updated value. Since the
> > * affected sequences are already in the INIT state, the running worker
> > * has no indication that a new synchronization has been requested. It
> > * would then apply the stale value it already fetched and mark the
> > * sequence READY, causing the new synchronization request to be lost and
> > * preventing the updated publisher values from being synchronized.
> > */
>
> Modified
>
> > 2)
> > I am unsure if a testcase is really needed here as it is a very simple
> > fix. But I'd like to see what others think here.
> > If a test is needed, I can review it, currently I have skipped it.
>
> Even I feel a test case is not needed for this.
>
> The attached v2 version patch has the changes for the same.
> In addition, it includes a fix for Finding 2
> (default_transaction_read_only) reported by Noah at [1], following the
> approach suggested by Amit at [2].
> This version also addresses Findings 6 and 17 by reporting an error
> when ALTER SUBSCRIPTION ... REFRESH SEQUENCES is executed against
> subscriptions created prior to PostgreSQL 19, and documents this
> behavior accordingly.
>
> [1] - https://www.postgresql.org/message-id/20260710045217.f0.noahmisch%40microsoft.com
> [2] - https://www.postgresql.org/message-id/CAA4eK1K8LD243UzHgVNCm4skJZ4UCjR3vowDhKp%3DcWnK5oBT-Q%40mail.g...
>
Thanks. A few comments:
001:
1)
+ if (walrcv_server_version(wrconn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences for subscription \"%s\" because
the publisher is running a version earlier than PostgreSQL 19",
+ sub->name));
+
I think the error can be independent of subscription name, we can give
a generic error, similar to:
"cannot enable retain_dead_tuples if the publisher is running a
version earlier than PostgreSQL 19"
2)
The fix will solve the purpose. But I have a doubt for the check in
copy_sequences(). Say a few sequences are already in the INIT state,
and the subscription is then repointed to a pre-PG19 publisher. Would
the apply worker keep launching the sequence sync worker, only for it
to exit each time with this error of copy_sequences? Is my
understanding correct? If so, is there a way to avoid launching the
sequence sync worker altogether when connected to a pre-PG19
publisher? We do have 'LogRepWorkerWalRcvConn' in the apply worker to
check the version.
002: looks good.
003: yet to review.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-14 02:51 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 03:32 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 2 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-14 02:51 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Mon, Jul 13, 2026 at 5:46 PM shveta malik <shveta.malik@gmail.com> wrote:
>
> 2)
> The fix will solve the purpose. But I have a doubt for the check in
> copy_sequences(). Say a few sequences are already in the INIT state,
> and the subscription is then repointed to a pre-PG19 publisher. Would
> the apply worker keep launching the sequence sync worker, only for it
> to exit each time with this error of copy_sequences? Is my
> understanding correct? If so, is there a way to avoid launching the
> sequence sync worker altogether when connected to a pre-PG19
> publisher? We do have 'LogRepWorkerWalRcvConn' in the apply worker to
> check the version.
>
Good question. I think to handle the case you pointed and even REFRESH
SEQUENCES case, isn't it better to raise an ERROR when the user is
trying to point connection to a version prior to 19 and the subscriber
has sequences? For example, we do something similar retain_dead_tuples
parameter in AlterSubscription, see handling of
ALTER_SUBSCRIPTION_SERVER/ALTER_SUBSCRIPTION_CONNECTION->check_pub_rdt
in AlterSubscription().
One additional comment on 0001:
===========================
*
@@ -435,6 +435,19 @@ copy_sequences(WalReceiverConn *conn)
StringInfoData cmd;
MemoryContext oldctx;
+ /*
+ * Sequence synchronization relies on pg_get_sequence_data(), which is
+ * only available since PostgreSQL 19. Fail with a clear diagnosis
+ * instead of sending a query that the publisher cannot execute (or
+ * cannot even parse), which would otherwise make this worker exit with
+ * a confusing "invalid query response" error.
+ */
+ if (walrcv_server_version(conn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot synchronize sequences for subscription \"%s\" because
the publisher is running a version earlier than PostgreSQL 19",
+ MySubscription->name));
I think it is better to move this check to the caller immediately
after making a connection to the publisher. Also, the second part of
the comment: "Fail with a clear diagnosis instead of sending a query
that the ..." appears redundant to me as the code is explicit about
the case.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-14 03:32 ` shveta malik <shveta.malik@gmail.com>
1 sibling, 0 replies; 82+ messages in thread
From: shveta malik @ 2026-07-14 03:32 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Tue, Jul 14, 2026 at 8:21 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Mon, Jul 13, 2026 at 5:46 PM shveta malik <shveta.malik@gmail.com> wrote:
> >
> > 2)
> > The fix will solve the purpose. But I have a doubt for the check in
> > copy_sequences(). Say a few sequences are already in the INIT state,
> > and the subscription is then repointed to a pre-PG19 publisher. Would
> > the apply worker keep launching the sequence sync worker, only for it
> > to exit each time with this error of copy_sequences? Is my
> > understanding correct? If so, is there a way to avoid launching the
> > sequence sync worker altogether when connected to a pre-PG19
> > publisher? We do have 'LogRepWorkerWalRcvConn' in the apply worker to
> > check the version.
> >
>
> Good question. I think to handle the case you pointed and even REFRESH
> SEQUENCES case, isn't it better to raise an ERROR when the user is
> trying to point connection to a version prior to 19 and the subscriber
> has sequences? For example, we do something similar retain_dead_tuples
> parameter in AlterSubscription, see handling of
> ALTER_SUBSCRIPTION_SERVER/ALTER_SUBSCRIPTION_CONNECTION->check_pub_rdt
> in AlterSubscription().
>
Yes, that makes sense, as the user will not be able to change the
connection string unless they modify the subscription to remove
sequences.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-14 13:16 ` vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
1 sibling, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-14 13:16 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Tue, 14 Jul 2026 at 08:21, Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Mon, Jul 13, 2026 at 5:46 PM shveta malik <shveta.malik@gmail.com> wrote:
> >
> > 2)
> > The fix will solve the purpose. But I have a doubt for the check in
> > copy_sequences(). Say a few sequences are already in the INIT state,
> > and the subscription is then repointed to a pre-PG19 publisher. Would
> > the apply worker keep launching the sequence sync worker, only for it
> > to exit each time with this error of copy_sequences? Is my
> > understanding correct? If so, is there a way to avoid launching the
> > sequence sync worker altogether when connected to a pre-PG19
> > publisher? We do have 'LogRepWorkerWalRcvConn' in the apply worker to
> > check the version.
> >
>
> Good question. I think to handle the case you pointed and even REFRESH
> SEQUENCES case, isn't it better to raise an ERROR when the user is
> trying to point connection to a version prior to 19 and the subscriber
> has sequences? For example, we do something similar retain_dead_tuples
> parameter in AlterSubscription, see handling of
> ALTER_SUBSCRIPTION_SERVER/ALTER_SUBSCRIPTION_CONNECTION->check_pub_rdt
> in AlterSubscription().
This is a better approach, the updated patch has the changes for the same.
> One additional comment on 0001:
> ===========================
> *
> @@ -435,6 +435,19 @@ copy_sequences(WalReceiverConn *conn)
> StringInfoData cmd;
> MemoryContext oldctx;
>
> + /*
> + * Sequence synchronization relies on pg_get_sequence_data(), which is
> + * only available since PostgreSQL 19. Fail with a clear diagnosis
> + * instead of sending a query that the publisher cannot execute (or
> + * cannot even parse), which would otherwise make this worker exit with
> + * a confusing "invalid query response" error.
> + */
> + if (walrcv_server_version(conn) < 190000)
> + ereport(ERROR,
> + errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> + errmsg("cannot synchronize sequences for subscription \"%s\" because
> the publisher is running a version earlier than PostgreSQL 19",
> + MySubscription->name));
>
> I think it is better to move this check to the caller immediately
> after making a connection to the publisher. Also, the second part of
> the comment: "Fail with a clear diagnosis instead of sending a query
> that the ..." appears redundant to me as the code is explicit about
> the case.
Now that we are checking it while setting the connection itself, this
cannot happen from the sequence synchronization worker, so removed
this code.
The attached v3 version patch has the changes for the same.
This patch also addresses Shveta's comments from [1], Amit's comment
from [2] and [3].
[1] - https://www.postgresql.org/message-id/CAJpy0uAgM5qg5OrwL3YsiZeaq7aCyXhjSTKWHuuXoxpVcpytBw%40mail.gma...
[2] - https://www.postgresql.org/message-id/CAA4eK1%2Ba6FyNW-LEqWAmohM3oC6h6KOrBBSeU%2B-cfopZQLXs3g%40mail...
[3] - https://www.postgresql.org/message-id/CAA4eK1Kf9kRm%2BT8DvkgeiPt6Az7%3DTBwWJgZ%2B3UfUEz9q9CDQeA%40ma...
Regards,
Vignesh
Attachments:
[application/octet-stream] v3-0003-Ignore-default_transaction_read_only-in-sequence-.patch (3.7K, ../../CALDaNm0wi5XxbHsmKT+phLHwV=NtUeFeCpiKkjx29RanJjJ7UQ@mail.gmail.com/2-v3-0003-Ignore-default_transaction_read_only-in-sequence-.patch)
download | inline diff:
From 446c23ee5fca547d9bb01fa3d65c90107dfbede6 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 14 Jul 2026 17:10:58 +0530
Subject: [PATCH v3 3/3] Ignore default_transaction_read_only in sequence sync
workers
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", even though apply and tablesync workers
are unaffected since they write via direct heap access.
Override default_transaction_read_only to "off" in
SequenceSyncWorkerMain(), so sequence synchronization keeps working
regardless of the subscriber's default setting.
---
.../replication/logical/sequencesync.c | 8 +++
src/test/subscription/t/036_sequences.pl | 51 +++++++++++++++++++
2 files changed, 59 insertions(+)
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 770fa5de10b..fa74f1edbaa 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -826,6 +826,14 @@ SequenceSyncWorkerMain(Datum main_arg)
{
int worker_slot = DatumGetInt32(main_arg);
+ /*
+ * Ignore default_transaction_read_only for sequence synchronization
+ * workers, as they need to be able to modify sequences regardless of that
+ * setting.
+ */
+ SetConfigOption("default_transaction_read_only", "off", PGC_SUSET,
+ PGC_S_OVERRIDE);
+
SetupApplyOrSyncWorker(worker_slot);
start_sequence_sync();
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 8b02b24a7e9..b72860add7f 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -188,6 +188,57 @@ is($result, '1|f',
'REFRESH PUBLICATION will not sync newly published sequence with copy_data as false'
);
+##########
+# Ensure that ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update
+# sequence values and mark the sequence as ready even when
+# default_transaction_read_only is enabled on the subscriber.
+##########
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = on;
+ SELECT pg_reload_conf();
+));
+
+# Update the existing sequence 'regress_s3' on the publisher
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,100);
+));
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ set default_transaction_read_only = off;
+ ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check - sequence value is updated despite default_transaction_read_only
+# being enabled on the subscriber
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT last_value, is_called FROM regress_s3;
+));
+is($result, '200|t',
+ 'REFRESH SEQUENCES updates sequence value with default_transaction_read_only enabled'
+);
+
+# Check - sequence is marked as ready ('r')
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass;
+));
+is($result, 'r',
+ 'sequence is marked as ready after REFRESH SEQUENCES with default_transaction_read_only enabled'
+);
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = off;
+ SELECT pg_reload_conf();
+));
+
##########
# ALTER SUBSCRIPTION ... REFRESH PUBLICATION should report an error when:
# a) sequence definitions differ between the publisher and subscriber, or
--
2.50.1 (Apple Git-155)
[application/octet-stream] v3-0002-Reject-changing-subscriptions-with-sequences-to-p.patch (10.4K, ../../CALDaNm0wi5XxbHsmKT+phLHwV=NtUeFeCpiKkjx29RanJjJ7UQ@mail.gmail.com/3-v3-0002-Reject-changing-subscriptions-with-sequences-to-p.patch)
download | inline diff:
From b79f707ab3f41de37f1ff4552b7a669d229eba7a Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 14 Jul 2026 12:22:39 +0530
Subject: [PATCH v3 2/3] Reject changing subscriptions with sequences to
pre-PG19 publishers
Sequence synchronization relies on pg_get_sequence_data(), which is
available only on PostgreSQL 19 and later. Changing an existing
subscription that contains replicated sequences to a pre-PostgreSQL 19
publisher using ALTER SUBSCRIPTION ... CONNECTION or
ALTER SUBSCRIPTION ... SERVER would leave sequence synchronization
unsupported.
Prevent this by checking the publisher version whenever the connection
or server of a subscription containing replicated sequences is changed.
If the new publisher is older than PostgreSQL 19, report an error.
Also document that sequence replication requires a PostgreSQL 19 or
later publisher, including in the documentation.
---
doc/src/sgml/logical-replication.sgml | 18 ++++++++-
doc/src/sgml/ref/alter_subscription.sgml | 16 ++++++++
src/backend/catalog/pg_subscription.c | 45 +++++++++++++++++++++++
src/backend/commands/subscriptioncmds.c | 45 +++++++++++++++++++++--
src/include/catalog/pg_subscription_rel.h | 1 +
5 files changed, 120 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 690598bff98..274ba8b447a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1772,6 +1772,13 @@ Included in publications:
<sect1 id="logical-replication-sequences">
<title>Replicating Sequences</title>
+ <note>
+ <para>
+ Sequence synchronization requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
+
<para>
To synchronize sequences from a publisher to a subscriber, first publish
them using <link linkend="sql-createpublication-params-for-all-sequences">
@@ -2368,7 +2375,16 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
<command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link>
or by copying the current data from the publisher (perhaps using
<command>pg_dump</command>) or by determining a sufficiently high value
- from the tables themselves.
+ from the tables themselves. Note that
+ <link linkend="sql-altersubscription-params-refresh-sequences">
+ <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+ re-synchronizes sequences that are already known to the subscription
+ (see <xref linkend="logical-replication-sequences"/>); in particular, it
+ requires the publisher to be running <productname>PostgreSQL</productname>
+ 19 or later. Before relying on it to prepare for a switchover or
+ failover, confirm that the publisher's version supports sequence
+ replication and that the sequences of interest are already known to the
+ subscription.
</para>
</listitem>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8d64744375a..f81792918f3 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -111,6 +111,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
set by <xref linkend="sql-createsubscription"/> with the foreign server
<replaceable>servername</replaceable>.
</para>
+ <note>
+ <para>
+ If the subscription already has sequences, the new publisher must be
+ running <productname>PostgreSQL</productname> 19 or later. Earlier
+ <productname>PostgreSQL</productname> versions do not support sequence
+ synchronization.
+ </para>
+ </note>
</listitem>
</varlistentry>
@@ -122,6 +130,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
set by <xref linkend="sql-createsubscription"/> with the connection
string <replaceable>conninfo</replaceable>.
</para>
+ <note>
+ <para>
+ If the subscription already has sequences, the new publisher must be
+ running <productname>PostgreSQL</productname> 19 or later. Earlier
+ <productname>PostgreSQL</productname> versions do not support sequence
+ synchronization.
+ </para>
+ </note>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 2068e03c571..fea9625aba2 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -614,6 +614,51 @@ HasSubscriptionTables(Oid subid)
return has_subtables;
}
+/*
+ * Does the subscription have any sequences?
+ *
+ * Use this function only to know true/false, and when you have no need for
+ * the List returned by GetSubscriptionRelations.
+ */
+bool
+HasSubscriptionSequences(Oid subid)
+{
+ Relation rel;
+ ScanKeyData skey[1];
+ SysScanDesc scan;
+ HeapTuple tup;
+ bool has_subsequences = false;
+
+ rel = table_open(SubscriptionRelRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_subscription_rel_srsubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(subid));
+
+ scan = systable_beginscan(rel, InvalidOid, false,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_subscription_rel subrel;
+
+ subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
+
+ if (get_rel_relkind(subrel->srrelid) == RELKIND_SEQUENCE)
+ {
+ has_subsequences = true;
+ break;
+ }
+ }
+
+ /* Cleanup */
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ return has_subsequences;
+}
+
/*
* Get the relations for the subscription.
*
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 48f4ff05d83..85da5e18971 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -139,6 +139,8 @@ static void check_publications_origin_sequences(WalReceiverConn *wrconn,
int subrel_count,
char *subname);
static void check_pub_dead_tuple_retention(WalReceiverConn *wrconn);
+static void check_pub_sequence_sync_support(WalReceiverConn *wrconn,
+ char *subname);
static void check_duplicates_in_publist(List *publist, Datum *datums);
static List *merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname);
static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err);
@@ -1606,6 +1608,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
bool update_failover = false;
bool update_two_phase = false;
bool check_pub_rdt = false;
+ bool check_pub_seq = false;
bool retain_dead_tuples;
int max_retention;
bool retention_active;
@@ -2142,6 +2145,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* retain_dead_tuples.
*/
check_pub_rdt = sub->retaindeadtuples;
+
+ /*
+ * If the subscription already has sequences, ensure the new
+ * publisher is new enough to synchronize them.
+ */
+ check_pub_seq = HasSubscriptionSequences(subid);
break;
case ALTER_SUBSCRIPTION_CONNECTION:
@@ -2175,6 +2184,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* retain_dead_tuples.
*/
check_pub_rdt = sub->retaindeadtuples;
+
+ /*
+ * If the subscription already has sequences, ensure the new
+ * publisher is new enough to synchronize them.
+ */
+ check_pub_seq = HasSubscriptionSequences(subid);
break;
case ALTER_SUBSCRIPTION_SET_PUBLICATION:
@@ -2375,15 +2390,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
}
/*
- * Try to acquire the connection necessary either for modifying the slot
- * or for checking if the remote server permits enabling
- * retain_dead_tuples.
+ * Try to acquire the connection necessary either for modifying the slot,
+ * for checking if the remote server permits enabling retain_dead_tuples,
+ * or for checking if it is new enough to synchronize the subscription's
+ * sequences.
*
* This has to be at the end because otherwise if there is an error while
* doing the database operations we won't be able to rollback altered
* slot.
*/
- if (update_failover || update_two_phase || check_pub_rdt)
+ if (update_failover || update_two_phase || check_pub_rdt || check_pub_seq)
{
bool must_use_password;
char *err;
@@ -2413,6 +2429,9 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
if (retain_dead_tuples)
check_pub_dead_tuple_retention(wrconn);
+ if (check_pub_seq)
+ check_pub_sequence_sync_support(wrconn, sub->name);
+
check_publications_origin_tables(wrconn, sub->publications, false,
retain_dead_tuples, origin, NULL, 0,
sub->name);
@@ -3356,6 +3375,24 @@ check_pub_dead_tuple_retention(WalReceiverConn *wrconn)
walrcv_clear_result(res);
}
+/*
+ * Check that the publisher is new enough to synchronize sequences.
+ *
+ * Sequence synchronization relies on pg_get_sequence_data(), which is only
+ * available since PostgreSQL 19. Called when the subscription's connection
+ * or server changes and the subscription already has sequences, so that an
+ * incompatible publisher is rejected immediately rather than only being
+ * discovered later when a sequencesync worker tries and fails to run.
+ */
+static void
+check_pub_sequence_sync_support(WalReceiverConn *wrconn, char *subname)
+{
+ if (walrcv_server_version(wrconn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+}
+
/*
* Check if the subscriber's configuration is adequate to enable the
* retain_dead_tuples option.
diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h
index 502640d3018..f5f1287b1f5 100644
--- a/src/include/catalog/pg_subscription_rel.h
+++ b/src/include/catalog/pg_subscription_rel.h
@@ -117,6 +117,7 @@ extern char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn);
extern void RemoveSubscriptionRel(Oid subid, Oid relid);
extern bool HasSubscriptionTables(Oid subid);
+extern bool HasSubscriptionSequences(Oid subid);
extern List *GetSubscriptionRelations(Oid subid, bool tables, bool sequences,
bool not_ready);
--
2.50.1 (Apple Git-155)
[application/octet-stream] v3-0001-Reject-concurrent-sequence-refreshes.patch (3.5K, ../../CALDaNm0wi5XxbHsmKT+phLHwV=NtUeFeCpiKkjx29RanJjJ7UQ@mail.gmail.com/4-v3-0001-Reject-concurrent-sequence-refreshes.patch)
download | inline diff:
From 597c0812811609b8cecaccd992a40457f04f4aa4 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 14 Jul 2026 10:07:11 +0530
Subject: [PATCH v3 1/3] Reject concurrent sequence refreshes
'ALTER SUBSCRIPTION ... REFRESH SEQUENCES' can race with an already
running sequence synchronization worker. If a second refresh request
resets the synchronization state while the worker has already fetched
sequence values from the publisher but has not yet applied them to the
subscriber, the worker can overwrite the subscriber with stale values
and mark the synchronization as complete.
Avoid this race by rejecting 'ALTER SUBSCRIPTION ... REFRESH SEQUENCES'
when a sequence synchronization worker is already running for the
subscription. The command reports an error asking the user to rerun it
after the current synchronization completes.
Also add a wait for the re-added 'regress_s4' sequence to finish
synchronizing in 036_sequences.pl, so the subsequent test does not
race against its sequencesync worker.
---
src/backend/commands/subscriptioncmds.c | 26 ++++++++++++++++++++++++
src/test/subscription/t/036_sequences.pl | 4 ++++
2 files changed, 30 insertions(+)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..48f4ff05d83 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1363,6 +1363,32 @@ AlterSubscription_refresh_seq(Subscription *sub)
WalReceiverConn *wrconn;
bool must_use_password;
+ /*
+ * Disallow a concurrent REFRESH SEQUENCES while a sequence sync worker
+ * for this subscription is still running. This avoids a race where the
+ * publisher's sequence advances after the current worker has fetched its
+ * value but before it marks the sequence READY. A user may then issue
+ * another REFRESH SEQUENCES to synchronize the updated value. Since the
+ * affected sequences are already in the INIT state, the running worker
+ * has no indication that a new synchronization has been requested. It
+ * would then apply the stale value it already fetched and mark the
+ * sequence READY, causing the new synchronization request to be lost and
+ * preventing the updated publisher values from being synchronized.
+ */
+ LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+ if (logicalrep_worker_find(WORKERTYPE_SEQUENCESYNC, sub->oid, InvalidOid,
+ true))
+ {
+ LWLockRelease(LogicalRepWorkerLock);
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot execute %s while a sequence synchronization worker is running",
+ "ALTER SUBSCRIPTION ... REFRESH SEQUENCES"),
+ errhint("Try again after the current synchronization completes."));
+ }
+
+ LWLockRelease(LogicalRepWorkerLock);
+
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 2a0819aaf01..8b02b24a7e9 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -232,6 +232,10 @@ $node_publisher->safe_psql(
CREATE SEQUENCE regress_s4 START 10 INCREMENT 2;
));
+# Wait for the missing sequence added to be synced
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
##########
# Ensure that insufficient privileges on the publisher for a sequence
# are reported correctly as a permission issue, not as a missing sequence.
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-15 01:31 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-15 01:31 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; Amit Kapila <amit.kapila16@gmail.com>
Dear Vignesh,
Thanks for updating the patch. Two comments:
01.
```
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot execute %s while a sequence synchronization worker for subscription \"%s\" is still running",
+ "ALTER SUBSCRIPTION ... REFRESH SEQUENCES",
+ sub->name),
+ errhint("Try again after the current synchronization completes."));
```
Maybe a translator note is needed, per other lines.
02.
```
@@ -2142,6 +2145,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* retain_dead_tuples.
*/
check_pub_rdt = sub->retaindeadtuples;
+
+ /*
+ * If the subscription already has sequences, ensure the new
+ * publisher is new enough to synchronize them.
+ */
+ check_pub_seq = HasSubscriptionSequences(subid);
```
HasSubscriptionSequences() can spend long time because it searches pg_subscription_rel
catalog linerly. Do you think we can do the deferrable approach? I imagined to
check the remote version first and then check the catalog if the version is prior
than PG 19. Or it might be more costly to check the remote version?
Also, I find a case that REFRESH command can work even after the sequencesync
worker. I think it's harmless because of the LockSharedObject(), but let me share
just in case.
1. run ALTER SUBSCRIPTION REFRESH SEQUENCES on the first session. all states for
the sequences can be initialized in pg_subscription_rel.
2. the apply worker finds the needs of sequencesync workers.
3. run ALTER SUBSCRIPTION REFRESH SEQUENCES on the second session. At that time
the sequencesync worker does not exist, so it does not raise an ERROR.
4. a sequencesync worker starts, but it waits for acquiring the shared object
with Access Exclusive.
5. the second session re-initialize the sequence states, then commit.
6. a sequencesync worker can resumes.
Can you come up with similar (and harmful) cases?
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
@ 2026-07-15 09:13 ` vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-15 09:13 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; Amit Kapila <amit.kapila16@gmail.com>
On Wed, 15 Jul 2026 at 07:01, Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
>
> Dear Vignesh,
>
> Thanks for updating the patch. Two comments:
>
> 01.
> ```
> + ereport(ERROR,
> + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("cannot execute %s while a sequence synchronization worker for subscription \"%s\" is still running",
> + "ALTER SUBSCRIPTION ... REFRESH SEQUENCES",
> + sub->name),
> + errhint("Try again after the current synchronization completes."));
> ```
>
> Maybe a translator note is needed, per other lines.
Yes, this should be fixed
> 02.
> ```
> @@ -2142,6 +2145,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
> * retain_dead_tuples.
> */
> check_pub_rdt = sub->retaindeadtuples;
> +
> + /*
> + * If the subscription already has sequences, ensure the new
> + * publisher is new enough to synchronize them.
> + */
> + check_pub_seq = HasSubscriptionSequences(subid);
> ```
>
> HasSubscriptionSequences() can spend long time because it searches pg_subscription_rel
> catalog linerly. Do you think we can do the deferrable approach? I imagined to
> check the remote version first and then check the catalog if the version is prior
> than PG 19. Or it might be more costly to check the remote version?
If we do it that way, every ALTER SUBSCRIPTION CONNECTION or SERVER
would connect to the publisher, even for subscriptions that don't have
any sequences. At present, we only establish a connection to the
publisher when it is actually required, such as when update_failover,
update_two_phase, or check_pub_rdt is true. Establishing a connection
to the publisher is not a lightweight operation. It involves creating
a new connection, performing authentication, and incurring network
round trips before we can determine whether the publication contains
any sequences. In contrast, HasSubscriptionSequences() only performs a
local catalog lookup on the subscriber to determine whether the
subscription has any sequences, which is significantly cheaper. Since
subscriptions are likely to have no sequences in few cases, always
connecting to the publisher would introduce additional overhead in the
common case just to discover information that we can often infer
locally. The local check also avoids unnecessary dependence on
publisher connectivity when no sequence-related work is needed.
Additionally this ALTER SUBSCRIPTION ... CONNECTION OR SERVER are very
rare operations. For these reasons, I think it's better to keep the
existing HasSubscriptionSequences() check so that we only connect to
the publisher when there is a possibility that sequence-related
validation is actually required.
> Also, I find a case that REFRESH command can work even after the sequencesync
> worker. I think it's harmless because of the LockSharedObject(), but let me share
> just in case.
>
> 1. run ALTER SUBSCRIPTION REFRESH SEQUENCES on the first session. all states for
> the sequences can be initialized in pg_subscription_rel.
> 2. the apply worker finds the needs of sequencesync workers.
> 3. run ALTER SUBSCRIPTION REFRESH SEQUENCES on the second session. At that time
> the sequencesync worker does not exist, so it does not raise an ERROR.
> 4. a sequencesync worker starts, but it waits for acquiring the shared object
> with Access Exclusive.
> 5. the second session re-initialize the sequence states, then commit.
> 6. a sequencesync worker can resumes.
I think this case is okay. Although the second REFRESH SEQUENCES
reinitializes the sequence states before the sequence sync worker
acquires the lock, the worker has not yet fetched any sequence values
from the publisher at that point. Once it starts running, it will read
the current sequence values from the publisher and synchronize those,
rather than applying any stale values. So I don't think this
interleaving can result in outdated sequence values being applied.
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-21 04:44 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-21 04:44 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, Jul 15, 2026 at 2:44 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Wed, 15 Jul 2026 at 07:01, Hayato Kuroda (Fujitsu)
> <kuroda.hayato@fujitsu.com> wrote:
> >
>
> > 02.
> > ```
> > @@ -2142,6 +2145,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
> > * retain_dead_tuples.
> > */
> > check_pub_rdt = sub->retaindeadtuples;
> > +
> > + /*
> > + * If the subscription already has sequences, ensure the new
> > + * publisher is new enough to synchronize them.
> > + */
> > + check_pub_seq = HasSubscriptionSequences(subid);
> > ```
> >
> > HasSubscriptionSequences() can spend long time because it searches pg_subscription_rel
> > catalog linerly. Do you think we can do the deferrable approach? I imagined to
> > check the remote version first and then check the catalog if the version is prior
> > than PG 19. Or it might be more costly to check the remote version?
>
> If we do it that way, every ALTER SUBSCRIPTION CONNECTION or SERVER
> would connect to the publisher, even for subscriptions that don't have
> any sequences. At present, we only establish a connection to the
> publisher when it is actually required, such as when update_failover,
> update_two_phase, or check_pub_rdt is true. Establishing a connection
> to the publisher is not a lightweight operation. It involves creating
> a new connection, performing authentication, and incurring network
> round trips before we can determine whether the publication contains
> any sequences. In contrast, HasSubscriptionSequences() only performs a
> local catalog lookup on the subscriber to determine whether the
> subscription has any sequences, which is significantly cheaper.
>
I agree with this reasoning for not performing connection before
checking sequences. I think if we want to make
HasSubscriptionSequences() cheap then we can have an index on srsubid
in pg_subscription_rel or somehow track the presence of sequences at
pg_subscription level. The other idea to optimize is that for the
cases where we anyway need to form connection with the publisher, we
can defer checking the sequences. However, I feel changing the
connection string shouldn't be performed often enough to worry about
or adding additional complexity in code or adding more optimizations
for it.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-21 05:18 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-21 05:18 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; vignesh C <vignesh21@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
Dear Amit, Vignesh,
> I agree with this reasoning for not performing connection before
> checking sequences.
My assumption was that the connection was not so heavy operation.
If it's not correct, yes the connection should be established later.
> However, I feel changing the
> connection string shouldn't be performed often enough to worry about
> or adding additional complexity in code or adding more optimizations
> for it.
I suspect this part rarely happens. So Ok for me not to spend hacking costs.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
@ 2026-07-21 05:34 ` vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-21 11:37 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
0 siblings, 2 replies; 82+ messages in thread
From: vignesh C @ 2026-07-21 05:34 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Tue, 21 Jul 2026 at 10:48, Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
>
> My assumption was that the connection was not so heavy operation.
> If it's not correct, yes the connection should be established later.
FYI, the v3-0002 patch from [1], which fixes this issue, applies
cleanly to both the master and PG19 branches. I'm re-attaching the
same patch here to make it easier for reviewers to access and review
it.
[1] - https://www.postgresql.org/message-id/CALDaNm0wi5XxbHsmKT%2BphLHwV%3DNtUeFeCpiKkjx29RanJjJ7UQ%40mail...
Regards,
Vignesh
Attachments:
[application/octet-stream] v3-0002-Reject-changing-subscriptions-with-sequences-to-p.patch (10.4K, ../../CALDaNm3MHsVJiW1mfzAX_n7bQvrvpR_D+xKNRCGOuToDoXhE2A@mail.gmail.com/2-v3-0002-Reject-changing-subscriptions-with-sequences-to-p.patch)
download | inline diff:
From b79f707ab3f41de37f1ff4552b7a669d229eba7a Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 14 Jul 2026 12:22:39 +0530
Subject: [PATCH v3 2/3] Reject changing subscriptions with sequences to
pre-PG19 publishers
Sequence synchronization relies on pg_get_sequence_data(), which is
available only on PostgreSQL 19 and later. Changing an existing
subscription that contains replicated sequences to a pre-PostgreSQL 19
publisher using ALTER SUBSCRIPTION ... CONNECTION or
ALTER SUBSCRIPTION ... SERVER would leave sequence synchronization
unsupported.
Prevent this by checking the publisher version whenever the connection
or server of a subscription containing replicated sequences is changed.
If the new publisher is older than PostgreSQL 19, report an error.
Also document that sequence replication requires a PostgreSQL 19 or
later publisher, including in the documentation.
---
doc/src/sgml/logical-replication.sgml | 18 ++++++++-
doc/src/sgml/ref/alter_subscription.sgml | 16 ++++++++
src/backend/catalog/pg_subscription.c | 45 +++++++++++++++++++++++
src/backend/commands/subscriptioncmds.c | 45 +++++++++++++++++++++--
src/include/catalog/pg_subscription_rel.h | 1 +
5 files changed, 120 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 690598bff98..274ba8b447a 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1772,6 +1772,13 @@ Included in publications:
<sect1 id="logical-replication-sequences">
<title>Replicating Sequences</title>
+ <note>
+ <para>
+ Sequence synchronization requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
+
<para>
To synchronize sequences from a publisher to a subscriber, first publish
them using <link linkend="sql-createpublication-params-for-all-sequences">
@@ -2368,7 +2375,16 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
<command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link>
or by copying the current data from the publisher (perhaps using
<command>pg_dump</command>) or by determining a sufficiently high value
- from the tables themselves.
+ from the tables themselves. Note that
+ <link linkend="sql-altersubscription-params-refresh-sequences">
+ <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+ re-synchronizes sequences that are already known to the subscription
+ (see <xref linkend="logical-replication-sequences"/>); in particular, it
+ requires the publisher to be running <productname>PostgreSQL</productname>
+ 19 or later. Before relying on it to prepare for a switchover or
+ failover, confirm that the publisher's version supports sequence
+ replication and that the sequences of interest are already known to the
+ subscription.
</para>
</listitem>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8d64744375a..f81792918f3 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -111,6 +111,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
set by <xref linkend="sql-createsubscription"/> with the foreign server
<replaceable>servername</replaceable>.
</para>
+ <note>
+ <para>
+ If the subscription already has sequences, the new publisher must be
+ running <productname>PostgreSQL</productname> 19 or later. Earlier
+ <productname>PostgreSQL</productname> versions do not support sequence
+ synchronization.
+ </para>
+ </note>
</listitem>
</varlistentry>
@@ -122,6 +130,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
set by <xref linkend="sql-createsubscription"/> with the connection
string <replaceable>conninfo</replaceable>.
</para>
+ <note>
+ <para>
+ If the subscription already has sequences, the new publisher must be
+ running <productname>PostgreSQL</productname> 19 or later. Earlier
+ <productname>PostgreSQL</productname> versions do not support sequence
+ synchronization.
+ </para>
+ </note>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 2068e03c571..fea9625aba2 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -614,6 +614,51 @@ HasSubscriptionTables(Oid subid)
return has_subtables;
}
+/*
+ * Does the subscription have any sequences?
+ *
+ * Use this function only to know true/false, and when you have no need for
+ * the List returned by GetSubscriptionRelations.
+ */
+bool
+HasSubscriptionSequences(Oid subid)
+{
+ Relation rel;
+ ScanKeyData skey[1];
+ SysScanDesc scan;
+ HeapTuple tup;
+ bool has_subsequences = false;
+
+ rel = table_open(SubscriptionRelRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_subscription_rel_srsubid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(subid));
+
+ scan = systable_beginscan(rel, InvalidOid, false,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_subscription_rel subrel;
+
+ subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
+
+ if (get_rel_relkind(subrel->srrelid) == RELKIND_SEQUENCE)
+ {
+ has_subsequences = true;
+ break;
+ }
+ }
+
+ /* Cleanup */
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ return has_subsequences;
+}
+
/*
* Get the relations for the subscription.
*
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 48f4ff05d83..85da5e18971 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -139,6 +139,8 @@ static void check_publications_origin_sequences(WalReceiverConn *wrconn,
int subrel_count,
char *subname);
static void check_pub_dead_tuple_retention(WalReceiverConn *wrconn);
+static void check_pub_sequence_sync_support(WalReceiverConn *wrconn,
+ char *subname);
static void check_duplicates_in_publist(List *publist, Datum *datums);
static List *merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname);
static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err);
@@ -1606,6 +1608,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
bool update_failover = false;
bool update_two_phase = false;
bool check_pub_rdt = false;
+ bool check_pub_seq = false;
bool retain_dead_tuples;
int max_retention;
bool retention_active;
@@ -2142,6 +2145,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* retain_dead_tuples.
*/
check_pub_rdt = sub->retaindeadtuples;
+
+ /*
+ * If the subscription already has sequences, ensure the new
+ * publisher is new enough to synchronize them.
+ */
+ check_pub_seq = HasSubscriptionSequences(subid);
break;
case ALTER_SUBSCRIPTION_CONNECTION:
@@ -2175,6 +2184,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* retain_dead_tuples.
*/
check_pub_rdt = sub->retaindeadtuples;
+
+ /*
+ * If the subscription already has sequences, ensure the new
+ * publisher is new enough to synchronize them.
+ */
+ check_pub_seq = HasSubscriptionSequences(subid);
break;
case ALTER_SUBSCRIPTION_SET_PUBLICATION:
@@ -2375,15 +2390,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
}
/*
- * Try to acquire the connection necessary either for modifying the slot
- * or for checking if the remote server permits enabling
- * retain_dead_tuples.
+ * Try to acquire the connection necessary either for modifying the slot,
+ * for checking if the remote server permits enabling retain_dead_tuples,
+ * or for checking if it is new enough to synchronize the subscription's
+ * sequences.
*
* This has to be at the end because otherwise if there is an error while
* doing the database operations we won't be able to rollback altered
* slot.
*/
- if (update_failover || update_two_phase || check_pub_rdt)
+ if (update_failover || update_two_phase || check_pub_rdt || check_pub_seq)
{
bool must_use_password;
char *err;
@@ -2413,6 +2429,9 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
if (retain_dead_tuples)
check_pub_dead_tuple_retention(wrconn);
+ if (check_pub_seq)
+ check_pub_sequence_sync_support(wrconn, sub->name);
+
check_publications_origin_tables(wrconn, sub->publications, false,
retain_dead_tuples, origin, NULL, 0,
sub->name);
@@ -3356,6 +3375,24 @@ check_pub_dead_tuple_retention(WalReceiverConn *wrconn)
walrcv_clear_result(res);
}
+/*
+ * Check that the publisher is new enough to synchronize sequences.
+ *
+ * Sequence synchronization relies on pg_get_sequence_data(), which is only
+ * available since PostgreSQL 19. Called when the subscription's connection
+ * or server changes and the subscription already has sequences, so that an
+ * incompatible publisher is rejected immediately rather than only being
+ * discovered later when a sequencesync worker tries and fails to run.
+ */
+static void
+check_pub_sequence_sync_support(WalReceiverConn *wrconn, char *subname)
+{
+ if (walrcv_server_version(wrconn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+}
+
/*
* Check if the subscriber's configuration is adequate to enable the
* retain_dead_tuples option.
diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h
index 502640d3018..f5f1287b1f5 100644
--- a/src/include/catalog/pg_subscription_rel.h
+++ b/src/include/catalog/pg_subscription_rel.h
@@ -117,6 +117,7 @@ extern char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn);
extern void RemoveSubscriptionRel(Oid subid, Oid relid);
extern bool HasSubscriptionTables(Oid subid);
+extern bool HasSubscriptionSequences(Oid subid);
extern List *GetSubscriptionRelations(Oid subid, bool tables, bool sequences,
bool not_ready);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-21 07:19 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-21 07:19 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; shveta malik <shveta.malik@gmail.com>
On Tue, Jul 21, 2026 at 11:04 AM vignesh C <vignesh21@gmail.com> wrote:
>
> On Tue, 21 Jul 2026 at 10:48, Hayato Kuroda (Fujitsu)
> <kuroda.hayato@fujitsu.com> wrote:
> >
> > My assumption was that the connection was not so heavy operation.
> > If it's not correct, yes the connection should be established later.
>
> FYI, the v3-0002 patch from [1], which fixes this issue, applies
> cleanly to both the master and PG19 branches. I'm re-attaching the
> same patch here to make it easier for reviewers to access and review
> it.
>
> [1] - https://www.postgresql.org/message-id/CALDaNm0wi5XxbHsmKT%2BphLHwV%3DNtUeFeCpiKkjx29RanJjJ7UQ%40mail...
>
The logic of fix looks good. A few comments:
1)
Note
Sequence synchronization requires the publisher to be running
PostgreSQL 19 or later.
This note appears to be at odd position in
logical-replication-sequences.html. Please have a look at html and
move it to end of that section if you agree.
2)
+ from the tables themselves. Note that
+ <link linkend="sql-altersubscription-params-refresh-sequences">
+ <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+ re-synchronizes sequences that are already known to the subscription
+ (see <xref linkend="logical-replication-sequences"/>); in particular, it
+ requires the publisher to be running <productname>PostgreSQL</productname>
+ 19 or later. Before relying on it to prepare for a switchover or
+ failover, confirm that the publisher's version supports sequence
+ replication and that the sequences of interest are already known to the
+ subscription.
Do you think above is necessary? At subscription creation time, a
publisher running a version earlier than PostgreSQL 19 cannot have a
publication containing all sequences, so the subscription cannot have
such sequences known to it. Furthermore, ALTER SUBSCRIPTION to change
publisher-connection will report an error if the required
prerequisites are not met. Therefore, asking users to separately
verify the publisher version and sequence membership seems redundant
to me.
3)
+bool
+HasSubscriptionSequences(Oid subid)
Do you think we can resuse HasSubscriptionTables instead of
duplicating the complete code? We can have
HasSubscriptionRelations(subid, bool *has_tables, bool
*has_sequences)) Or is it not worth?
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-22 02:46 ` vignesh C <vignesh21@gmail.com>
2026-07-22 03:28 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 06:28 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-22 10:46 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 3 replies; 82+ messages in thread
From: vignesh C @ 2026-07-22 02:46 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Tue, 21 Jul 2026 at 12:49, shveta malik <shveta.malik@gmail.com> wrote:
>
> The logic of fix looks good. A few comments:
>
> 1)
> Note
> Sequence synchronization requires the publisher to be running
> PostgreSQL 19 or later.
>
> This note appears to be at odd position in
> logical-replication-sequences.html. Please have a look at html and
> move it to end of that section if you agree.
Modified
> 2)
> + from the tables themselves. Note that
> + <link linkend="sql-altersubscription-params-refresh-sequences">
> + <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
> + re-synchronizes sequences that are already known to the subscription
> + (see <xref linkend="logical-replication-sequences"/>); in particular, it
> + requires the publisher to be running <productname>PostgreSQL</productname>
> + 19 or later. Before relying on it to prepare for a switchover or
> + failover, confirm that the publisher's version supports sequence
> + replication and that the sequences of interest are already known to the
> + subscription.
>
> Do you think above is necessary? At subscription creation time, a
> publisher running a version earlier than PostgreSQL 19 cannot have a
> publication containing all sequences, so the subscription cannot have
> such sequences known to it. Furthermore, ALTER SUBSCRIPTION to change
> publisher-connection will report an error if the required
> prerequisites are not met. Therefore, asking users to separately
> verify the publisher version and sequence membership seems redundant
> to me.
This scenario can be possible, Kuroda-san has reported a similar issue at [1].
> 3)
> +bool
> +HasSubscriptionSequences(Oid subid)
>
> Do you think we can resuse HasSubscriptionTables instead of
> duplicating the complete code? We can have
> HasSubscriptionRelations(subid, bool *has_tables, bool
> *has_sequences)) Or is it not worth?
Since checking at set connection does not handle all the cases, now
I'm checking this at refresh sequences because of which this code has
been now removed.
The attached patch has the changes for the same. This patch also fixes
the issue reported by Kuroda-san at [1].
[1] - https://www.postgresql.org/message-id/flat/OS9PR01MB12149C5AC91BB56D5CB4B96B2F5C22%40OS9PR01MB12149....
Regards,
Vignesh
Attachments:
[application/octet-stream] v5-0001-Reject-sequence-synchronization-against-pre-PG19-.patch (5.3K, ../../CALDaNm3RPwc3uHK3OV0qM41YvO7qHkx6yAu4ayScTo7kaMZ_AQ@mail.gmail.com/2-v5-0001-Reject-sequence-synchronization-against-pre-PG19-.patch)
download | inline diff:
From 5e058dddd3764460bfd388acd461acaf04955472 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 22 Jul 2026 06:45:22 +0530
Subject: [PATCH v5] Reject sequence synchronization against pre-PG19
publishers
Sequence synchronization relies on pg_get_sequence_data(), which was
added in PostgreSQL 19. Previously, requesting sequence sync against
an older publisher (via ALTER SUBSCRIPTION ... REFRESH SEQUENCES,
or a sequencesync worker) would either reset the affected sequences
to INIT state and have the worker 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 with a clear diagnosis 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.
---
doc/src/sgml/logical-replication.sgml | 18 +++++++++++++++++-
doc/src/sgml/ref/alter_subscription.sgml | 6 ++++++
src/backend/commands/subscriptioncmds.c | 9 +++++++++
src/backend/replication/logical/sequencesync.c | 9 +++++++++
4 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 690598bff98..36298cacb75 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1818,6 +1818,13 @@ Included in publications:
configuration.
</para>
+ <note>
+ <para>
+ Sequence synchronization requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
+
<sect2 id="sequence-definition-mismatches">
<title>Sequence Definition Mismatches</title>
<para>
@@ -2368,7 +2375,16 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
<command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link>
or by copying the current data from the publisher (perhaps using
<command>pg_dump</command>) or by determining a sufficiently high value
- from the tables themselves.
+ from the tables themselves. Note that
+ <link linkend="sql-altersubscription-params-refresh-sequences">
+ <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+ re-synchronizes sequences that are already known to the subscription
+ (see <xref linkend="logical-replication-sequences"/>); in particular, it
+ requires the publisher to be running <productname>PostgreSQL</productname>
+ 19 or later. Before relying on it to prepare for a switchover or
+ failover, confirm that the publisher's version supports sequence
+ replication and that the sequences of interest are already known to the
+ subscription.
</para>
</listitem>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8d64744375a..6fc3e07a2d5 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -245,6 +245,12 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
sequences are subscribed. Run <literal>REFRESH PUBLICATION</literal>
first if the publication's set of sequences has changed.
</para>
+ <note>
+ <para>
+ Sequence replication requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
<para>
See <xref linkend="sequence-definition-mismatches"/> for
recommendations on how to handle any warnings about sequence definition
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 63d288a4630..4a2c1d3f7b8 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1381,6 +1381,15 @@ AlterSubscription_refresh_seq(Subscription *sub)
/* The publisher connection is only needed for the origin check. */
PG_TRY();
{
+ /*
+ * Sequence synchronization requires publisher support, which is
+ * available only in PostgreSQL 19 and later.
+ */
+ if (walrcv_server_version(wrconn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+
check_publications_origin_sequences(wrconn, sub->publications, true,
sub->origin, NULL, 0, sub->name);
}
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 63ad46d7fd7..cc38c7322a5 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -444,6 +444,15 @@ copy_sequences(WalReceiverConn *conn)
StringInfoData cmd;
MemoryContext oldctx;
+ /*
+ * Sequence synchronization requires publisher support, which is available
+ * only in PostgreSQL 19 and later.
+ */
+ if (walrcv_server_version(conn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+
initStringInfo(&seqstr);
initStringInfo(&cmd);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-22 03:28 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 03:39 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2 siblings, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-22 03:28 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; shveta malik <shveta.malik@gmail.com>
On Wed, Jul 22, 2026 at 8:16 AM vignesh C <vignesh21@gmail.com> wrote:
>
> On Tue, 21 Jul 2026 at 12:49, shveta malik <shveta.malik@gmail.com> wrote:
> >
> > The logic of fix looks good. A few comments:
> >
> > 1)
> > Note
> > Sequence synchronization requires the publisher to be running
> > PostgreSQL 19 or later.
> >
> > This note appears to be at odd position in
> > logical-replication-sequences.html. Please have a look at html and
> > move it to end of that section if you agree.
>
> Modified
>
> > 2)
> > + from the tables themselves. Note that
> > + <link linkend="sql-altersubscription-params-refresh-sequences">
> > + <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
> > + re-synchronizes sequences that are already known to the subscription
> > + (see <xref linkend="logical-replication-sequences"/>); in particular, it
> > + requires the publisher to be running <productname>PostgreSQL</productname>
> > + 19 or later. Before relying on it to prepare for a switchover or
> > + failover, confirm that the publisher's version supports sequence
> > + replication and that the sequences of interest are already known to the
> > + subscription.
> >
> > Do you think above is necessary? At subscription creation time, a
> > publisher running a version earlier than PostgreSQL 19 cannot have a
> > publication containing all sequences, so the subscription cannot have
> > such sequences known to it. Furthermore, ALTER SUBSCRIPTION to change
> > publisher-connection will report an error if the required
> > prerequisites are not met. Therefore, asking users to separately
> > verify the publisher version and sequence membership seems redundant
> > to me.
>
> This scenario can be possible, Kuroda-san has reported a similar issue at [1].
>
> > 3)
> > +bool
> > +HasSubscriptionSequences(Oid subid)
> >
> > Do you think we can resuse HasSubscriptionTables instead of
> > duplicating the complete code? We can have
> > HasSubscriptionRelations(subid, bool *has_tables, bool
> > *has_sequences)) Or is it not worth?
>
> Since checking at set connection does not handle all the cases, now
> I'm checking this at refresh sequences because of which this code has
> been now removed.
>
> The attached patch has the changes for the same. This patch also fixes
> the issue reported by Kuroda-san at [1].
>
When this idea was discussed earlier, I had asked whether we could
move the version check from copy_sequences() to the apply worker
(ProcessSequencesForSync) before it starts sequnece sync worker. The
intention was to avoid repeatedly starting the sequence-sync worker
only for it to exit in copy_sequences(). The apply worker itself could
report an error similar to how the backend reports it during REFRESH.
Thoughts?
--
Commit msg says:
Sequence synchronization relies on pg_get_sequence_data(), which was
added in PostgreSQL 19.
IIRC, it is present in PG18 as well, no?
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-22 03:28 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-22 03:39 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 04:49 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-22 03:39 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; shveta malik <shveta.malik@gmail.com>
On Wed, Jul 22, 2026 at 8:58 AM shveta malik <shveta.malik@gmail.com> wrote:
>
> On Wed, Jul 22, 2026 at 8:16 AM vignesh C <vignesh21@gmail.com> wrote:
> >
> > On Tue, 21 Jul 2026 at 12:49, shveta malik <shveta.malik@gmail.com> wrote:
> > >
> > > The logic of fix looks good. A few comments:
> > >
> > > 1)
> > > Note
> > > Sequence synchronization requires the publisher to be running
> > > PostgreSQL 19 or later.
> > >
> > > This note appears to be at odd position in
> > > logical-replication-sequences.html. Please have a look at html and
> > > move it to end of that section if you agree.
> >
> > Modified
> >
> > > 2)
> > > + from the tables themselves. Note that
> > > + <link linkend="sql-altersubscription-params-refresh-sequences">
> > > + <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
> > > + re-synchronizes sequences that are already known to the subscription
> > > + (see <xref linkend="logical-replication-sequences"/>); in particular, it
> > > + requires the publisher to be running <productname>PostgreSQL</productname>
> > > + 19 or later. Before relying on it to prepare for a switchover or
> > > + failover, confirm that the publisher's version supports sequence
> > > + replication and that the sequences of interest are already known to the
> > > + subscription.
> > >
> > > Do you think above is necessary? At subscription creation time, a
> > > publisher running a version earlier than PostgreSQL 19 cannot have a
> > > publication containing all sequences, so the subscription cannot have
> > > such sequences known to it. Furthermore, ALTER SUBSCRIPTION to change
> > > publisher-connection will report an error if the required
> > > prerequisites are not met. Therefore, asking users to separately
> > > verify the publisher version and sequence membership seems redundant
> > > to me.
> >
> > This scenario can be possible, Kuroda-san has reported a similar issue at [1].
> >
> > > 3)
> > > +bool
> > > +HasSubscriptionSequences(Oid subid)
> > >
> > > Do you think we can resuse HasSubscriptionTables instead of
> > > duplicating the complete code? We can have
> > > HasSubscriptionRelations(subid, bool *has_tables, bool
> > > *has_sequences)) Or is it not worth?
> >
> > Since checking at set connection does not handle all the cases, now
> > I'm checking this at refresh sequences because of which this code has
> > been now removed.
> >
> > The attached patch has the changes for the same. This patch also fixes
> > the issue reported by Kuroda-san at [1].
> >
>
> When this idea was discussed earlier, I had asked whether we could
> move the version check from copy_sequences() to the apply worker
> (ProcessSequencesForSync) before it starts sequnece sync worker. The
> intention was to avoid repeatedly starting the sequence-sync worker
> only for it to exit in copy_sequences(). The apply worker itself could
> report an error similar to how the backend reports it during REFRESH.
> Thoughts?
Apply-worker can report a WARNING instead of ERROR. It's no use if the
apply worker keeps exiting and restarting instead of the seqsync
worker.
> --
>
> Commit msg says:
> Sequence synchronization relies on pg_get_sequence_data(), which was
> added in PostgreSQL 19.
>
> IIRC, it is present in PG18 as well, no?
>
> thanks
> Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-22 03:28 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 03:39 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-22 04:49 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-22 05:42 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-22 04:49 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, Jul 22, 2026 at 9:09 AM shveta malik <shveta.malik@gmail.com> wrote:
>
> On Wed, Jul 22, 2026 at 8:58 AM shveta malik <shveta.malik@gmail.com> wrote:
> >
> > When this idea was discussed earlier, I had asked whether we could
> > move the version check from copy_sequences() to the apply worker
> > (ProcessSequencesForSync) before it starts sequnece sync worker. The
> > intention was to avoid repeatedly starting the sequence-sync worker
> > only for it to exit in copy_sequences(). The apply worker itself could
> > report an error similar to how the backend reports it during REFRESH.
> > Thoughts?
>
> Apply-worker can report a WARNING instead of ERROR. It's no use if the
> apply worker keeps exiting and restarting instead of the seqsync
> worker.
>
I feel the WARNINGs can go unnoticed. Anyway, we do restart sync/apply
workers on ERROR or parameter change, so it seems okay to give ERROR
from sequence syncworker itself.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-22 03:28 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 03:39 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 04:49 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-22 05:42 ` shveta malik <shveta.malik@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: shveta malik @ 2026-07-22 05:42 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; shveta malik <shveta.malik@gmail.com>
On Wed, Jul 22, 2026 at 10:19 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Wed, Jul 22, 2026 at 9:09 AM shveta malik <shveta.malik@gmail.com> wrote:
> >
> > On Wed, Jul 22, 2026 at 8:58 AM shveta malik <shveta.malik@gmail.com> wrote:
> > >
> > > When this idea was discussed earlier, I had asked whether we could
> > > move the version check from copy_sequences() to the apply worker
> > > (ProcessSequencesForSync) before it starts sequnece sync worker. The
> > > intention was to avoid repeatedly starting the sequence-sync worker
> > > only for it to exit in copy_sequences(). The apply worker itself could
> > > report an error similar to how the backend reports it during REFRESH.
> > > Thoughts?
> >
> > Apply-worker can report a WARNING instead of ERROR. It's no use if the
> > apply worker keeps exiting and restarting instead of the seqsync
> > worker.
> >
>
> I feel the WARNINGs can go unnoticed. Anyway, we do restart sync/apply
> workers on ERROR or parameter change, so it seems okay to give ERROR
> from sequence syncworker itself.
>
Okay, works for me.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-22 06:28 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2 siblings, 0 replies; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-22 06:28 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; shveta malik <shveta.malik@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
Dear Vignesh,
> The attached patch has the changes for the same. This patch also fixes
> the issue reported by Kuroda-san at [1].
Thanks for updating the patch. I confirmed after applying your patch, the ALTER
SUBSCRIPTION could raise an ERROR like:
```
postgres=# ALTER SERVER server OPTIONS (SET port '5434');
ALTER SERVER
postgres=# ALTER SUBSCRIPTION sub REFRESH SEQUENCES ;
ERROR: cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19
```
... because the backend always confirms the remove version before updating the local catalog.
I have no comments anymore.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-22 10:46 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-23 04:14 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-22 10:46 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, Jul 22, 2026 at 8:16 AM vignesh C <vignesh21@gmail.com> wrote:
>
> The attached patch has the changes for the same. This patch also fixes
> the issue reported by Kuroda-san at [1].
>
The patch used two different error codes
(ERRCODE_FEATURE_NOT_SUPPORTED and
ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE) for the same condition. I
preferred to use ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE in both
places because the error is about the remote server's state (it isn't
new enough), not about a capability this backend fails to implement.
That's exactly what OBJECT_NOT_IN_PREREQUISITE_STATE is for, and it's
what the existing publisher-version guards use. OTOH,
FEATURE_NOT_SUPPORTED reads as "PostgreSQL doesn't implement this" —
misleading here, since sequence sync is implemented; the publisher
just predates it.
I made this change and slightly changed the comments in the patch. See attached.
--
With Regards,
Amit Kapila.
Attachments:
[application/octet-stream] v6-0001-Reject-sequence-synchronization-against-pre-Postg.patch (5.8K, ../../CAA4eK1LwG5rMPiEprQAS0ZsoNMNcnhWy0Aw33k9kv4gPQJtfnQ@mail.gmail.com/2-v6-0001-Reject-sequence-synchronization-against-pre-Postg.patch)
download | inline diff:
From 7e168da216ac4488d017d4a037a240709e89d304 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Wed, 22 Jul 2026 06:45:22 +0530
Subject: [PATCH v7] Reject sequence synchronization against pre-PostgreSQL 19
publishers.
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
---
doc/src/sgml/logical-replication.sgml | 18 +++++++++++++++++-
doc/src/sgml/ref/alter_subscription.sgml | 6 ++++++
src/backend/commands/subscriptioncmds.c | 10 ++++++++++
src/backend/replication/logical/sequencesync.c | 10 ++++++++++
4 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 690598bff98..36298cacb75 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -1818,6 +1818,13 @@ Included in publications:
configuration.
</para>
+ <note>
+ <para>
+ Sequence synchronization requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
+
<sect2 id="sequence-definition-mismatches">
<title>Sequence Definition Mismatches</title>
<para>
@@ -2368,7 +2375,16 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
<command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link>
or by copying the current data from the publisher (perhaps using
<command>pg_dump</command>) or by determining a sufficiently high value
- from the tables themselves.
+ from the tables themselves. Note that
+ <link linkend="sql-altersubscription-params-refresh-sequences">
+ <command>ALTER SUBSCRIPTION ... REFRESH SEQUENCES</command></link> only
+ re-synchronizes sequences that are already known to the subscription
+ (see <xref linkend="logical-replication-sequences"/>); in particular, it
+ requires the publisher to be running <productname>PostgreSQL</productname>
+ 19 or later. Before relying on it to prepare for a switchover or
+ failover, confirm that the publisher's version supports sequence
+ replication and that the sequences of interest are already known to the
+ subscription.
</para>
</listitem>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8d64744375a..6fc3e07a2d5 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -245,6 +245,12 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
sequences are subscribed. Run <literal>REFRESH PUBLICATION</literal>
first if the publication's set of sequences has changed.
</para>
+ <note>
+ <para>
+ Sequence replication requires the publisher to be running
+ <productname>PostgreSQL</productname> 19 or later.
+ </para>
+ </note>
<para>
See <xref linkend="sequence-definition-mismatches"/> for
recommendations on how to handle any warnings about sequence definition
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 63d288a4630..7f946c5b454 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1381,6 +1381,16 @@ AlterSubscription_refresh_seq(Subscription *sub)
/* The publisher connection is only needed for the origin check. */
PG_TRY();
{
+ /*
+ * Sequence synchronization depends on publisher-side functionality
+ * introduced in PostgreSQL 19, so it cannot work against an older
+ * publisher.
+ */
+ if (walrcv_server_version(wrconn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+
check_publications_origin_sequences(wrconn, sub->publications, true,
sub->origin, NULL, 0, sub->name);
}
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 63ad46d7fd7..28d4d011a84 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -444,6 +444,16 @@ copy_sequences(WalReceiverConn *conn)
StringInfoData cmd;
MemoryContext oldctx;
+ /*
+ * Sequence synchronization depends on publisher-side functionality
+ * introduced in PostgreSQL 19, so it cannot work against an older
+ * publisher.
+ */
+ if (walrcv_server_version(conn) < 190000)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
+
initStringInfo(&seqstr);
initStringInfo(&cmd);
--
2.54.0
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-22 10:46 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-23 04:14 ` vignesh C <vignesh21@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: vignesh C @ 2026-07-23 04:14 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, 22 Jul 2026 at 16:16, Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Wed, Jul 22, 2026 at 8:16 AM vignesh C <vignesh21@gmail.com> wrote:
> >
> > The attached patch has the changes for the same. This patch also fixes
> > the issue reported by Kuroda-san at [1].
> >
>
> The patch used two different error codes
> (ERRCODE_FEATURE_NOT_SUPPORTED and
> ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE) for the same condition. I
> preferred to use ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE in both
> places because the error is about the remote server's state (it isn't
> new enough), not about a capability this backend fails to implement.
> That's exactly what OBJECT_NOT_IN_PREREQUISITE_STATE is for, and it's
> what the existing publisher-version guards use. OTOH,
> FEATURE_NOT_SUPPORTED reads as "PostgreSQL doesn't implement this" —
> misleading here, since sequence sync is implemented; the publisher
> just predates it.
>
> I made this change and slightly changed the comments in the patch. See attached.
Thanks for the updated patch.
I verified the following scenarios:
Scenario 1:
1. Create a logical replication setup with sequence publication on
PostgreSQL 20.
2. Change the subscription connection to point to a PostgreSQL 18 publisher.
3. Run 'ALTER SUBSCRIPTION ... REFRESH SEQUENCES'.
The command fails with expected error:
ERROR: cannot synchronize sequences if the publisher is running a
version earlier than PostgreSQL 19
Scenario 2:
1. Create a logical replication setup with sequence publication on
PostgreSQL 20, but create the subscription with 'WITH (enabled =
false)' so that the sequences remain in the INIT state in
'pg_subscription_rel'.
2. Change the subscription connection to point to a PostgreSQL 18 publisher.
3. Enable the subscription, which starts the sequence synchronization worker.
It logs the expected error logs:
ERROR: cannot synchronize sequences if the publisher is running a
version earlier than PostgreSQL 19
I also verified the above scenarios with PostgreSQL 19 logical
replication setup and changing the connection to PostgreSQL 18
publication
I tested both scenarios using the 'CONNECTION' and 'SERVER' variants
of 'SUBSCRIPTION', and in both cases it works as expected.
The changes look good to me.
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 13:16 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-21 11:37 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
1 sibling, 0 replies; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-21 11:37 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
Dear Vignesh,
Thanks for attaching the patch. I think there is a corner case that ALTER
SERVER command changes the connection to the pre-PG19 server.
Firstly, we can create a server for connecting to the PG19 server:
CREATE SERVER server ... OPTIONS (...);
Then we can use the SERVER in the subscription definition:
CREATE SUBSCRIPTION sub SERVER server ...;
After that, we can alter the SERVER to connect to the PG18 server:
ALTER SERVER server OPTION (SET dbname 'postgres' port '5434');
If users run the ALTER SUBSCRITPION REFRESH SEQUENCES command, it could succeed
but the worker will raise the ERROR periodically.
```
ERROR: invalid query response
DETAIL: Expected 11 fields, got 10 fields.
LOG: background worker "logical replication sequencesync worker" (PID 2269290) exited with exit code 1
```
Above can be reported because pg_get_sequence_data() has been available since
PG18 but the returned datatype was different.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-14 03:24 ` Amit Kapila <amit.kapila16@gmail.com>
2 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-14 03:24 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Mon, Jul 13, 2026 at 4:38 PM vignesh C <vignesh21@gmail.com> wrote:
>
> The attached v2 version patch has the changes for the same.
> In addition, it includes a fix for Finding 2
> (default_transaction_read_only) reported by Noah at [1], following the
> approach suggested by Amit at [2].
>
*
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5809,6 +5809,14 @@ InitializeLogRepWorker(void)
*/
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+ /*
+ * Ignore default_transaction_read_only for logical replication workers,
+ * as they need to be able to modify subscriber-side state (e.g. apply
+ * changes, update sequences) regardless of that setting.
+ */
+ SetConfigOption("default_transaction_read_only", "off", PGC_SUSET,
+ PGC_S_OVERRIDE);
+
ApplyContext = AllocSetContextCreate(TopMemoryContext,
"ApplyContext",
ALLOCSET_DEFAULT_SIZES);
As pointed out in my previous email, initially let's make
backpatchable fix by adding this override only for sequencesync worker
in SequenceSyncWorkerMain(). We can discuss in a separate thread to
make this generic for all logical rep workers as a HEAD-only patch.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-13 05:37 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-14 04:54 ` Amit Kapila <amit.kapila16@gmail.com>
2 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-14 04:54 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: shveta malik <shveta.malik@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Mon, Jul 13, 2026 at 4:38 PM vignesh C <vignesh21@gmail.com> wrote:
>
Comments on 0002:
================
*
/*
- * Sequence synchronization relies on pg_get_sequence_data(), which
- * is only available since PostgreSQL 19. Fail immediately with a
- * clear diagnosis instead of resetting the sequences to INIT state
- * and letting a sequencesync worker repeatedly fail trying to fetch
+ * Sequence synchronization relies on pg_get_sequence_data(), which is
+ * only available since PostgreSQL 19. Fail immediately with a clear
+ * diagnosis instead of resetting the sequences to INIT state and
+ * letting a sequencesync worker repeatedly fail trying to fetch
* sequence data with a query that can never succeed against this
This change doesn't seem to belong to this patch.
*
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot execute %s while a sequence synchronization worker
for subscription \"%s\" is still running",
+ "ALTER SUBSCRIPTION ... REFRESH SEQUENCES",
How about a slightly shorter message like: "cannot execute %s while a
sequence synchronization worker is running"?
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-11 20:14 ` Noah Misch <noah@leadboat.com>
2026-07-13 03:23 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: Noah Misch @ 2026-07-11 20:14 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Sat, Jul 11, 2026 at 10:31:06AM +0530, Amit Kapila wrote:
> On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > A Fable 5 review of logical replication of sequences found a way to get
> > subscribed sequences into READY state despite the subscriber side having data
> > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > I reviewed the test, and I think it identifies a genuine defect.
>
> Good catch. We have following ways to fix: (a) As mentioned by
> Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> sequencesync worker is in progress, we can either make the command
> wait till the sequencesync is finished, return ERROR suggesting
> sequence sync already in-progress, or first stop the sequencesync
> worker and then complete the command and let the worker restart after
> REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> that are not in ready state as proposed by Vignesh. Shall we
> additionally add a Note for user to ensure seuencesync worker is not
> in-progress before REFRESH SEQUENCES command?
>
> Do you have any preference? I think WARNING+HINT should be sufficient
> for users as this shouldn't be a common scenario but going the other
> way is also fine.
I haven't formed a preference, but I would use these principles to decide.
Assume we eventually support continuous, WAL-decoded replication of sequences,
not just snapshot replication. Which choice would make sequence replication
behave most like table replication under the analogous concurrent actions?
If it's an ERROR, I'd probably omit the doc note, since the ERROR would be
clear and unsurprising. In general, there's no need to document everything
that causes an error. Documentation is most useful when an error is
surprising or affects how the user writes the application.
If the command merely raises a WARNING, documentation becomes more important,
because warnings are easy to overlook. That is also an argument against a
WARNING: if the user must take action to avoid incorrect replicated state, it
is risky to rely on the user noticing one.
Does that help?
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 20:14 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-13 03:23 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-13 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-13 03:23 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Sun, Jul 12, 2026 at 1:44 AM Noah Misch <noah@leadboat.com> wrote:
>
> On Sat, Jul 11, 2026 at 10:31:06AM +0530, Amit Kapila wrote:
> > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > > A Fable 5 review of logical replication of sequences found a way to get
> > > subscribed sequences into READY state despite the subscriber side having data
> > > older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> > > I reviewed the test, and I think it identifies a genuine defect.
> >
> > Good catch. We have following ways to fix: (a) As mentioned by
> > Kuroda-san, during REFRESH SEQUENCES command, if we detect that the
> > sequencesync worker is in progress, we can either make the command
> > wait till the sequencesync is finished, return ERROR suggesting
> > sequence sync already in-progress, or first stop the sequencesync
> > worker and then complete the command and let the worker restart after
> > REFRESH command is finished; (b) raise a WARNING+HINT for sequences
> > that are not in ready state as proposed by Vignesh. Shall we
> > additionally add a Note for user to ensure seuencesync worker is not
> > in-progress before REFRESH SEQUENCES command?
> >
> > Do you have any preference? I think WARNING+HINT should be sufficient
> > for users as this shouldn't be a common scenario but going the other
> > way is also fine.
>
> I haven't formed a preference, but I would use these principles to decide.
> Assume we eventually support continuous, WAL-decoded replication of sequences,
> not just snapshot replication. Which choice would make sequence replication
> behave most like table replication under the analogous concurrent actions?
>
The other point that we could consider is a future extension of
autosync sequences by allowing syncing at periodic intervals without
user intervention.
> If it's an ERROR, I'd probably omit the doc note, since the ERROR would be
> clear and unsurprising. In general, there's no need to document everything
> that causes an error. Documentation is most useful when an error is
> surprising or affects how the user writes the application.
>
> If the command merely raises a WARNING, documentation becomes more important,
> because warnings are easy to overlook. That is also an argument against a
> WARNING: if the user must take action to avoid incorrect replicated state, it
> is risky to rely on the user noticing one.
>
Sounds reasonable. Considering both your points, I am leaning towards
option (a) (during REFRESH SEQUENCES command, if we detect that the
sequencesync worker is in progress, we make the command return ERROR
suggesting sequence sync is already in-progress). If in future auto
sequence sync functionality is implemented, such an ERROR could still
be appropriate because the auto sync should automatically take care of
syncing. Or, we can even go in the direction that we can use this
command to somehow immediately perform an auto sequence sync cycle
instead of time-based re-sync.
> Does that help?
Yes, thanks. But share your suggestion if you disagree with the above
or any other fix for the problem(s) reported by you.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-11 05:01 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 20:14 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 03:23 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-13 03:35 ` Noah Misch <noah@leadboat.com>
0 siblings, 0 replies; 82+ messages in thread
From: Noah Misch @ 2026-07-13 03:35 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Mon, Jul 13, 2026 at 08:53:42AM +0530, Amit Kapila wrote:
> On Sun, Jul 12, 2026 at 1:44 AM Noah Misch <noah@leadboat.com> wrote:
> Sounds reasonable. Considering both your points, I am leaning towards
> option (a) (during REFRESH SEQUENCES command, if we detect that the
> sequencesync worker is in progress, we make the command return ERROR
> suggesting sequence sync is already in-progress). If in future auto
> sequence sync functionality is implemented, such an ERROR could still
> be appropriate because the auto sync should automatically take care of
> syncing. Or, we can even go in the direction that we can use this
> command to somehow immediately perform an auto sequence sync cycle
> instead of time-based re-sync.
Sounds good.
> > Does that help?
>
> Yes, thanks. But share your suggestion if you disagree with the above
> or any other fix for the problem(s) reported by you.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-13 10:07 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
6 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-13 10:07 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
>
> Fable 5 also wrote a lot more that neither it nor I confirmed by test case
> construction. I'm attaching the report; feel free to disregard. Finding-2
> about default_transaction_read_only=on looks worth fixing if true,
>
Agreed on Finding-2 as well. The issue is that the sequencesync
worker sets the value via SetSequence(), which calls
PreventCommandIfReadOnly("setval()") for non-temp sequences, so with
"default_transaction_read_only=on" on the subscriber the worker's
transaction is read-only and sequence sync fails and never reaches
READY. Table apply is unaffected only because
ExecSimpleRelationInsert() bypasses the executor's
ExecCheckXactReadOnly() path which is an undocumented, untested detail
rather than a stated guarantee.
For a minimal backpatch, we can force the sequencesync worker to run
read-write (e.g. set default_transaction_read_only=off for its session
at startup) so it matches table apply, plus a test that sets the GUC
on the subscriber and verifies sequences reach READY. Separately, it's
worth documenting that logical replication apply is exempt from
default_transaction_read_only — it's a per-transaction default meant
to guard user writes and never makes the node physically read-only —
and making that exemption explicit for all logical replication workers
so tables no longer rely on the bypass. What do you think?
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-15 02:58 ` Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Noah Misch @ 2026-07-15 02:58 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Mon, Jul 13, 2026 at 03:37:54PM +0530, Amit Kapila wrote:
> On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > Fable 5 also wrote a lot more that neither it nor I confirmed by test case
> > construction. I'm attaching the report; feel free to disregard. Finding-2
> > about default_transaction_read_only=on looks worth fixing if true,
>
> Agreed on Finding-2 as well. The issue is that the sequencesync
> worker sets the value via SetSequence(), which calls
> PreventCommandIfReadOnly("setval()") for non-temp sequences, so with
> "default_transaction_read_only=on" on the subscriber the worker's
> transaction is read-only and sequence sync fails and never reaches
> READY. Table apply is unaffected only because
> ExecSimpleRelationInsert() bypasses the executor's
> ExecCheckXactReadOnly() path which is an undocumented, untested detail
> rather than a stated guarantee.
>
> For a minimal backpatch, we can force the sequencesync worker to run
> read-write (e.g. set default_transaction_read_only=off for its session
> at startup) so it matches table apply, plus a test that sets the GUC
> on the subscriber and verifies sequences reach READY. Separately, it's
> worth documenting that logical replication apply is exempt from
> default_transaction_read_only — it's a per-transaction default meant
> to guard user writes and never makes the node physically read-only —
> and making that exemption explicit for all logical replication workers
> so tables no longer rely on the bypass. What do you think?
I wouldn't document those things. default_transaction_read_only just has the
user write "BEGIN READ WRITE" instead of plain "BEGIN". Hence, it's more like
an "are you sure?" prompt than a restrictive guard. It's no surprise that
logical replication apply achieves the equivalent of BEGIN READ WRITE; I don't
see that outcome as an exemption.
If easy, I would have the worker do the C equivalent of "BEGIN READ WRITE"
instead of actually changing the GUC. That makes it clear exactly which areas
are overriding the default. But changing the GUC is fine.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-15 11:53 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-20 06:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 2 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-15 11:53 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Wed, Jul 15, 2026 at 8:28 AM Noah Misch <noah@leadboat.com> wrote:
>
> On Mon, Jul 13, 2026 at 03:37:54PM +0530, Amit Kapila wrote:
> > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > > Fable 5 also wrote a lot more that neither it nor I confirmed by test case
> > > construction. I'm attaching the report; feel free to disregard. Finding-2
> > > about default_transaction_read_only=on looks worth fixing if true,
> >
> > Agreed on Finding-2 as well. The issue is that the sequencesync
> > worker sets the value via SetSequence(), which calls
> > PreventCommandIfReadOnly("setval()") for non-temp sequences, so with
> > "default_transaction_read_only=on" on the subscriber the worker's
> > transaction is read-only and sequence sync fails and never reaches
> > READY. Table apply is unaffected only because
> > ExecSimpleRelationInsert() bypasses the executor's
> > ExecCheckXactReadOnly() path which is an undocumented, untested detail
> > rather than a stated guarantee.
> >
> > For a minimal backpatch, we can force the sequencesync worker to run
> > read-write (e.g. set default_transaction_read_only=off for its session
> > at startup) so it matches table apply, plus a test that sets the GUC
> > on the subscriber and verifies sequences reach READY. Separately, it's
> > worth documenting that logical replication apply is exempt from
> > default_transaction_read_only — it's a per-transaction default meant
> > to guard user writes and never makes the node physically read-only —
> > and making that exemption explicit for all logical replication workers
> > so tables no longer rely on the bypass. What do you think?
>
> I wouldn't document those things. default_transaction_read_only just has the
> user write "BEGIN READ WRITE" instead of plain "BEGIN". Hence, it's more like
> an "are you sure?" prompt than a restrictive guard. It's no surprise that
> logical replication apply achieves the equivalent of BEGIN READ WRITE; I don't
> see that outcome as an exemption.
>
> If easy, I would have the worker do the C equivalent of "BEGIN READ WRITE"
> instead of actually changing the GUC. That makes it clear exactly which areas
> are overriding the default. But changing the GUC is fine.
>
Fair enough. I think this means we need to set XactReadOnly as false
each time after StartTransactionCommand() (where required) as we are
doing in snapbuild.c. There seems to be multiple places and some care
is required unless we want to do it each time after
StartTransactionCommand(). So, I prefer the GUC approach and we are
already overriding it for session_replication_role and search_path
GUC's. The one minor difference could be that for PG19, we set it only
for sequencesync worker and in HEAD for all workers.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-15 11:56 ` Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 82+ messages in thread
From: Noah Misch @ 2026-07-15 11:56 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh21@gmail.com; pgsql-hackers
On Wed, Jul 15, 2026 at 05:23:05PM +0530, Amit Kapila wrote:
> On Wed, Jul 15, 2026 at 8:28 AM Noah Misch <noah@leadboat.com> wrote:
> > On Mon, Jul 13, 2026 at 03:37:54PM +0530, Amit Kapila wrote:
> > > On Fri, Jul 10, 2026 at 10:22 AM Noah Misch <noah@leadboat.com> wrote:
> > > > Fable 5 also wrote a lot more that neither it nor I confirmed by test case
> > > > construction. I'm attaching the report; feel free to disregard. Finding-2
> > > > about default_transaction_read_only=on looks worth fixing if true,
> > >
> > > Agreed on Finding-2 as well. The issue is that the sequencesync
> > > worker sets the value via SetSequence(), which calls
> > > PreventCommandIfReadOnly("setval()") for non-temp sequences, so with
> > > "default_transaction_read_only=on" on the subscriber the worker's
> > > transaction is read-only and sequence sync fails and never reaches
> > > READY. Table apply is unaffected only because
> > > ExecSimpleRelationInsert() bypasses the executor's
> > > ExecCheckXactReadOnly() path which is an undocumented, untested detail
> > > rather than a stated guarantee.
> > >
> > > For a minimal backpatch, we can force the sequencesync worker to run
> > > read-write (e.g. set default_transaction_read_only=off for its session
> > > at startup) so it matches table apply, plus a test that sets the GUC
> > > on the subscriber and verifies sequences reach READY. Separately, it's
> > > worth documenting that logical replication apply is exempt from
> > > default_transaction_read_only — it's a per-transaction default meant
> > > to guard user writes and never makes the node physically read-only —
> > > and making that exemption explicit for all logical replication workers
> > > so tables no longer rely on the bypass. What do you think?
> >
> > I wouldn't document those things. default_transaction_read_only just has the
> > user write "BEGIN READ WRITE" instead of plain "BEGIN". Hence, it's more like
> > an "are you sure?" prompt than a restrictive guard. It's no surprise that
> > logical replication apply achieves the equivalent of BEGIN READ WRITE; I don't
> > see that outcome as an exemption.
> >
> > If easy, I would have the worker do the C equivalent of "BEGIN READ WRITE"
> > instead of actually changing the GUC. That makes it clear exactly which areas
> > are overriding the default. But changing the GUC is fine.
>
> Fair enough. I think this means we need to set XactReadOnly as false
> each time after StartTransactionCommand() (where required) as we are
> doing in snapbuild.c. There seems to be multiple places and some care
> is required unless we want to do it each time after
> StartTransactionCommand(). So, I prefer the GUC approach
In that case, changing the GUC works for me.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-16 00:51 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Tom Lane @ 2026-07-16 00:51 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; vignesh21@gmail.com; pgsql-hackers
BF member skink (uses valgrind) has failed
subscription/t/036_sequences.pl twice since f38afa4ab went in.
I can reproduce that locally if I run the postmaster under valgrind.
However, valgrind isn't issuing any complaint AFAICT. It looks like
valgrind simply slows things down enough to expose a race condition.
What is in my subscriber's log is
...
2026-07-15 20:29:08.328 EDT client backend[3612746] 036_sequences.pl LOG: statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
2026-07-15 20:29:09.083 EDT logical replication sequencesync worker[3612758] LOG: logical replication sequence synchronization worker for subscription "regress_seq_sub" has started
2026-07-15 20:29:10.095 EDT logical replication sequencesync worker[3612758] WARNING: insufficient privileges on publisher sequence ("public.regress_s2")
2026-07-15 20:29:10.095 EDT logical replication sequencesync worker[3612758] HINT: Grant SELECT on the sequence to the role used for the replication connection on the publisher.
2026-07-15 20:29:10.097 EDT logical replication sequencesync worker[3612758] ERROR: logical replication sequence synchronization failed for subscription "regress_seq_sub"
2026-07-15 20:29:10.274 EDT postmaster[3612613] LOG: background worker "logical replication sequencesync worker" (PID 3612758) exited with exit code 1
2026-07-15 20:29:10.501 EDT logical replication sequencesync worker[3612763] LOG: logical replication sequence synchronization worker for subscription "regress_seq_sub" has started
2026-07-15 20:29:10.814 EDT client backend[3612766] 036_sequences.pl LOG: statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl ERROR: cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES while a sequence synchronization worker is running
2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl HINT: Try again after the current synchronization completes.
2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl STATEMENT: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
That is, after checking for the "insufficient privileges" case,
we aren't waiting long enough for the new seqsync worker to quiesce.
This wasn't a problem before f38afa4ab, because it wasn't an error
condition for that worker to still be running.
regards, tom lane
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-07-16 02:41 ` vignesh C <vignesh21@gmail.com>
2026-07-16 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 2 replies; 82+ messages in thread
From: vignesh C @ 2026-07-16 02:41 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Noah Misch <noah@leadboat.com>; Amit Kapila <amit.kapila16@gmail.com>; pgsql-hackers
On Thu, 16 Jul 2026 at 06:21, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> BF member skink (uses valgrind) has failed
> subscription/t/036_sequences.pl twice since f38afa4ab went in.
> I can reproduce that locally if I run the postmaster under valgrind.
> However, valgrind isn't issuing any complaint AFAICT. It looks like
> valgrind simply slows things down enough to expose a race condition.
> What is in my subscriber's log is
>
> ...
> 2026-07-15 20:29:08.328 EDT client backend[3612746] 036_sequences.pl LOG: statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
> 2026-07-15 20:29:09.083 EDT logical replication sequencesync worker[3612758] LOG: logical replication sequence synchronization worker for subscription "regress_seq_sub" has started
> 2026-07-15 20:29:10.095 EDT logical replication sequencesync worker[3612758] WARNING: insufficient privileges on publisher sequence ("public.regress_s2")
> 2026-07-15 20:29:10.095 EDT logical replication sequencesync worker[3612758] HINT: Grant SELECT on the sequence to the role used for the replication connection on the publisher.
> 2026-07-15 20:29:10.097 EDT logical replication sequencesync worker[3612758] ERROR: logical replication sequence synchronization failed for subscription "regress_seq_sub"
> 2026-07-15 20:29:10.274 EDT postmaster[3612613] LOG: background worker "logical replication sequencesync worker" (PID 3612758) exited with exit code 1
> 2026-07-15 20:29:10.501 EDT logical replication sequencesync worker[3612763] LOG: logical replication sequence synchronization worker for subscription "regress_seq_sub" has started
> 2026-07-15 20:29:10.814 EDT client backend[3612766] 036_sequences.pl LOG: statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
> 2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl ERROR: cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES while a sequence synchronization worker is running
> 2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl HINT: Try again after the current synchronization completes.
> 2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl STATEMENT: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
>
> That is, after checking for the "insufficient privileges" case,
> we aren't waiting long enough for the new seqsync worker to quiesce.
> This wasn't a problem before f38afa4ab, because it wasn't an error
> condition for that worker to still be running.
Thanks for reporting this. I was able to reproduce the issue in my
environment as well, and I agree with your analysis of the root cause.
Here's a breakdown of what is happening from the logs at [1]:
Here, the sequence synchronization worker fails due to insufficient
privileges on the publisher sequence, as shown in the following log:
2026-07-15 23:36:03.801 CEST [1681065][logical replication
sequencesync worker][121/0:0] WARNING: insufficient privileges on
publisher sequence ("public.regress_s2")
2026-07-15 23:36:03.801 CEST [1681065][logical replication
sequencesync worker][121/0:0] HINT: Grant SELECT on the sequence to
the role used for the replication connection on the publisher.
The worker then exits with:
2026-07-15 23:36:03.813 CEST [1681065][logical replication
sequencesync worker][121/0:0] ERROR: logical replication sequence
synchronization failed for subscription "regress_seq_sub"
Since not all sequences have reached the READY state, the sequence
sync worker get restarted:
2026-07-15 23:36:05.523 CEST [1684606][logical replication
sequencesync worker][122/6:0] LOG: logical replication sequence
synchronization worker for subscription "regress_seq_sub" has started
At nearly the same time, the test executes another ALTER SUBSCRIPTION
... REFRESH SEQUENCES, which fails because a sequence synchronization
worker is already running:
2026-07-15 23:36:05.523 CEST [1684606][logical replication
sequencesync worker][122/6:0] LOG: logical replication sequence
synchronization worker for subscription "regress_seq_sub" has started
2026-07-15 23:36:07.081 CEST [1685689][client backend][31/2:0] LOG:
statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
2026-07-15 23:36:07.215 CEST [1685689][client backend][31/2:0] ERROR:
cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES while a
sequence synchronization worker is running
The intent of this test is to verify the "insufficient privileges"
error first, and then verify the behavior for missing sequences. Since
the affected sequence has not yet reached the READY state, the apply
worker will automatically launch a new sequence synchronization
worker. Therefore, there is no need to issue an explicit ALTER
SUBSCRIPTION ... REFRESH SEQUENCES in this test.
I think we can simply remove the REFRESH SEQUENCES command and add a
comment explaining that the sequence synchronization worker is
restarted automatically while there are sequences that are not yet in
the READY state.
The attached patch has the changes for the same.
[1] - https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&dt=2026-07-15%2018%3A16%3A44
Regards,
Vignesh
Attachments:
[application/octet-stream] 0001-Test-avoid-redundant-REFRESH-SEQUENCES-in-036_sequen.patch (1.6K, ../../CALDaNm2myhhkoctERWc2x9xA2N3eJojk9h-mAPi-oXatq+ivMA@mail.gmail.com/2-0001-Test-avoid-redundant-REFRESH-SEQUENCES-in-036_sequen.patch)
download | inline diff:
From 5effed3466ebae18c42a46afcc0827ea0879348c Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 16 Jul 2026 07:47:08 +0530
Subject: [PATCH] Test: avoid redundant REFRESH SEQUENCES in 036_sequences
The TAP test issued ALTER SUBSCRIPTION ... REFRESH SEQUENCES after
verifying the "insufficient privileges" error. However, since the failed
sequence has not yet reached the READY state, the apply worker
automatically restarts the sequence synchronization worker.
As a result, the explicit REFRESH SEQUENCES can race with the restarted
worker and fail with:
ERROR: cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES
while a sequence synchronization worker is running
Remove the unnecessary REFRESH SEQUENCES command.
---
src/test/subscription/t/036_sequences.pl | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 8b02b24a7e9..42386b18f07 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -274,9 +274,8 @@ $node_publisher->safe_psql('postgres', qq(DROP SEQUENCE regress_s2;));
$log_offset = -s $node_subscriber->logfile;
-$node_subscriber->safe_psql('postgres',
- "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
-
+# No need to issue REFRESH SEQUENCES. The sequence synchronization worker is
+# restarted automatically because regress_s2 is not yet in the READY state.
$node_subscriber->wait_for_log(
qr/WARNING: ( [A-Z0-9]+:)? missing sequence on publisher \("public.regress_s2"\)/,
$log_offset);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-16 02:58 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 04:28 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: Tom Lane @ 2026-07-16 02:58 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; Amit Kapila <amit.kapila16@gmail.com>; pgsql-hackers
vignesh C <vignesh21@gmail.com> writes:
> On Thu, 16 Jul 2026 at 06:21, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> That is, after checking for the "insufficient privileges" case,
>> we aren't waiting long enough for the new seqsync worker to quiesce.
>> This wasn't a problem before f38afa4ab, because it wasn't an error
>> condition for that worker to still be running.
> I think we can simply remove the REFRESH SEQUENCES command and add a
> comment explaining that the sequence synchronization worker is
> restarted automatically while there are sequences that are not yet in
> the READY state.
Well, that would fix this test, but I wonder if this result isn't
telling us that f38afa4ab has created a failure condition that will
bite real users. Why would the user issuing REFRESH SEQUENCES be
aware of whether there's a sync worker running right then? Why
should it be his problem to avoid that?
regards, tom lane
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-16 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-07-16 04:28 ` Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-16 04:28 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Thu, Jul 16, 2026 at 8:28 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> vignesh C <vignesh21@gmail.com> writes:
> > On Thu, 16 Jul 2026 at 06:21, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> That is, after checking for the "insufficient privileges" case,
> >> we aren't waiting long enough for the new seqsync worker to quiesce.
> >> This wasn't a problem before f38afa4ab, because it wasn't an error
> >> condition for that worker to still be running.
>
> > I think we can simply remove the REFRESH SEQUENCES command and add a
> > comment explaining that the sequence synchronization worker is
> > restarted automatically while there are sequences that are not yet in
> > the READY state.
>
> Well, that would fix this test, but I wonder if this result isn't
> telling us that f38afa4ab has created a failure condition that will
> bite real users. Why would the user issuing REFRESH SEQUENCES be
> aware of whether there's a sync worker running right then? Why
> should it be his problem to avoid that?
>
The reject is there for the correctness reason explained in the commit
(otherwise an in-flight worker can mark sequences READY with values it
fetched before the REFRESH, silently losing the refresh). But you
raised a fair point that it's not ideal to make the user mind worker
timing, but I think it will be uncommon in practice:
1. The sequencesync worker isn't a persistent process; it runs only
while some sequences are still non-READY, i.e. while a sync from
CREATE/REFRESH is initiated. Once all are READY, no worker runs and
REFRESH SEQUENCES just works.
2. While a worker is running, REFRESH SEQUENCES is largely redundant
anyway, as the running worker will itself bring those sequences to
READY with current values. It's genuinely needed only to re-pull newer
values for already-READY sequences, and in that state there's no
worker to conflict with.
3. REFRESH SEQUENCES isn't routine. Since ongoing sequence changes
aren't replicated, its primary use is to update sequences before a
planned switchover/failover/upgrade, and at that point the sequences
are normally already READY, so no worker is running.
The main exception is a sync that keeps failing and restarting (e.g.
missing publisher privileges), which is what skink (BF animal) hit,
but even then the user's goal is met once the underlying issue is
fixed, as the auto-restarting worker completes on its own. And the
command errors with a clear hint to retry after the sync completes.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-17 05:17 ` vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-17 05:17 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Noah Misch <noah@leadboat.com>; Amit Kapila <amit.kapila16@gmail.com>; pgsql-hackers
On Thu, 16 Jul 2026 at 08:11, vignesh C <vignesh21@gmail.com> wrote:
>
> On Thu, 16 Jul 2026 at 06:21, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >
> > BF member skink (uses valgrind) has failed
> > subscription/t/036_sequences.pl twice since f38afa4ab went in.
> > I can reproduce that locally if I run the postmaster under valgrind.
> > However, valgrind isn't issuing any complaint AFAICT. It looks like
> > valgrind simply slows things down enough to expose a race condition.
> > What is in my subscriber's log is
> >
> > ...
> > 2026-07-15 20:29:08.328 EDT client backend[3612746] 036_sequences.pl LOG: statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
> > 2026-07-15 20:29:09.083 EDT logical replication sequencesync worker[3612758] LOG: logical replication sequence synchronization worker for subscription "regress_seq_sub" has started
> > 2026-07-15 20:29:10.095 EDT logical replication sequencesync worker[3612758] WARNING: insufficient privileges on publisher sequence ("public.regress_s2")
> > 2026-07-15 20:29:10.095 EDT logical replication sequencesync worker[3612758] HINT: Grant SELECT on the sequence to the role used for the replication connection on the publisher.
> > 2026-07-15 20:29:10.097 EDT logical replication sequencesync worker[3612758] ERROR: logical replication sequence synchronization failed for subscription "regress_seq_sub"
> > 2026-07-15 20:29:10.274 EDT postmaster[3612613] LOG: background worker "logical replication sequencesync worker" (PID 3612758) exited with exit code 1
> > 2026-07-15 20:29:10.501 EDT logical replication sequencesync worker[3612763] LOG: logical replication sequence synchronization worker for subscription "regress_seq_sub" has started
> > 2026-07-15 20:29:10.814 EDT client backend[3612766] 036_sequences.pl LOG: statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
> > 2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl ERROR: cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES while a sequence synchronization worker is running
> > 2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl HINT: Try again after the current synchronization completes.
> > 2026-07-15 20:29:10.837 EDT client backend[3612766] 036_sequences.pl STATEMENT: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
> >
> > That is, after checking for the "insufficient privileges" case,
> > we aren't waiting long enough for the new seqsync worker to quiesce.
> > This wasn't a problem before f38afa4ab, because it wasn't an error
> > condition for that worker to still be running.
>
> Thanks for reporting this. I was able to reproduce the issue in my
> environment as well, and I agree with your analysis of the root cause.
> Here's a breakdown of what is happening from the logs at [1]:
> Here, the sequence synchronization worker fails due to insufficient
> privileges on the publisher sequence, as shown in the following log:
> 2026-07-15 23:36:03.801 CEST [1681065][logical replication
> sequencesync worker][121/0:0] WARNING: insufficient privileges on
> publisher sequence ("public.regress_s2")
> 2026-07-15 23:36:03.801 CEST [1681065][logical replication
> sequencesync worker][121/0:0] HINT: Grant SELECT on the sequence to
> the role used for the replication connection on the publisher.
>
> The worker then exits with:
> 2026-07-15 23:36:03.813 CEST [1681065][logical replication
> sequencesync worker][121/0:0] ERROR: logical replication sequence
> synchronization failed for subscription "regress_seq_sub"
>
> Since not all sequences have reached the READY state, the sequence
> sync worker get restarted:
> 2026-07-15 23:36:05.523 CEST [1684606][logical replication
> sequencesync worker][122/6:0] LOG: logical replication sequence
> synchronization worker for subscription "regress_seq_sub" has started
>
> At nearly the same time, the test executes another ALTER SUBSCRIPTION
> ... REFRESH SEQUENCES, which fails because a sequence synchronization
> worker is already running:
> 2026-07-15 23:36:05.523 CEST [1684606][logical replication
> sequencesync worker][122/6:0] LOG: logical replication sequence
> synchronization worker for subscription "regress_seq_sub" has started
> 2026-07-15 23:36:07.081 CEST [1685689][client backend][31/2:0] LOG:
> statement: ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES
> 2026-07-15 23:36:07.215 CEST [1685689][client backend][31/2:0] ERROR:
> cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES while a
> sequence synchronization worker is running
>
> The intent of this test is to verify the "insufficient privileges"
> error first, and then verify the behavior for missing sequences. Since
> the affected sequence has not yet reached the READY state, the apply
> worker will automatically launch a new sequence synchronization
> worker. Therefore, there is no need to issue an explicit ALTER
> SUBSCRIPTION ... REFRESH SEQUENCES in this test.
>
> I think we can simply remove the REFRESH SEQUENCES command and add a
> comment explaining that the sequence synchronization worker is
> restarted automatically while there are sequences that are not yet in
> the READY state.
>
> The attached patch has the changes for the same.
While reviewing this further, I noticed another scenario. After the
sequence synchronization worker marks all sequences as READY, if we
pause execution at report_sequence_errors(), an ALTER SUBSCRIPTION ...
REFRESH SEQUENCES can still fail. This means that checking
pg_subscription_rel and verifying that all sequences are in the READY
state is not sufficient. We also need to ensure that the sequence
synchronization worker itself has exited before proceeding, since it
may still be running even after updating the state to READY. I've
updated the test accordingly to include this additional check.
The attached v2 version patch has the changes for the same.
Regards,
Vignesh
Attachments:
[application/octet-stream] v2-0001-Avoid-redundant-REFRESH-SEQUENCES-in-036_sequence.patch (3.3K, ../../CALDaNm2mSAAjAX3d=E-XUXWZgVyy-dtcAhh+whe0a=6JTM0Bgg@mail.gmail.com/2-v2-0001-Avoid-redundant-REFRESH-SEQUENCES-in-036_sequence.patch)
download | inline diff:
From a91433161b14e2efa802d5edff9e7a082aed3e10 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 16 Jul 2026 07:47:08 +0530
Subject: [PATCH v2] Avoid redundant REFRESH SEQUENCES in 036_sequences
The TAP test issued ALTER SUBSCRIPTION ... REFRESH SEQUENCES after
verifying the "insufficient privileges" error. However, since the failed
sequence has not yet reached the READY state, the apply worker
automatically restarts the sequence synchronization worker.
As a result, the explicit REFRESH SEQUENCES can race with the restarted
worker and fail with:
ERROR: cannot execute ALTER SUBSCRIPTION ... REFRESH SEQUENCES
while a sequence synchronization worker is running
Remove the unnecessary REFRESH SEQUENCES command.
Additionally, synced_query only checked pg_subscription_rel.srsubstate
for 'r', but a sequence synchronization worker sets that state before it
actually exits (e.g. it may still be reporting warnings/errors and doing
cleanup). Since the worker remains registered in shared memory during
that window, any subsequent ALTER SUBSCRIPTION ... REFRESH
SEQUENCES/PUBLICATION issued right after polling synced_query can still
hit the same "sequence synchronization worker is running" error.
Strengthen synced_query to also wait until pg_stat_subscription no
longer reports a running 'sequence synchronization' worker.
---
src/test/subscription/t/036_sequences.pl | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index b3b3b20f82b..d6da5bbc9a9 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -57,9 +57,16 @@ $node_subscriber->safe_psql('postgres',
"CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub"
);
-# Wait for initial sync to finish
+# Wait for initial sync to finish. Besides checking that all sequences have
+# reached the 'r' (ready) state, also make sure that no sequence
+# synchronization worker is still running. A worker can remain registered for
+# a short time after marking its sequences ready (e.g. while reporting
+# errors/warnings before exiting), and issuing ALTER SUBSCRIPTION ... REFRESH
+# SEQUENCES/PUBLICATION while it is still running would fail with "cannot
+# execute ... while a sequence synchronization worker is running".
my $synced_query =
- "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+ "SELECT (SELECT count(1) FROM pg_subscription_rel WHERE srsubstate NOT IN ('r')) = 0 "
+ . "AND (SELECT count(1) FROM pg_stat_subscription WHERE worker_type = 'sequence synchronization') = 0;";
$node_subscriber->poll_query_until('postgres', $synced_query)
or die "Timed out while waiting for subscriber to synchronize data";
@@ -328,9 +335,8 @@ $node_publisher->safe_psql('postgres', qq(DROP SEQUENCE regress_s2;));
$log_offset = -s $node_subscriber->logfile;
-$node_subscriber->safe_psql('postgres',
- "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
-
+# No need to issue REFRESH SEQUENCES. The sequence synchronization worker is
+# restarted automatically because regress_s2 is not yet in the READY state.
$node_subscriber->wait_for_log(
qr/WARNING: ( [A-Z0-9]+:)? missing sequence on publisher \("public.regress_s2"\)/,
$log_offset);
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-17 05:33 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Tom Lane @ 2026-07-17 05:33 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; Amit Kapila <amit.kapila16@gmail.com>; pgsql-hackers
[ please, please, please: proper bottom-quoting does not mean to quote
the entire damn thread and then add a few lines of new material. The
point of quoting is just to *briefly* remind readers of the topic. ]
vignesh C <vignesh21@gmail.com> writes:
> While reviewing this further, I noticed another scenario. After the
> sequence synchronization worker marks all sequences as READY, if we
> pause execution at report_sequence_errors(), an ALTER SUBSCRIPTION ...
> REFRESH SEQUENCES can still fail. This means that checking
> pg_subscription_rel and verifying that all sequences are in the READY
> state is not sufficient. We also need to ensure that the sequence
> synchronization worker itself has exited before proceeding, since it
> may still be running even after updating the state to READY. I've
> updated the test accordingly to include this additional check.
I grow even more skeptical that f38afa4ab was a good idea. It seems
inevitable that it will cause annoying failures in the field.
I think that our goal here needs to be to fix the backend, not
band-aid these existing, always-worked-before test cases.
Could we, on detecting the race condition problem, wait awhile
to see if it resolves? Or simply exit and let the REFRESH be
a no-op?
regards, tom lane
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
@ 2026-07-17 06:44 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-17 06:44 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Fri, Jul 17, 2026 at 11:03 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Could we, on detecting the race condition problem, wait awhile
> to see if it resolves?
>
Yes, we can do that. But it may not be completely reliable because
sequencesync worker can exit and restart due to some ERROR. In those
cases, again we can miss synchronizing the latest value of sequence.
>
> Or simply exit and let the REFRESH be
> a no-op?
>
But this will bring us to where we are without commit f38afa4ab. We
wanted to make users aware that REFRESH may not have refreshed all
sequences, so do you think a LOG/WARNING with a hint be better option
or we can always points user to our documentation [1] (Refreshing
Out-of-Sync Sequences) where we documented how ensure sequences are
synced?
[1] - https://www.postgresql.org/docs/19/logical-replication-sequences.html#LOGICAL-REPLICATION-SEQUENCES
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-17 11:53 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-17 11:53 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Fri, Jul 17, 2026 at 12:14 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Fri, Jul 17, 2026 at 11:03 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >
> > Could we, on detecting the race condition problem, wait awhile
> > to see if it resolves?
> >
>
> Yes, we can do that. But it may not be completely reliable because
> sequencesync worker can exit and restart due to some ERROR. In those
> cases, again we can miss synchronizing the latest value of sequence.
>
Thinking some more on this idea, we can achieve to close the race
condition Noah reported without giving ERROR. The idea is that REFRESH
SEQUENCES can acquired lock on pg_subscription_rel in
AccessExclusiveLock to prevent a race with the apply worker
re-launching a sequence sync worker while we reset the states. Even if
a new worker starts, it can't progress: it opens pg_subscription_rel
in AccessShareLock mode in LogicalRepSyncSequences() and will block
until we commit, by which time the sequences are INIT and it will sync
the latest values. After acquiring, REFRESH SEQUENCES command can stop
the sequencesync worker and then made all sequence states to INIT. The
stop-and-restart design along with lock ensures that after REFRESH
SEQUENCES is finished all sequences will be guaranteed to have new
values. Attached patch implements this idea. I did basic testing and
it seems to be working but needs more review and test. I also want to
once review the decision of locking pg_subscription_rel.
Note: I'll think some more on the above idea and if I or someone
didn't find any hole in it then will revert f38afa4ab tomorrow and
then make this patch ready early next week.
--
With Regards,
Amit Kapila.
Attachments:
[application/octet-stream] v1-0001-Handle-concurrent-sequence-refreshes.patch (6.4K, ../../CAA4eK1+WE+wRf5M5S6sFqmshhN7KhHp7w3PZ1Kkr_fXt+d5_Qg@mail.gmail.com/2-v1-0001-Handle-concurrent-sequence-refreshes.patch)
download | inline diff:
From c9ceb242e3e3cb8913c0778fcea45b2a9a48deb8 Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Fri, 17 Jul 2026 15:07:56 +0530
Subject: [PATCH v1] Handle concurrent sequence refreshes.
'ALTER SUBSCRIPTION ... REFRESH SEQUENCES' can race with a running
sequence synchronization worker: if the worker has fetched a sequence's
value from the publisher but not yet marked it READY, a concurrent
refresh that resets the sequence to INIT can be overwritten by the
worker's stale value, silently losing the refresh request.
Handle this by stopping any running sequence sync worker and then
resetting the sequences to INIT. To keep it race-free, lock
pg_subscription_rel in AccessExclusiveLock mode across the stop and the
reset: this blocks the apply worker from re-launching a worker and any
re-launched worker from proceeding, since both read pg_subscription_rel
in AccessShareLock mode. Stopping the worker is also required, because a
worker merely blocked on the lock would otherwise resume after commit
and overwrite the reset with the stale value it had already fetched.
---
src/backend/commands/subscriptioncmds.c | 88 ++++++++++++------------
src/test/subscription/t/036_sequences.pl | 4 --
2 files changed, 44 insertions(+), 48 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 3de358f9374..78c6a22e493 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1362,33 +1362,8 @@ AlterSubscription_refresh_seq(Subscription *sub)
char *err = NULL;
WalReceiverConn *wrconn;
bool must_use_password;
-
- /*
- * Disallow a concurrent REFRESH SEQUENCES while a sequence sync worker
- * for this subscription is still running. This avoids a race where the
- * publisher's sequence advances after the current worker has fetched its
- * value but before it marks the sequence READY. A user may then issue
- * another REFRESH SEQUENCES to synchronize the updated value. Since the
- * affected sequences are already in the INIT state, the running worker
- * has no indication that a new synchronization has been requested. It
- * would then apply the stale value it already fetched and mark the
- * sequence READY, causing the new synchronization request to be lost and
- * preventing the updated publisher values from being synchronized.
- */
- LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
- if (logicalrep_worker_find(WORKERTYPE_SEQUENCESYNC, sub->oid, InvalidOid,
- true))
- {
- LWLockRelease(LogicalRepWorkerLock);
- ereport(ERROR,
- errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- /* translator: %s is an SQL ALTER command */
- errmsg("cannot execute %s while a sequence synchronization worker is running",
- "ALTER SUBSCRIPTION ... REFRESH SEQUENCES"),
- errhint("Try again after the current synchronization completes."));
- }
-
- LWLockRelease(LogicalRepWorkerLock);
+ Relation rel;
+ List *subrel_states;
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
@@ -1403,33 +1378,58 @@ AlterSubscription_refresh_seq(Subscription *sub)
errmsg("subscription \"%s\" could not connect to the publisher: %s",
sub->name, err));
+ /*
+ * Validate the publisher side while holding no lock on pg_subscription_rel,
+ * so the network round-trips do not stall other subscriptions'
+ * synchronization bookkeeping.
+ */
PG_TRY();
{
- List *subrel_states;
-
check_publications_origin_sequences(wrconn, sub->publications, true,
sub->origin, NULL, 0, sub->name);
-
- /* Get local sequence list. */
- subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
- foreach_ptr(SubscriptionRelState, subrel, subrel_states)
- {
- Oid relid = subrel->relid;
-
- UpdateSubscriptionRelState(sub->oid, relid, SUBREL_STATE_INIT,
- InvalidXLogRecPtr, false);
- ereport(DEBUG1,
- errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
- get_namespace_name(get_rel_namespace(relid)),
- get_rel_name(relid),
- sub->name));
- }
}
PG_FINALLY();
{
walrcv_disconnect(wrconn);
}
PG_END_TRY();
+
+ /*
+ * Reset the sequences to INIT so they get re-synchronized with the latest
+ * publisher values.
+ *
+ * Lock pg_subscription_rel with AccessExclusiveLock to prevent a race with
+ * the apply worker re-launching a sequence sync worker while we reset the
+ * states. Even if a new worker starts, it can't progress: it opens
+ * pg_subscription_rel in AccessShareLock mode in LogicalRepSyncSequences()
+ * and will block until we commit, by which time the sequences are INIT and
+ * it will sync the latest values.
+ *
+ * The lock alone is not enough, though: a worker already running may have
+ * fetched a sequence's value but not yet marked it READY, and would resume
+ * after we commit and overwrite our reset with that stale value. So we also
+ * stop any running sequence sync worker while holding the lock.
+ */
+ rel = table_open(SubscriptionRelRelationId, AccessExclusiveLock);
+
+ logicalrep_worker_stop(WORKERTYPE_SEQUENCESYNC, sub->oid, InvalidOid);
+
+ /* Reset every local sequence of this subscription to INIT. */
+ subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
+ foreach_ptr(SubscriptionRelState, subrel, subrel_states)
+ {
+ Oid relid = subrel->relid;
+
+ UpdateSubscriptionRelState(sub->oid, relid, SUBREL_STATE_INIT,
+ InvalidXLogRecPtr, false);
+ ereport(DEBUG1,
+ errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid),
+ sub->name));
+ }
+
+ table_close(rel, NoLock);
}
/*
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index b3b3b20f82b..77ac9386cd8 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -286,10 +286,6 @@ $node_publisher->safe_psql(
CREATE SEQUENCE regress_s4 START 10 INCREMENT 2;
));
-# Wait for the missing sequence added to be synced
-$node_subscriber->poll_query_until('postgres', $synced_query)
- or die "Timed out while waiting for subscriber to synchronize data";
-
##########
# Ensure that insufficient privileges on the publisher for a sequence
# are reported correctly as a permission issue, not as a missing sequence.
--
2.54.0
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-18 03:35 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 14:40 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-20 04:46 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-07-20 05:50 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
0 siblings, 3 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-18 03:35 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Fri, Jul 17, 2026 at 5:23 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> Thinking some more on this idea, we can achieve to close the race
> condition Noah reported without giving ERROR. The idea is that REFRESH
> SEQUENCES can acquired lock on pg_subscription_rel in
> AccessExclusiveLock to prevent a race with the apply worker
> re-launching a sequence sync worker while we reset the states. Even if
> a new worker starts, it can't progress: it opens pg_subscription_rel
> in AccessShareLock mode in LogicalRepSyncSequences() and will block
> until we commit, by which time the sequences are INIT and it will sync
> the latest values. After acquiring, REFRESH SEQUENCES command can stop
> the sequencesync worker and then made all sequence states to INIT. The
> stop-and-restart design along with lock ensures that after REFRESH
> SEQUENCES is finished all sequences will be guaranteed to have new
> values. Attached patch implements this idea. I did basic testing and
> it seems to be working but needs more review and test. I also want to
> once review the decision of locking pg_subscription_rel.
>
> Note: I'll think some more on the above idea and if I or someone
> didn't find any hole in it then will revert f38afa4ab tomorrow and
> then make this patch ready early next week.
>
I have pushed the revert. Attached patch fixes the race without taking
lock on pg_subscription_rel. This should be race-free because
AlterSubscription() already holds AccessExclusiveLock on the
subscription object. That lock blocks a running worker's
UpdateSubscriptionRelState(), which takes AccessShareLock on the
object, and also any worker the apply worker re-launches, because a
new worker takes AccessShareLock on the object in
InitializeLogRepWorker() before it reads pg_subscription_rel. Such a
worker cannot act on the sequence states until the refresh commits, by
which time they are reset to INIT and it will synchronize the latest
publisher values.
--
With Regards,
Amit Kapila.
Attachments:
[application/octet-stream] v2-0001-Handle-concurrent-sequence-refreshes.patch (4.9K, ../../CAA4eK1LA_zXfXsT1Rn=1R6EOR09s9QnQq9VCqLcXBo3JSydr3w@mail.gmail.com/2-v2-0001-Handle-concurrent-sequence-refreshes.patch)
download | inline diff:
From 11e092eb9823f5b893e6d7d588cb8bbb8fa29d42 Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Sat, 18 Jul 2026 08:59:55 +0530
Subject: [PATCH v2] Handle concurrent sequence refreshes.
'ALTER SUBSCRIPTION ... REFRESH SEQUENCES' can race with a running
sequence synchronization worker. If the worker has fetched a sequence's
value from the publisher but not yet marked it READY, a concurrent
refresh that resets the sequence to INIT can be overwritten by the
worker's stale value, silently losing the refresh request.
Handle this by stopping any running sequence sync worker before
resetting the sequences to INIT. This is race-free because
AlterSubscription() already holds AccessExclusiveLock on the
subscription object. That lock blocks a running worker's
UpdateSubscriptionRelState(), which takes AccessShareLock on the object,
and also any worker the apply worker re-launches, because a new worker
takes AccessShareLock on the object in InitializeLogRepWorker() before
it reads pg_subscription_rel. Such a worker cannot act on the sequence
states until the refresh commits, by which time they are reset to INIT
and it will synchronize the latest publisher values.
---
src/backend/commands/subscriptioncmds.c | 65 ++++++++++++++++++-------
1 file changed, 48 insertions(+), 17 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..0237a61235d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -50,6 +50,7 @@
#include "replication/walsender.h"
#include "replication/worker_internal.h"
#include "storage/lmgr.h"
+#include "storage/lock.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/guc.h"
@@ -1362,6 +1363,7 @@ AlterSubscription_refresh_seq(Subscription *sub)
char *err = NULL;
WalReceiverConn *wrconn;
bool must_use_password;
+ List *subrel_states;
/* Load the library providing us libpq calls. */
load_file("libpqwalreceiver", false);
@@ -1376,33 +1378,62 @@ AlterSubscription_refresh_seq(Subscription *sub)
errmsg("subscription \"%s\" could not connect to the publisher: %s",
sub->name, err));
+ /* The publisher connection is only needed for the origin check. */
PG_TRY();
{
- List *subrel_states;
-
check_publications_origin_sequences(wrconn, sub->publications, true,
sub->origin, NULL, 0, sub->name);
-
- /* Get local sequence list. */
- subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
- foreach_ptr(SubscriptionRelState, subrel, subrel_states)
- {
- Oid relid = subrel->relid;
-
- UpdateSubscriptionRelState(sub->oid, relid, SUBREL_STATE_INIT,
- InvalidXLogRecPtr, false);
- ereport(DEBUG1,
- errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
- get_namespace_name(get_rel_namespace(relid)),
- get_rel_name(relid),
- sub->name));
- }
}
PG_FINALLY();
{
walrcv_disconnect(wrconn);
}
PG_END_TRY();
+
+ /*
+ * Reset the sequences to INIT so they get re-synchronized with the latest
+ * publisher values.
+ *
+ * A sequence sync worker may already be running. If it has fetched a
+ * sequence's value from the publisher but not yet marked it READY, it must
+ * not be allowed to complete that update, as it would overwrite the reset
+ * below with a stale value and silently lose this refresh request. So we
+ * stop any running sequence sync worker before resetting the states.
+ *
+ * This is race-free because AlterSubscription() already holds
+ * AccessExclusiveLock on the subscription object. That lock blocks a
+ * running worker's UpdateSubscriptionRelState(), which takes AccessShareLock
+ * on the object. It also blocks any worker the apply worker re-launches,
+ * because a new worker takes AccessShareLock on the object in
+ * InitializeLogRepWorker() before it reads pg_subscription_rel. Such a
+ * worker cannot act on the states until we commit, by which time they are
+ * reset to INIT and it will sync the latest values.
+ */
+#ifdef USE_ASSERT_CHECKING
+ {
+ LOCKTAG tag;
+
+ SET_LOCKTAG_OBJECT(tag, InvalidOid, SubscriptionRelationId, sub->oid, 0);
+ Assert(LockHeldByMe(&tag, AccessExclusiveLock, true));
+ }
+#endif
+
+ logicalrep_worker_stop(WORKERTYPE_SEQUENCESYNC, sub->oid, InvalidOid);
+
+ /* Reset every local sequence of this subscription to INIT. */
+ subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
+ foreach_ptr(SubscriptionRelState, subrel, subrel_states)
+ {
+ Oid relid = subrel->relid;
+
+ UpdateSubscriptionRelState(sub->oid, relid, SUBREL_STATE_INIT,
+ InvalidXLogRecPtr, false);
+ ereport(DEBUG1,
+ errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
+ get_namespace_name(get_rel_namespace(relid)),
+ get_rel_name(relid),
+ sub->name));
+ }
}
/*
--
2.54.0
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-18 14:40 ` vignesh C <vignesh21@gmail.com>
2026-07-20 05:16 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2 siblings, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-18 14:40 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Sat, 18 Jul 2026 at 09:05, Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> I have pushed the revert. Attached patch fixes the race without taking
> lock on pg_subscription_rel.
The changes look good to me. I verified the behavior using a debugger
on both the PG20 and PG19 branches, confirming that a concurrent
refresh successfully stops the sequence sync worker and resets the
sequences to their initial state. I also verified the fix using the
attached injection point test. I'm not entirely sure if we should
include this test in the final commit, as it requires introducing a
new injection point to the codebase.
Regards,
Vignesh
Attachments:
[application/octet-stream] 0001-Test-add-test-for-REFRESH-SEQUENCES-stopping-a-runni.patch (5.6K, ../../CALDaNm2RP5QAF0GTcyyEsCx8SZHrRtEB4ET-bp6JfFJUqOG-jw@mail.gmail.com/2-0001-Test-add-test-for-REFRESH-SEQUENCES-stopping-a-runni.patch)
download | inline diff:
From ad7371272d9e12f5bfeb6c89474916772480f04e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sat, 18 Jul 2026 18:28:02 +0530
Subject: [PATCH] Test: add test for REFRESH SEQUENCES stopping a running
worker
ALTER SUBSCRIPTION ... REFRESH SEQUENCES stops any running sequence
synchronization worker before resetting sequences to INIT, rather
than failing or letting the worker apply a stale value it had
already fetched. Add a test to 036_sequences.pl that uses the
injection point at sequencesync-before-copy-sequence to block a
worker after it has fetched a sequence's publisher value but before
applying it, issue a second REFRESH SEQUENCES while the worker is
blocked, and confirm it succeeds, resets the sequence to INIT, and
that a freshly launched worker converges on the publisher's latest
value rather than the stale one.
---
.../replication/logical/sequencesync.c | 4 +
src/test/subscription/t/036_sequences.pl | 84 +++++++++++++++++++
2 files changed, 88 insertions(+)
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 63ad46d7fd7..5d7d8505d9d 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -65,6 +65,7 @@
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/injection_point.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -561,8 +562,11 @@ copy_sequences(WalReceiverConn *conn)
sync_status = get_and_validate_seq_info(slot, &sequence_rel,
&seqinfo, &seqidx);
if (sync_status == COPYSEQ_SUCCESS)
+ {
+ INJECTION_POINT("sequencesync-before-copy-sequence", NULL);
sync_status = copy_sequence(seqinfo,
sequence_rel->rd_rel->relowner);
+ }
switch (sync_status)
{
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 77ac9386cd8..1328cf47805 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -242,6 +242,90 @@ $node_publisher->safe_psql(
$node_subscriber->poll_query_until('postgres', $synced_query)
or die "Timed out while waiting for subscriber to synchronize data";
+##########
+# ALTER SUBSCRIPTION ... REFRESH SEQUENCES must not fail merely because a
+# sequencesync worker is already running for this subscription. Instead, it
+# must stop the running worker and reset every sequence to INIT again, so a
+# freshly launched worker discards any stale value the stopped worker had
+# already fetched but not yet applied, and resynchronizes with the
+# publisher's latest values.
+##########
+
+my $injection_points_supported =
+ $node_subscriber->check_extension('injection_points');
+
+if ($injection_points_supported != 0)
+{
+ $node_subscriber->append_conf('postgresql.conf',
+ "shared_preload_libraries = 'injection_points'");
+ $node_subscriber->restart;
+
+ $node_subscriber->safe_psql('postgres',
+ "CREATE EXTENSION injection_points;");
+
+ # Block the sequencesync worker just before it applies a sequence's
+ # fetched values.
+ $node_subscriber->safe_psql('postgres',
+ "SELECT injection_points_attach('sequencesync-before-copy-sequence', 'wait');"
+ );
+
+ $node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+ $node_subscriber->wait_for_event('logical replication sequencesync worker',
+ 'sequencesync-before-copy-sequence');
+
+ $result = $node_subscriber->safe_psql('postgres',
+ "SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass"
+ );
+ is($result, 'i', 'sequence is still in INIT state while the worker is blocked');
+
+ # Advance the sequence on the publisher, so the value already fetched by
+ # the blocked worker becomes stale.
+ $node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,200);
+ ));
+
+ # A second REFRESH SEQUENCES while the worker is still blocked must
+ # succeed: it stops the blocked worker (discarding its stale fetched
+ # value) and resets the sequence to INIT again, rather than failing
+ # because a sequence synchronization worker is running.
+ $node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+ $result = $node_subscriber->safe_psql('postgres',
+ "SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass"
+ );
+ is($result, 'i',
+ 'sequence remains in INIT state after the second REFRESH SEQUENCES stops the worker'
+ );
+
+ # Detach the injection point so the freshly launched worker is not
+ # blocked by it too.
+ $node_subscriber->safe_psql('postgres',
+ "SELECT injection_points_wakeup('sequencesync-before-copy-sequence');
+ SELECT injection_points_detach('sequencesync-before-copy-sequence');"
+ );
+
+ $node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+ # The freshly launched worker must reflect the publisher's latest value
+ # (200), not the stale value (1) already fetched by the stopped worker.
+ $result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT last_value, is_called FROM regress_s3;
+ ));
+ is($result, '200|t',
+ 'REFRESH SEQUENCES resynchronizes with the latest publisher value after stopping a running worker'
+ );
+}
+else
+{
+ note 'Injection points not supported by this build';
+}
+
##########
# ALTER SUBSCRIPTION ... REFRESH PUBLICATION should report an error when:
# a) sequence definitions differ between the publisher and subscriber, or
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 14:40 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-20 05:16 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-20 05:48 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-20 05:16 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Sat, Jul 18, 2026 at 8:10 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Sat, 18 Jul 2026 at 09:05, Amit Kapila <amit.kapila16@gmail.com> wrote:
> >
> > I have pushed the revert. Attached patch fixes the race without taking
> > lock on pg_subscription_rel.
>
> The changes look good to me. I verified the behavior using a debugger
> on both the PG20 and PG19 branches, confirming that a concurrent
> refresh successfully stops the sequence sync worker and resets the
> sequences to their initial state. I also verified the fix using the
> attached injection point test. I'm not entirely sure if we should
> include this test in the final commit, as it requires introducing a
> new injection point to the codebase.
>
Thanks for the test, it helps in verifying functionality. However, it
fails intermittently on my Windows environment. IIUC, the stop of
worker being performed by REFRESH doesn't honor injection_point wait.
So, it is not able to coordinate the REFRESH and wait which leads to
this failure. See:
[09:09:58.126](3.805s) ok 10 - sequence is still in INIT state while
the worker is blocked
[09:09:58.453](0.328s) ok 11 - sequence remains in INIT state after
the second REFRESH SEQUENCES stops the worker
[09:15:40.610](342.157s) # poll_query_until timed out executing this query:
# SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');
# expecting this output:
# t
# last actual query output:
# f
# with stderr:
[09:15:40.612](0.002s) # die: Timed out while waiting for subscriber
to synchronize data at
C:/Workspace/code/postgresql/src/test/subscription/t/036_sequences.pl
line 311.
I am attaching the subscriber log as well. I am planning to proceed
without a test as I also can't see a reliable way to write an
automated test for it. But if you can come up with something, we can
add it as well.
--
With Regards,
Amit Kapila.
Attachments:
[application/octet-stream] 036_sequences_subscriber.log (293.5K, ../../CAA4eK1Ju4nexut5F0GuDJYaHfpancrZLfPrA4UypxNtr5RfZkQ@mail.gmail.com/2-036_sequences_subscriber.log)
download
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 14:40 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-20 05:16 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-20 05:48 ` vignesh C <vignesh21@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: vignesh C @ 2026-07-20 05:48 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Mon, 20 Jul 2026 at 10:47, Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> I am attaching the subscriber log as well. I am planning to proceed
> without a test as I also can't see a reliable way to write an
> automated test for it.
I don't have a better test for this right now. Since the fix has
already been thoroughly validated manually and verified through local
debugging, I'm okay with proceeding without a test.
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-20 04:46 ` shveta malik <shveta.malik@gmail.com>
2 siblings, 0 replies; 82+ messages in thread
From: shveta malik @ 2026-07-20 04:46 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Sat, Jul 18, 2026 at 9:05 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Fri, Jul 17, 2026 at 5:23 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
> >
> > Thinking some more on this idea, we can achieve to close the race
> > condition Noah reported without giving ERROR. The idea is that REFRESH
> > SEQUENCES can acquired lock on pg_subscription_rel in
> > AccessExclusiveLock to prevent a race with the apply worker
> > re-launching a sequence sync worker while we reset the states. Even if
> > a new worker starts, it can't progress: it opens pg_subscription_rel
> > in AccessShareLock mode in LogicalRepSyncSequences() and will block
> > until we commit, by which time the sequences are INIT and it will sync
> > the latest values. After acquiring, REFRESH SEQUENCES command can stop
> > the sequencesync worker and then made all sequence states to INIT. The
> > stop-and-restart design along with lock ensures that after REFRESH
> > SEQUENCES is finished all sequences will be guaranteed to have new
> > values. Attached patch implements this idea. I did basic testing and
> > it seems to be working but needs more review and test. I also want to
> > once review the decision of locking pg_subscription_rel.
> >
> > Note: I'll think some more on the above idea and if I or someone
> > didn't find any hole in it then will revert f38afa4ab tomorrow and
> > then make this patch ready early next week.
> >
>
> I have pushed the revert. Attached patch fixes the race without taking
> lock on pg_subscription_rel. This should be race-free because
> AlterSubscription() already holds AccessExclusiveLock on the
> subscription object. That lock blocks a running worker's
> UpdateSubscriptionRelState(), which takes AccessShareLock on the
> object, and also any worker the apply worker re-launches, because a
> new worker takes AccessShareLock on the object in
> InitializeLogRepWorker() before it reads pg_subscription_rel. Such a
> worker cannot act on the sequence states until the refresh commits, by
> which time they are reset to INIT and it will synchronize the latest
> publisher values.
>
The patch looks good. These are my obseravtions while testing:
1. If REFERSH-seq is triggered before the current seq-worker has not
reached its first UpdateSubscriptionRelState(), the seq-worker is
immediately killed and REFRESH proceeds.
2. If REFERSH-seq is triggered while a running seq-worker has already
processed one or few UpdateSubscriptionRelState(), REFRESH waits for
the worker to commit that batch, then the worker is killed and REFRESH
proceeds.
3. If seq-worker is re-launched while REFERSH-seq is changing states,
seq-worker is blocked to acquire AccessShareLock on
SubscriptionRelationId in InitializeLogRepWorker. Worker proceeds only
when REFERSH commits.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:17 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Re: sequencesync worker race with REFRESH SEQUENCES Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-20 05:50 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2 siblings, 0 replies; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-20 05:50 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers; Tom Lane <tgl@sss.pgh.pa.us>
Dear Amit,
> I have pushed the revert. Attached patch fixes the race without taking
> lock on pg_subscription_rel.
Thanks for posting the patch. I confirmed that if ALTER SEQUENCE REFRESH
SEQUENCES are called twice, the second call would wait till the seqencesync
worker exits. As you told, the command will acquire the AccessExclusiveLock for the
subscription object thus it could be blocked till the sequencesync worker releases
the same lock, typically it finishes the transaction.
One concern was that CHECK_FOR_INTERRUPTS() is not called at the beginning of
the batch loop(), but it won't cause the large issue: upcoming walrcv_exec()
handles that.
The main code LGTM. I did not check the test code though.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-20 06:41 ` vignesh C <vignesh21@gmail.com>
2026-07-21 04:17 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-20 06:41 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, 15 Jul 2026 at 17:23, Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> So, I prefer the GUC approach and we are
> already overriding it for session_replication_role and search_path
> GUC's. The one minor difference could be that for PG19, we set it only
> for sequencesync worker and in HEAD for all workers.
Here is a patch to handle the same. v4-0001 patch has the changes to
set default_transaction_read_only guc in InitializeLogRepWorker which
is common to logical replication workers in master branch and v4_PG19
patch has the changes to set default_transaction_read_only guc in
SequenceSyncWorkerMain which is only for sequence sync worker.
Regards,
Vignesh
Attachments:
[application/octet-stream] v4_PG19-0001-Ignore-default_transaction_read_only-in-sequence-.patch (3.7K, ../../CALDaNm0Yt4ZxOjZ=acZr9YRRDt1qAfMzW9Vv_A4yJ0r62H7N_w@mail.gmail.com/2-v4_PG19-0001-Ignore-default_transaction_read_only-in-sequence-.patch)
download | inline diff:
From 892644d7d2442d120ca1a603a751746decc9a770 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 20 Jul 2026 11:42:45 +0530
Subject: [PATCH v4] Ignore default_transaction_read_only in sequence sync
workers
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", even though apply and tablesync workers
are unaffected since they write via direct heap access.
Override default_transaction_read_only to "off" in
SequenceSyncWorkerMain(), so sequence synchronization keeps working
regardless of the subscriber's default setting.
---
.../replication/logical/sequencesync.c | 8 +++
src/test/subscription/t/036_sequences.pl | 51 +++++++++++++++++++
2 files changed, 59 insertions(+)
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 63ad46d7fd7..82f503f1c00 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -835,6 +835,14 @@ SequenceSyncWorkerMain(Datum main_arg)
{
int worker_slot = DatumGetInt32(main_arg);
+ /*
+ * Ignore default_transaction_read_only for sequence synchronization
+ * workers, as they need to be able to modify sequences regardless of that
+ * setting.
+ */
+ SetConfigOption("default_transaction_read_only", "off", PGC_SUSET,
+ PGC_S_OVERRIDE);
+
SetupApplyOrSyncWorker(worker_slot);
start_sequence_sync();
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 77ac9386cd8..dd6fa515df3 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -188,6 +188,57 @@ is($result, '1|f',
'REFRESH PUBLICATION will not sync newly published sequence with copy_data as false'
);
+##########
+# Ensure that ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update
+# sequence values and mark the sequence as ready even when
+# default_transaction_read_only is enabled on the subscriber.
+##########
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = on;
+ SELECT pg_reload_conf();
+));
+
+# Update the existing sequence 'regress_s3' on the publisher
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,100);
+));
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ set default_transaction_read_only = off;
+ ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check - sequence value is updated despite default_transaction_read_only
+# being enabled on the subscriber
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT last_value, is_called FROM regress_s3;
+));
+is($result, '200|t',
+ 'REFRESH SEQUENCES updates sequence value with default_transaction_read_only enabled'
+);
+
+# Check - sequence is marked as ready ('r')
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass;
+));
+is($result, 'r',
+ 'sequence is marked as ready after REFRESH SEQUENCES with default_transaction_read_only enabled'
+);
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = off;
+ SELECT pg_reload_conf();
+));
+
##########
# A sequence dropped concurrently on the publisher, while the sequencesync
# worker's batch query is executing, must be treated the same as any other
--
2.50.1 (Apple Git-155)
[application/octet-stream] v4-0001-Allow-logical-replication-workers-to-ignore-defau.patch (4.0K, ../../CALDaNm0Yt4ZxOjZ=acZr9YRRDt1qAfMzW9Vv_A4yJ0r62H7N_w@mail.gmail.com/3-v4-0001-Allow-logical-replication-workers-to-ignore-defau.patch)
download | inline diff:
From 578a3843e8126d6588645739b199909020ee1fee Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 20 Jul 2026 11:44:07 +0530
Subject: [PATCH v4] Allow logical replication workers to ignore
default_transaction_read_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.
---
src/backend/replication/logical/worker.c | 8 ++++
src/test/subscription/t/036_sequences.pl | 51 ++++++++++++++++++++++++
2 files changed, 59 insertions(+)
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7799266c614..0ff5cef63cd 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5809,6 +5809,14 @@ InitializeLogRepWorker(void)
*/
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
+ /*
+ * Ignore default_transaction_read_only for logical replication workers,
+ * as they need to be able to modify subscriber-side state regardless of
+ * that setting.
+ */
+ SetConfigOption("default_transaction_read_only", "off", PGC_SUSET,
+ PGC_S_OVERRIDE);
+
ApplyContext = AllocSetContextCreate(TopMemoryContext,
"ApplyContext",
ALLOCSET_DEFAULT_SIZES);
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 77ac9386cd8..dd6fa515df3 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -188,6 +188,57 @@ is($result, '1|f',
'REFRESH PUBLICATION will not sync newly published sequence with copy_data as false'
);
+##########
+# Ensure that ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update
+# sequence values and mark the sequence as ready even when
+# default_transaction_read_only is enabled on the subscriber.
+##########
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = on;
+ SELECT pg_reload_conf();
+));
+
+# Update the existing sequence 'regress_s3' on the publisher
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,100);
+));
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ set default_transaction_read_only = off;
+ ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
+));
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+# Check - sequence value is updated despite default_transaction_read_only
+# being enabled on the subscriber
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT last_value, is_called FROM regress_s3;
+));
+is($result, '200|t',
+ 'REFRESH SEQUENCES updates sequence value with default_transaction_read_only enabled'
+);
+
+# Check - sequence is marked as ready ('r')
+$result = $node_subscriber->safe_psql(
+ 'postgres', qq(
+ SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass;
+));
+is($result, 'r',
+ 'sequence is marked as ready after REFRESH SEQUENCES with default_transaction_read_only enabled'
+);
+
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SYSTEM SET default_transaction_read_only = off;
+ SELECT pg_reload_conf();
+));
+
##########
# A sequence dropped concurrently on the publisher, while the sequencesync
# worker's batch query is executing, must be treated the same as any other
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-20 06:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-21 04:17 ` shveta malik <shveta.malik@gmail.com>
2026-07-21 04:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: shveta malik @ 2026-07-21 04:17 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Mon, Jul 20, 2026 at 12:11 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Wed, 15 Jul 2026 at 17:23, Amit Kapila <amit.kapila16@gmail.com> wrote:
> >
> > So, I prefer the GUC approach and we are
> > already overriding it for session_replication_role and search_path
> > GUC's. The one minor difference could be that for PG19, we set it only
> > for sequencesync worker and in HEAD for all workers.
>
> Here is a patch to handle the same. v4-0001 patch has the changes to
> set default_transaction_read_only guc in InitializeLogRepWorker which
> is common to logical replication workers in master branch and v4_PG19
> patch has the changes to set default_transaction_read_only guc in
> SequenceSyncWorkerMain which is only for sequence sync worker.
>
Few suggestions:
1)
+ /*
+ * Ignore default_transaction_read_only for logical replication workers,
+ * as they need to be able to modify subscriber-side state regardless of
+ * that setting.
+ */
Shall we say:
/*
* Ignore default_transaction_read_only for logical replication workers,
* as they need to modify subscriber-side state even when the GUC is enabled.
*/
Or
/*
* Ignore default_transaction_read_only for logical replication workers,
* as they need to modify subscriber-side state, which is not possible in
* a read-only transaction.
*/
2)
+##########
+# Ensure that ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update
+# sequence values and mark the sequence as ready even when
+# default_transaction_read_only is enabled on the subscriber.
+##########
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ set default_transaction_read_only = off;
+ ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
+));
In header we say that REFERSH SEQ can still work with
default_transaction_read_only=on, but here we temprarily set
default_transaction_read_only=off before 'REFRESH SEQUENCES'. This can
be confusing for new readers. We shall change the header comment and
add one comment atop REFRESH.
Suggestion:
##########
# Ensure that the sequencesync worker triggered by
# ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update sequence
# values and mark the sequence as ready even when
# default_transaction_read_only is enabled on the subscriber.
##########
Atop REFRESH:
# ALTER SUBSCRIPTION cannot be executed in a read-only transaction.
# Disable default_transaction_read_only for this session before executing
# REFRESH SEQUENCES. The sequencesync worker will still see
# default_transaction_read_only as being enabled.
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-20 06:41 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-21 04:17 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
@ 2026-07-21 04:35 ` Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-21 04:35 UTC (permalink / raw)
To: shveta malik <shveta.malik@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Tue, Jul 21, 2026 at 9:47 AM shveta malik <shveta.malik@gmail.com> wrote:
>
> On Mon, Jul 20, 2026 at 12:11 PM vignesh C <vignesh21@gmail.com> wrote:
> >
> > On Wed, 15 Jul 2026 at 17:23, Amit Kapila <amit.kapila16@gmail.com> wrote:
> > >
> > > So, I prefer the GUC approach and we are
> > > already overriding it for session_replication_role and search_path
> > > GUC's. The one minor difference could be that for PG19, we set it only
> > > for sequencesync worker and in HEAD for all workers.
> >
> > Here is a patch to handle the same. v4-0001 patch has the changes to
> > set default_transaction_read_only guc in InitializeLogRepWorker which
> > is common to logical replication workers in master branch and v4_PG19
> > patch has the changes to set default_transaction_read_only guc in
> > SequenceSyncWorkerMain which is only for sequence sync worker.
> >
>
> Few suggestions:
>
>
> 1)
> + /*
> + * Ignore default_transaction_read_only for logical replication workers,
> + * as they need to be able to modify subscriber-side state regardless of
> + * that setting.
> + */
>
> Shall we say:
> /*
> * Ignore default_transaction_read_only for logical replication workers,
> * as they need to modify subscriber-side state even when the GUC is enabled.
> */
>
Either of these is fine but now that I have committed the patch, let's
keep the original one.
>
> 2)
>
> +##########
> +# Ensure that ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update
> +# sequence values and mark the sequence as ready even when
> +# default_transaction_read_only is enabled on the subscriber.
> +##########
>
>
> +$node_subscriber->safe_psql(
> + 'postgres', qq(
> + set default_transaction_read_only = off;
> + ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES;
> +));
>
>
> In header we say that REFERSH SEQ can still work with
> default_transaction_read_only=on, but here we temprarily set
> default_transaction_read_only=off before 'REFRESH SEQUENCES'. This can
> be confusing for new readers.
>
The difference is between using SET and ALTER SYSTEM.
>
We shall change the header comment and
> add one comment atop REFRESH.
>
> Suggestion:
>
> ##########
> # Ensure that the sequencesync worker triggered by
> # ALTER SUBSCRIPTION ... REFRESH SEQUENCES can still update sequence
> # values and mark the sequence as ready even when
> # default_transaction_read_only is enabled on the subscriber.
> ##########
>
> Atop REFRESH:
>
> # ALTER SUBSCRIPTION cannot be executed in a read-only transaction.
> # Disable default_transaction_read_only for this session before executing
> # REFRESH SEQUENCES. The sequencesync worker will still see
> # default_transaction_read_only as being enabled.
>
I am not sure if these additional comments are very helpful.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-13 12:52 ` vignesh C <vignesh21@gmail.com>
6 siblings, 0 replies; 82+ messages in thread
From: vignesh C @ 2026-07-13 12:52 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com; pgsql-hackers
On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
>
> A Fable 5 review of logical replication of sequences found a way to get
> subscribed sequences into READY state despite the subscriber side having data
> older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> I reviewed the test, and I think it identifies a genuine defect.
>
> Fable 5 also wrote a lot more that neither it nor I confirmed by test case
> construction. I'm attaching the report; feel free to disregard. Finding-2
> about default_transaction_read_only=on looks worth fixing if true, as do
> finding-7 assert failure
I agree that the assertion failure reported in Finding 7 can occur
when a sequence is dropped concurrently while ALTER SUBSCRIPTION ...
REFRESH SEQUENCES is running. I've analyzed the issue and posted a
proposed fix at [1], the thread where the relevant code was originally
introduced.
[1] - https://www.postgresql.org/message-id/CALDaNm2fHGLeiQKj0r6OG7N9QeayxSmpLrWYJRyt4dL_m3VRWw%40mail.gma...
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-24 04:07 ` vignesh C <vignesh21@gmail.com>
2026-07-24 05:19 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-29 06:55 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
6 siblings, 2 replies; 82+ messages in thread
From: vignesh C @ 2026-07-24 04:07 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com; pgsql-hackers
On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
>
The other findings are gray areas, perhaps.
I had analyzed some more issues and found that these issues need to be fixed:
a) Finding 9: A running sequence synchronization worker continues
processing after ALTER SUBSCRIPTION ... DISABLE. In this case the
leader apply worker only terminated its parallel apply workers on
detach, so a running sequencesync worker could outlive a DISABLE.
logicalrep_worker_detach() now also stops the sequencesync worker. b)
Finding 11: The sequence-removal loop runs after publisher-side
synchronization slots are dropped, violating the existing "slot drops
last" invariant. The fix moves the sequence-removal loop before the
slot-drop loop to keep the drop slots at the end. c) Finding 14: psql
tab completion for ALTER SUBSCRIPTION ... REFRESH PUBLICATION
regressed and no longer suggests WITH (. The fix restores the missing
tab-completion rule. d) Finding 15: pg_stat_subscription reports
misleading timestamp values for the sequence synchronization worker,
and the documentation does not describe its NULL column values. The
fix reports NULL for the appropriate timestamp fields and updates the
monitoring documentation accordingly. e) Finding 16: The catalogs.sgml
description of srsublsn does not describe its meaning for sequence
rows. The fix updates the documentation to describe the semantics of
srsublsn for sequence rows.
There are a few remaining issues that I will discuss in a subsequent email.
Regards,
Vignesh
Attachments:
[application/octet-stream] 0001-Fix-issues-reported-by-claude.patch (9.7K, ../../CALDaNm0+NJNAV_CTLKGH8vsA6xdcu_3405yBNjMC_3zrqpV_ZQ@mail.gmail.com/2-0001-Fix-issues-reported-by-claude.patch)
download | inline diff:
From ce9170d84d96eba77110947808f4e1815232d468 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 24 Jul 2026 07:41:48 +0530
Subject: [PATCH] Fix issues reported by claude
1) Stop a running sequence synchronization worker when
ALTER SUBSCRIPTION ... DISABLE is executed.
2) Restore the invariant that publisher-side synchronization
slots are dropped last by moving the sequence-removal loop
before the slot-drop loop.
3) Restore psql tab completion for
ALTER SUBSCRIPTION ... REFRESH PUBLICATION WITH (.
4) Make pg_stat_subscription report NULL for fields that are
not applicable to sequence synchronization workers, and update
the corresponding documentation.
5) Update the pg_subscription_rel.srsublsn catalog documentation
to describe its semantics for sequence rows.
---
doc/src/sgml/catalogs.sgml | 6 ++-
doc/src/sgml/monitoring.sgml | 19 ++++----
src/backend/commands/subscriptioncmds.c | 56 +++++++++++-----------
src/backend/replication/logical/launcher.c | 2 +-
src/backend/replication/logical/worker.c | 14 +++++-
src/bin/psql/tab-complete.in.c | 3 ++
6 files changed, 60 insertions(+), 40 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..6066c4784f4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8893,7 +8893,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<para>
Remote LSN of the state change used for synchronization coordination
when in <literal>s</literal> or <literal>r</literal> states,
- otherwise null
+ otherwise null. For sequences, this instead holds the publisher
+ sequence's page LSN as of the last synchronization, which does not
+ track replication progress the way it does for tables; see
+ <xref linkend="sequences-out-of-sync"/> for how it is used to detect
+ out-of-sync sequences.
</para></entry>
</row>
</tbody>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index b087d499041..070141bad1f 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -2468,8 +2468,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Process ID of the leader apply worker if this process is a parallel
- apply worker; NULL if this process is a leader apply worker or a table
- synchronization worker
+ apply worker; NULL if this process is a leader apply worker, a table
+ synchronization worker or a sequence synchronization worker
</para></entry>
</row>
@@ -2479,7 +2479,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
OID of the relation that the worker is synchronizing; NULL for the
- leader apply worker and parallel apply workers
+ leader apply worker, parallel apply workers and the sequence
+ synchronization worker
</para></entry>
</row>
@@ -2489,7 +2490,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Last write-ahead log location received, the initial value of
- this field being 0; NULL for parallel apply workers
+ this field being 0; NULL for parallel apply workers and the sequence
+ synchronization worker
</para></entry>
</row>
@@ -2499,7 +2501,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Send time of last message received from origin WAL sender; NULL for
- parallel apply workers
+ parallel apply workers and the sequence synchronization worker
</para></entry>
</row>
@@ -2509,7 +2511,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Receipt time of last message received from origin WAL sender; NULL for
- parallel apply workers
+ parallel apply workers and the sequence synchronization worker
</para></entry>
</row>
@@ -2519,7 +2521,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Last write-ahead log location reported to origin WAL sender; NULL for
- parallel apply workers
+ parallel apply workers and the sequence synchronization worker
</para></entry>
</row>
@@ -2529,7 +2531,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Time of last write-ahead log location reported to origin WAL
- sender; NULL for parallel apply workers
+ sender; NULL for parallel apply workers and the sequence synchronization
+ worker
</para></entry>
</row>
</tbody>
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 7f946c5b454..482db0a8942 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1288,34 +1288,6 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
}
}
- /*
- * Drop the tablesync slots associated with removed tables. This has
- * to be at the end because otherwise if there is an error while doing
- * the database operations we won't be able to rollback dropped slots.
- */
- foreach_ptr(SubRemoveRels, sub_remove_rel, sub_remove_rels)
- {
- if (sub_remove_rel->state != SUBREL_STATE_READY &&
- sub_remove_rel->state != SUBREL_STATE_SYNCDONE)
- {
- char syncslotname[NAMEDATALEN] = {0};
-
- /*
- * For READY/SYNCDONE states we know the tablesync slot has
- * already been dropped by the tablesync worker.
- *
- * For other states, there is no certainty, maybe the slot
- * does not exist yet. Also, if we fail after removing some of
- * the slots, next time, it will again try to drop already
- * dropped slots and fail. For these reasons, we allow
- * missing_ok = true for the drop.
- */
- ReplicationSlotNameForTablesync(sub->oid, sub_remove_rel->relid,
- syncslotname, sizeof(syncslotname));
- ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
- }
- }
-
/*
* Next remove state for sequences we should not care about anymore
* using the data we collected above
@@ -1343,6 +1315,34 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
sub->name));
}
}
+
+ /*
+ * Drop the tablesync slots associated with removed tables. This has
+ * to be at the end because otherwise if there is an error while doing
+ * the database operations we won't be able to rollback dropped slots.
+ */
+ foreach_ptr(SubRemoveRels, sub_remove_rel, sub_remove_rels)
+ {
+ if (sub_remove_rel->state != SUBREL_STATE_READY &&
+ sub_remove_rel->state != SUBREL_STATE_SYNCDONE)
+ {
+ char syncslotname[NAMEDATALEN] = {0};
+
+ /*
+ * For READY/SYNCDONE states we know the tablesync slot has
+ * already been dropped by the tablesync worker.
+ *
+ * For other states, there is no certainty, maybe the slot
+ * does not exist yet. Also, if we fail after removing some of
+ * the slots, next time, it will again try to drop already
+ * dropped slots and fail. For these reasons, we allow
+ * missing_ok = true for the drop.
+ */
+ ReplicationSlotNameForTablesync(sub->oid, sub_remove_rel->relid,
+ syncslotname, sizeof(syncslotname));
+ ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
+ }
+ }
}
PG_FINALLY();
{
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 313e31ff2e3..67ca8ec9b12 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -824,7 +824,7 @@ logicalrep_worker_detach(void)
{
LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc);
- if (isParallelApplyWorker(w))
+ if (isParallelApplyWorker(w) || isSequenceSyncWorker(w))
logicalrep_worker_stop_internal(w, SIGTERM);
}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0ff5cef63cd..0bd19074010 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5985,8 +5985,18 @@ SetupApplyOrSyncWorker(int worker_slot)
*/
/* Initialise stats to a sanish value */
- MyLogicalRepWorker->last_send_time = MyLogicalRepWorker->last_recv_time =
- MyLogicalRepWorker->reply_time = GetCurrentTimestamp();
+ if (am_sequencesync_worker())
+ {
+ MyLogicalRepWorker->last_send_time =
+ MyLogicalRepWorker->last_recv_time =
+ MyLogicalRepWorker->reply_time = 0;
+ }
+ else
+ {
+ MyLogicalRepWorker->last_send_time =
+ MyLogicalRepWorker->last_recv_time =
+ MyLogicalRepWorker->reply_time = GetCurrentTimestamp();
+ }
/* Load the libpq-specific functions */
load_file("libpqwalreceiver", false);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 1cacc8c3ea2..17dcabe755f 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2354,6 +2354,9 @@ match_previous_words(int pattern_id,
/* ALTER SUBSCRIPTION <name> REFRESH */
else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH"))
COMPLETE_WITH("PUBLICATION", "SEQUENCES");
+ /* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION */
+ else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH", "PUBLICATION"))
+ COMPLETE_WITH("WITH (");
/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION WITH ( */
else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH", "PUBLICATION", "WITH", "("))
COMPLETE_WITH("copy_data");
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-24 05:19 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-24 12:38 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-24 05:19 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: amit.kapila16@gmail.com <amit.kapila16@gmail.com>; pgsql-hackers; Noah Misch <noah@leadboat.com>
Dear Vignesh,
> a) Finding 9: A running sequence synchronization worker continues
> processing after ALTER SUBSCRIPTION ... DISABLE. In this case the
> leader apply worker only terminated its parallel apply workers on
> detach, so a running sequencesync worker could outlive a DISABLE.
> logicalrep_worker_detach() now also stops the sequencesync worker.
Hmm, but tablesync workers, which can connect to the publisher by itself are not
terminated by the function. IIUC, tablesync workers can read the subscription
catalog and recognize the catalog. I feel we should follow that approach.
Note: I cannot find the place sequencesync worker calls maybe_reread_subscription().
It might mean that the worker cannot recognize alternations of the subscription.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-24 05:19 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
@ 2026-07-24 12:38 ` vignesh C <vignesh21@gmail.com>
2026-07-24 13:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
0 siblings, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-24 12:38 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; +Cc: amit.kapila16@gmail.com <amit.kapila16@gmail.com>; pgsql-hackers; Noah Misch <noah@leadboat.com>
On Fri, 24 Jul 2026 at 10:49, Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
>
> Hmm, but tablesync workers, which can connect to the publisher by itself are not
> terminated by the function. IIUC, tablesync workers can read the subscription
> catalog and recognize the catalog. I feel we should follow that approach.
Yes it is better to keep sequence synchronization worker also similar.
The attached v2 version has the changes to call
maybe_reread_subscription and identify if the subscription is
disabled.
Regards,
Vignesh
Attachments:
[application/octet-stream] v2-0001-Fix-issues-reported-by-claude.patch (9.9K, ../../CALDaNm0pKs=vWvzNgSTJ6HJpVtWLiAcW2LA8rBvX91MD0i0fPA@mail.gmail.com/2-v2-0001-Fix-issues-reported-by-claude.patch)
download | inline diff:
From b99e2a563cba44e6ed1dfafebb814076229b6681 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Fri, 24 Jul 2026 07:41:48 +0530
Subject: [PATCH v2] Fix issues reported by claude
1) Stop a running sequence synchronization worker when
ALTER SUBSCRIPTION ... DISABLE is executed.
2) Restore the invariant that publisher-side synchronization
slots are dropped last by moving the sequence-removal loop
before the slot-drop loop.
3) Restore psql tab completion for
ALTER SUBSCRIPTION ... REFRESH PUBLICATION WITH (.
4) Make pg_stat_subscription report NULL for fields that are
not applicable to sequence synchronization workers, and update
the corresponding documentation.
5) Update the pg_subscription_rel.srsublsn catalog documentation
to describe its semantics for sequence rows.
---
doc/src/sgml/catalogs.sgml | 6 +-
doc/src/sgml/monitoring.sgml | 19 ++++---
src/backend/commands/subscriptioncmds.c | 56 +++++++++----------
.../replication/logical/sequencesync.c | 2 +
src/backend/replication/logical/worker.c | 14 ++++-
src/bin/psql/tab-complete.in.c | 3 +
6 files changed, 61 insertions(+), 39 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4b474c13917..6066c4784f4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8893,7 +8893,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<para>
Remote LSN of the state change used for synchronization coordination
when in <literal>s</literal> or <literal>r</literal> states,
- otherwise null
+ otherwise null. For sequences, this instead holds the publisher
+ sequence's page LSN as of the last synchronization, which does not
+ track replication progress the way it does for tables; see
+ <xref linkend="sequences-out-of-sync"/> for how it is used to detect
+ out-of-sync sequences.
</para></entry>
</row>
</tbody>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 1ce0ef00799..a209e891b18 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -2473,8 +2473,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Process ID of the leader apply worker if this process is a parallel
- apply worker; NULL if this process is a leader apply worker or a table
- synchronization worker
+ apply worker; NULL if this process is a leader apply worker, a table
+ synchronization worker or a sequence synchronization worker
</para></entry>
</row>
@@ -2484,7 +2484,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
OID of the relation that the worker is synchronizing; NULL for the
- leader apply worker and parallel apply workers
+ leader apply worker, parallel apply workers and the sequence
+ synchronization worker
</para></entry>
</row>
@@ -2494,7 +2495,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Last write-ahead log location received, the initial value of
- this field being 0; NULL for parallel apply workers
+ this field being 0; NULL for parallel apply workers and the sequence
+ synchronization worker
</para></entry>
</row>
@@ -2504,7 +2506,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Send time of last message received from origin WAL sender; NULL for
- parallel apply workers
+ parallel apply workers and the sequence synchronization worker
</para></entry>
</row>
@@ -2514,7 +2516,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Receipt time of last message received from origin WAL sender; NULL for
- parallel apply workers
+ parallel apply workers and the sequence synchronization worker
</para></entry>
</row>
@@ -2524,7 +2526,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Last write-ahead log location reported to origin WAL sender; NULL for
- parallel apply workers
+ parallel apply workers and the sequence synchronization worker
</para></entry>
</row>
@@ -2534,7 +2536,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
<para>
Time of last write-ahead log location reported to origin WAL
- sender; NULL for parallel apply workers
+ sender; NULL for parallel apply workers and the sequence synchronization
+ worker
</para></entry>
</row>
</tbody>
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index d4504b4a0c6..013ac46db07 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1288,34 +1288,6 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
}
}
- /*
- * Drop the tablesync slots associated with removed tables. This has
- * to be at the end because otherwise if there is an error while doing
- * the database operations we won't be able to rollback dropped slots.
- */
- foreach_ptr(SubRemoveRels, sub_remove_rel, sub_remove_rels)
- {
- if (sub_remove_rel->state != SUBREL_STATE_READY &&
- sub_remove_rel->state != SUBREL_STATE_SYNCDONE)
- {
- char syncslotname[NAMEDATALEN] = {0};
-
- /*
- * For READY/SYNCDONE states we know the tablesync slot has
- * already been dropped by the tablesync worker.
- *
- * For other states, there is no certainty, maybe the slot
- * does not exist yet. Also, if we fail after removing some of
- * the slots, next time, it will again try to drop already
- * dropped slots and fail. For these reasons, we allow
- * missing_ok = true for the drop.
- */
- ReplicationSlotNameForTablesync(sub->oid, sub_remove_rel->relid,
- syncslotname, sizeof(syncslotname));
- ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
- }
- }
-
/*
* Next remove state for sequences we should not care about anymore
* using the data we collected above
@@ -1343,6 +1315,34 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
sub->name));
}
}
+
+ /*
+ * Drop the tablesync slots associated with removed tables. This has
+ * to be at the end because otherwise if there is an error while doing
+ * the database operations we won't be able to rollback dropped slots.
+ */
+ foreach_ptr(SubRemoveRels, sub_remove_rel, sub_remove_rels)
+ {
+ if (sub_remove_rel->state != SUBREL_STATE_READY &&
+ sub_remove_rel->state != SUBREL_STATE_SYNCDONE)
+ {
+ char syncslotname[NAMEDATALEN] = {0};
+
+ /*
+ * For READY/SYNCDONE states we know the tablesync slot has
+ * already been dropped by the tablesync worker.
+ *
+ * For other states, there is no certainty, maybe the slot
+ * does not exist yet. Also, if we fail after removing some of
+ * the slots, next time, it will again try to drop already
+ * dropped slots and fail. For these reasons, we allow
+ * missing_ok = true for the drop.
+ */
+ ReplicationSlotNameForTablesync(sub->oid, sub_remove_rel->relid,
+ syncslotname, sizeof(syncslotname));
+ ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
+ }
+ }
}
PG_FINALLY();
{
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 28d4d011a84..76edecbdb91 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -479,6 +479,7 @@ copy_sequences(WalReceiverConn *conn)
TupleTableSlot *slot;
StartTransactionCommand();
+ maybe_reread_subscription();
for (int idx = cur_batch_base_index; idx < n_seqinfos; idx++)
{
@@ -708,6 +709,7 @@ LogicalRepSyncSequences(void)
StringInfoData app_name;
StartTransactionCommand();
+ maybe_reread_subscription();
rel = table_open(SubscriptionRelRelationId, AccessShareLock);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0ff5cef63cd..0bd19074010 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5985,8 +5985,18 @@ SetupApplyOrSyncWorker(int worker_slot)
*/
/* Initialise stats to a sanish value */
- MyLogicalRepWorker->last_send_time = MyLogicalRepWorker->last_recv_time =
- MyLogicalRepWorker->reply_time = GetCurrentTimestamp();
+ if (am_sequencesync_worker())
+ {
+ MyLogicalRepWorker->last_send_time =
+ MyLogicalRepWorker->last_recv_time =
+ MyLogicalRepWorker->reply_time = 0;
+ }
+ else
+ {
+ MyLogicalRepWorker->last_send_time =
+ MyLogicalRepWorker->last_recv_time =
+ MyLogicalRepWorker->reply_time = GetCurrentTimestamp();
+ }
/* Load the libpq-specific functions */
load_file("libpqwalreceiver", false);
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 1cacc8c3ea2..17dcabe755f 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -2354,6 +2354,9 @@ match_previous_words(int pattern_id,
/* ALTER SUBSCRIPTION <name> REFRESH */
else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH"))
COMPLETE_WITH("PUBLICATION", "SEQUENCES");
+ /* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION */
+ else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH", "PUBLICATION"))
+ COMPLETE_WITH("WITH (");
/* ALTER SUBSCRIPTION <name> REFRESH PUBLICATION WITH ( */
else if (Matches("ALTER", "SUBSCRIPTION", MatchAny, MatchAnyN, "REFRESH", "PUBLICATION", "WITH", "("))
COMPLETE_WITH("copy_data");
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-24 05:19 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-24 12:38 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-24 13:31 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-27 05:26 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-24 13:31 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: amit.kapila16@gmail.com <amit.kapila16@gmail.com>; pgsql-hackers; Noah Misch <noah@leadboat.com>
Dear Vignesh,
> Yes it is better to keep sequence synchronization worker also similar.
> The attached v2 version has the changes to call
> maybe_reread_subscription and identify if the subscription is
> disabled.
Thanks for updating the patch. LGTM.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-24 05:19 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-24 12:38 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-24 13:31 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
@ 2026-07-27 05:26 ` Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-27 05:26 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; +Cc: vignesh C <vignesh21@gmail.com>; pgsql-hackers; Noah Misch <noah@leadboat.com>
On Fri, Jul 24, 2026 at 7:01 PM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
>
> > Yes it is better to keep sequence synchronization worker also similar.
> > The attached v2 version has the changes to call
> > maybe_reread_subscription and identify if the subscription is
> > disabled.
>
> Thanks for updating the patch. LGTM.
>
LGTM as well, so pushed.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-29 06:55 ` vignesh C <vignesh21@gmail.com>
2026-07-30 08:22 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
1 sibling, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-29 06:55 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com; pgsql-hackers
On Fri, 24 Jul 2026 at 09:37, vignesh C <vignesh21@gmail.com> wrote:
>
> On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
> >
> The other findings are gray areas, perhaps.
>
> There are a few remaining issues that I will discuss in a subsequent email.
I had a look at the remaining 4 findings that were left, here are my thoughts:
Finding 3: `run_as_owner=false`: one un-SET-ROLE-able sequence owner
stalls all sequence sync, unattributed.
I think this need not be handled. We have already handled the most
common per-sequence failure cases, such as sequence mismatch,
insufficient privileges on the publisher, insufficient privileges on
the subscriber, and sequences skipped because they were removed. Those
are common scenarios that users are expected to encounter during
sequence synchronization. The case described here requires the
subscription owner to be unable to SET ROLE to the sequence owner when
run_as_owner = false, which is a much less common configuration issue.
We can revisit and do it if this gets reported from the field.
Finding 5: Sequencesync worker starved by tablesync workers; REFRESH
SEQUENCES silently deferred
This is handled like this intentionally and need not be handled. The
sequence synchronization worker intentionally shares the same
max_sync_workers_per_subscription budget as table synchronization
workers. This is an existing design choice to limit the number of
concurrent synchronization workers for a subscription, and the
documentation already states that both table and sequence
synchronization workers consume this budget. Prioritizing table
synchronization is also intentional, since a subscription is generally
not considered fully initialized until table synchronization has
completed. Delaying sequence synchronization until worker slots become
available is therefore expected behavior rather than starvation.
Finding 10: REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE:
internal XX000 errors
I don't think this needs to be addressed. REFRESH SEQUENCES is an
administrative command that is expected to be run infrequently, and
concurrently dropping replicated sequences on the subscriber while it
is executing is not a common or recommended operation. The reported
failures arise only from this narrow race and are transient. After
such a failure, the sequence synchronization worker will be restarted
automatically. On the subsequent run, it will either synchronize the
remaining sequences successfully or report an appropriate error.
Finding 18: Restrictions bullet still claims only tables can be replicated
I don't think this needs to be changed. The restriction is describing
the kinds of relations that participate in logical replication itself,
i.e., replicating changes from the publisher to the subscriber.
Sequences are different—they do not participate in ongoing logical
replication. The current feature only provides synchronization of
sequence values on demand (during CREATE SUBSCRIPTION with copy_data,
ALTER SUBSCRIPTION ... REFRESH PUBLICATION, or ALTER SUBSCRIPTION ...
REFRESH SEQUENCES); subsequent sequence changes are not replicated
automatically. Therefore, I don't think the existing restriction is
misleading. The "Replicating Sequences" section already explains the
supported behavior and its limitations, making the distinction between
table replication and sequence synchronization clear. Changing this
bullet could instead blur that distinction by implying that sequences
participate in logical replication in the same way as tables, which
they currently do not.
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-24 04:07 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-29 06:55 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-30 08:22 ` Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-07-30 08:22 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, Jul 29, 2026 at 12:25 PM vignesh C <vignesh21@gmail.com> wrote:
>
> I had a look at the remaining 4 findings that were left, here are my thoughts:
> Finding 3: `run_as_owner=false`: one un-SET-ROLE-able sequence owner
> stalls all sequence sync, unattributed.
> I think this need not be handled. We have already handled the most
> common per-sequence failure cases, such as sequence mismatch,
> insufficient privileges on the publisher, insufficient privileges on
> the subscriber, and sequences skipped because they were removed. Those
> are common scenarios that users are expected to encounter during
> sequence synchronization. The case described here requires the
> subscription owner to be unable to SET ROLE to the sequence owner when
> run_as_owner = false, which is a much less common configuration issue.
> We can revisit and do it if this gets reported from the field.
>
Agreed, I also feel it would be difficult to detect all ERRORs and
skip to next sequence sync. So, we can leave this at least for now.
> Finding 5: Sequencesync worker starved by tablesync workers; REFRESH
> SEQUENCES silently deferred
> This is handled like this intentionally and need not be handled. The
> sequence synchronization worker intentionally shares the same
> max_sync_workers_per_subscription budget as table synchronization
> workers. This is an existing design choice to limit the number of
> concurrent synchronization workers for a subscription, and the
> documentation already states that both table and sequence
> synchronization workers consume this budget. Prioritizing table
> synchronization is also intentional, since a subscription is generally
> not considered fully initialized until table synchronization has
> completed. Delaying sequence synchronization until worker slots become
> available is therefore expected behavior rather than starvation.
>
Right, so we can leave this one as well.
> Finding 10: REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE:
> internal XX000 errors
> I don't think this needs to be addressed. REFRESH SEQUENCES is an
> administrative command that is expected to be run infrequently, and
> concurrently dropping replicated sequences on the subscriber while it
> is executing is not a common or recommended operation. The reported
> failures arise only from this narrow race and are transient. After
> such a failure, the sequence synchronization worker will be restarted
> automatically. On the subsequent run, it will either synchronize the
> remaining sequences successfully or report an appropriate error.
>
Make sense. We can leave this one as well.
> Finding 18: Restrictions bullet still claims only tables can be replicated
> I don't think this needs to be changed. The restriction is describing
> the kinds of relations that participate in logical replication itself,
> i.e., replicating changes from the publisher to the subscriber.
> Sequences are different—they do not participate in ongoing logical
> replication. The current feature only provides synchronization of
> sequence values on demand (during CREATE SUBSCRIPTION with copy_data,
> ALTER SUBSCRIPTION ... REFRESH PUBLICATION, or ALTER SUBSCRIPTION ...
> REFRESH SEQUENCES); subsequent sequence changes are not replicated
> automatically. Therefore, I don't think the existing restriction is
> misleading. The "Replicating Sequences" section already explains the
> supported behavior and its limitations, making the distinction between
> table replication and sequence synchronization clear. Changing this
> bullet could instead blur that distinction by implying that sequences
> participate in logical replication in the same way as tables, which
> they currently do not.
>
The bullet point: "Incremental sequence changes are not
replicated...." on the Restriction page explained the limitations
related to sequences, so I don't see any need to update this section
of docs.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-28 07:02 ` vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
6 siblings, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-07-28 07:02 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com; pgsql-hackers
On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
>
> The other findings are gray areas, perhaps.
I analyzed finding 4: "Gathering scan holds a lock on every INIT
relation in one unbounded transaction."
The sequence synchronization worker acquires a RowExclusiveLock on
every INIT sequence while scanning pg_subscription_rel and retains
those locks until the transaction commits. On subscriptions with a
large number of INIT sequences, this can exhaust the shared lock
table, causing repeated "out of shared memory" failures. The attached
v1-0001 patch addresses this by releasing the lock immediately after
synchronizing each sequence, preventing lock accumulation. It also
acquires an AccessShareLock instead of a RowExclusiveLock, since
AccessShareLock is sufficient to ensure that the sequence's identity
(namespace and name) remains stable against concurrent DROP, RENAME,
and SET SCHEMA operations while the sequence is being synchronized.
The attached v1-0002 patch contains a TAP test that reproduces the
issue on HEAD. I'm attaching it in case anyone wants to reproduce the
problem locally, although I don't think this test needs to be
committed.
Regards,
Vignesh
Attachments:
[application/octet-stream] v1-0001-Avoid-accumulating-relation-locks-during-sequence.patch (2.4K, ../../CALDaNm1jzTXoqXWbnpcOuS7PM-bAFLswcya5fvpqp=H9Y6xEGg@mail.gmail.com/2-v1-0001-Avoid-accumulating-relation-locks-during-sequence.patch)
download | inline diff:
From 3beebde4991a6f00bd5c87978b2403c217143d10 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 27 Jul 2026 17:01:10 +0530
Subject: [PATCH v1 1/2] Avoid accumulating relation locks during sequence
synchronization
The sequence synchronization worker acquired a RowExclusiveLock on every
INIT sequence while scanning pg_subscription_rel and retained those locks
until the transaction committed.
On subscriptions with many INIT sequences, this could exhaust the shared
lock table, causing the worker to repeatedly fail with "out of shared
memory".
The worker only needs to ensure that the sequence's identity (namespace
and name) remains stable while it is being synchronized. An
AccessShareLock is sufficient to prevent concurrent DROP, RENAME, and
SET SCHEMA operations from changing that identity.
Acquire an AccessShareLock instead and release it immediately after the
sequence has been synchronized. This avoids accumulating relation locks
while preserving the required protection against concurrent DDL.
---
src/backend/replication/logical/sequencesync.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index fe506a98c20..72519e651fb 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -752,7 +752,13 @@ LogicalRepSyncSequences(void)
subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
- sequence_rel = try_table_open(subrel->srrelid, RowExclusiveLock);
+ /*
+ * Lock the sequence so its identity (namespace and name) cannot change
+ * under us via a concurrent DROP, RENAME or SET SCHEMA. Release it
+ * right away rather than at transaction end, to avoid accumulating a
+ * lock per sequence.
+ */
+ sequence_rel = try_table_open(subrel->srrelid, AccessShareLock);
/* Skip if sequence was dropped concurrently */
if (!sequence_rel)
@@ -761,7 +767,7 @@ LogicalRepSyncSequences(void)
/* Skip if the relation is not a sequence */
if (sequence_rel->rd_rel->relkind != RELKIND_SEQUENCE)
{
- table_close(sequence_rel, NoLock);
+ table_close(sequence_rel, AccessShareLock);
continue;
}
@@ -779,7 +785,7 @@ LogicalRepSyncSequences(void)
MemoryContextSwitchTo(oldctx);
- table_close(sequence_rel, NoLock);
+ table_close(sequence_rel, AccessShareLock);
}
/* Cleanup */
--
2.50.1 (Apple Git-155)
[application/octet-stream] v1-0002-Add-a-TAP-test-to-reproduce-sequence-sync-lock-ta.patch (6.5K, ../../CALDaNm1jzTXoqXWbnpcOuS7PM-bAFLswcya5fvpqp=H9Y6xEGg@mail.gmail.com/3-v1-0002-Add-a-TAP-test-to-reproduce-sequence-sync-lock-ta.patch)
download | inline diff:
From efc021670d34d2b8bd87944202b9fb41f678d0ca Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 28 Jul 2026 11:22:40 +0530
Subject: [PATCH v1 2/2] Add a TAP test to reproduce sequence sync lock table
exhaustion
Add a regression test that reproduces the failure where the sequence
synchronization worker exhausts the subscriber's shared lock table while
gathering sequences to synchronize.
The test configures a small shared lock table and creates enough sequences
in the INIT state so that the worker opens all of them with
RowExclusiveLock in a single transaction, exhausting the available lock
entries. It then verifies that the worker fails with the expected "out of
shared memory" error and the corresponding hint to increase
max_locks_per_transaction.
---
src/test/subscription/meson.build | 1 +
.../t/039_sequencesync_lock_exhaustion.pl | 120 ++++++++++++++++++
2 files changed, 121 insertions(+)
create mode 100644 src/test/subscription/t/039_sequencesync_lock_exhaustion.pl
diff --git a/src/test/subscription/meson.build b/src/test/subscription/meson.build
index e71e95c6297..94cc9d0a112 100644
--- a/src/test/subscription/meson.build
+++ b/src/test/subscription/meson.build
@@ -48,6 +48,7 @@ tests += {
't/036_sequences.pl',
't/037_except.pl',
't/038_walsnd_shutdown_timeout.pl',
+ 't/039_sequencesync_lock_exhaustion.pl',
't/100_bugs.pl',
],
},
diff --git a/src/test/subscription/t/039_sequencesync_lock_exhaustion.pl b/src/test/subscription/t/039_sequencesync_lock_exhaustion.pl
new file mode 100644
index 00000000000..e00efaf493a
--- /dev/null
+++ b/src/test/subscription/t/039_sequencesync_lock_exhaustion.pl
@@ -0,0 +1,120 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that the sequencesync worker's initial "gathering" scan can exhaust the
+# subscriber's shared lock table.
+#
+# LogicalRepSyncSequences() (sequencesync.c) walks every srsubstate='i' relation
+# of the subscription and opens each one with try_table_open(..., RowExclusiveLock)
+# in a *single* transaction, retaining all of those locks until that transaction
+# commits. With enough sequences in INIT state and a small shared lock table,
+# that one transaction runs out of lock-table space and fails with:
+#
+# out of shared memory
+# HINT: You might need to increase "max_locks_per_transaction".
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Enough sequences that the gathering scan's per-relation RowExclusiveLocks
+# overflow the small shared lock table configured further below. With that
+# configuration the table holds 210 entries and each backend has only 16
+# fast-path slots, so 300 sequences overflow it by a wide margin.
+my $nseqs = 300;
+
+# Initialize publisher node
+my $node_publisher = PostgreSQL::Test::Cluster->new('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Initialize subscriber node. It starts with the default (large) lock table so
+# the initial synchronization of all sequences succeeds; the lock table is
+# shrunk afterwards, before the REFRESH that is expected to fail.
+my $node_subscriber = PostgreSQL::Test::Cluster->new('subscriber');
+$node_subscriber->init;
+$node_subscriber->start;
+
+# Create the sequences on both sides.
+my $create_seqs =
+ join('', map { "CREATE SEQUENCE regress_s$_;\n" } (1 .. $nseqs));
+$node_publisher->safe_psql('postgres', $create_seqs);
+$node_subscriber->safe_psql('postgres', $create_seqs);
+
+# Advance each sequence on the publisher so there is real state to copy.
+$node_publisher->safe_psql('postgres',
+ join('', map { "SELECT nextval('regress_s$_');\n" } (1 .. $nseqs)));
+
+# Setup logical replication.
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION regress_seq_pub FOR ALL SEQUENCES");
+$node_subscriber->safe_psql('postgres',
+ "CREATE SUBSCRIPTION regress_seq_sub CONNECTION '$publisher_connstr' PUBLICATION regress_seq_pub"
+);
+
+# The initial sync of all sequences must succeed with the default lock table.
+my $synced_query =
+ "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r');";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize sequences";
+
+is( $node_subscriber->safe_psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubstate = 'r'"),
+ $nseqs,
+ "all $nseqs sequences initially synchronized");
+
+##########
+# Shrink the subscriber's shared lock table below the number of sequences, then
+# REFRESH SEQUENCES so the gathering scan tries to lock all of them in one
+# transaction and runs out of shared memory.
+##########
+
+# NLOCKENTS() = max_locks_per_transaction * (MaxBackends + max_prepared_xacts).
+# With the settings below:
+# MaxBackends = max_connections (10) + autovacuum_worker_slots (1)
+# + max_worker_processes (8) + max_wal_senders (0)
+# + NUM_SPECIAL_WORKER_PROCS (2)
+# = 21
+# max_prepared_xacts = 0
+# => NLOCKENTS() = 10 * 21 = 210, well below $nseqs (300).
+$node_subscriber->append_conf(
+ 'postgresql.conf', qq(
+max_locks_per_transaction = 10
+max_connections = 10
+autovacuum_worker_slots = 1
+max_worker_processes = 8
+max_wal_senders = 0
+));
+$node_subscriber->restart;
+
+my $log_offset = -s $node_subscriber->logfile;
+
+# REFRESH SEQUENCES resets every sequence to INIT. Note that this foreground
+# command only updates catalog rows and does not lock the sequence relations,
+# so it succeeds; it is the background sequencesync worker's gathering scan that
+# holds a RowExclusiveLock on every INIT sequence at once and overflows the
+# shared lock table.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+$node_subscriber->wait_for_log(qr/out of shared memory/, $log_offset);
+$node_subscriber->wait_for_log(
+ qr/You might need to increase "max_locks_per_transaction"/, $log_offset);
+
+pass('sequencesync gathering scan exhausts the shared lock table');
+
+# The scan fails before any sequence is marked READY, so synchronization makes
+# no progress: every sequence is still in a non-ready state.
+is( $node_subscriber->safe_psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_subscription_rel WHERE srsubstate = 'r'"),
+ '0',
+ 'no sequence reaches READY while the lock table is exhausted');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
+
+done_testing();
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-28 10:44 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-28 12:21 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-28 10:44 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com <amit.kapila16@gmail.com>; pgsql-hackers
Dear Vignesh,
> The attached v1-0002 patch contains a TAP test that reproduces the
> issue on HEAD. I'm attaching it in case anyone wants to reproduce the
> problem locally, although I don't think this test needs to be
> committed.
Thanks for posting a patch. I confirmed that "out of shared memory" error
could happen on the HEAD, and after the patch it won't happen anymore.
One minor comment:
```
+ /*
+ * Lock the sequence so its identity (namespace and name) cannot change
+ * under us via a concurrent DROP, RENAME or SET SCHEMA. Release it
+ * right away rather than at transaction end, to avoid accumulating a
+ * lock per sequence.
+ */
+ sequence_rel = try_table_open(subrel->srrelid, AccessShareLock);
```
I think this comment is not enough. We can describe why the releasing the lock
immediately is OK. IIUC, it's because the name and the namespace would be checked
again in copy_sequences(), right?
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
@ 2026-07-28 12:21 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 03:05 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-28 12:21 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Tue, Jul 28, 2026 at 4:14 PM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
>
> One minor comment:
> ```
> + /*
> + * Lock the sequence so its identity (namespace and name) cannot change
> + * under us via a concurrent DROP, RENAME or SET SCHEMA. Release it
> + * right away rather than at transaction end, to avoid accumulating a
> + * lock per sequence.
> + */
> + sequence_rel = try_table_open(subrel->srrelid, AccessShareLock);
> ```
>
> I think this comment is not enough. We can describe why the releasing the lock
> immediately is OK. IIUC, it's because the name and the namespace would be checked
> again in copy_sequences(), right?
>
Yes, that is correct. How about a comment as follows:
/*
* Lock the sequence so its identity (namespace and name) cannot change
* under us via a concurrent DROP, RENAME or SET SCHEMA while we read it.
* The lock is released immediately rather than at transaction end. The
* later synchronization does not depend on this captured identity
* remaining valid, as it re-opens the sequence and tolerates concurrent
* changes. Releasing early also avoids holding one lock per sequence,
* which could exhaust the lock table.
*/
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* RE: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-28 12:21 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-29 03:05 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-29 06:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-29 03:05 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
Dear Amit,
> Yes, that is correct. How about a comment as follows:
> /*
> * Lock the sequence so its identity (namespace and name) cannot change
> * under us via a concurrent DROP, RENAME or SET SCHEMA while we read it.
> * The lock is released immediately rather than at transaction end. The
> * later synchronization does not depend on this captured identity
> * remaining valid, as it re-opens the sequence and tolerates concurrent
> * changes. Releasing early also avoids holding one lock per sequence,
> * which could exhaust the lock table.
> */
LGTM.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-28 12:21 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 03:05 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
@ 2026-07-29 06:35 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 22:36 ` Re: sequencesync worker race with REFRESH SEQUENCES Peter Smith <smithpb2250@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-29 06:35 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, Jul 29, 2026 at 8:35 AM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:
>
> > Yes, that is correct. How about a comment as follows:
> > /*
> > * Lock the sequence so its identity (namespace and name) cannot change
> > * under us via a concurrent DROP, RENAME or SET SCHEMA while we read it.
> > * The lock is released immediately rather than at transaction end. The
> > * later synchronization does not depend on this captured identity
> > * remaining valid, as it re-opens the sequence and tolerates concurrent
> > * changes. Releasing early also avoids holding one lock per sequence,
> > * which could exhaust the lock table.
> > */
>
> LGTM.
>
Thanks, pushed now.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-28 12:21 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 03:05 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-29 06:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-29 22:36 ` Peter Smith <smithpb2250@gmail.com>
2026-07-30 10:46 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Peter Smith @ 2026-07-29 22:36 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Wed, Jul 29, 2026 at 4:35 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
>
> On Wed, Jul 29, 2026 at 8:35 AM Hayato Kuroda (Fujitsu)
> <kuroda.hayato@fujitsu.com> wrote:
> >
> > > Yes, that is correct. How about a comment as follows:
> > > /*
> > > * Lock the sequence so its identity (namespace and name) cannot change
> > > * under us via a concurrent DROP, RENAME or SET SCHEMA while we read it.
> > > * The lock is released immediately rather than at transaction end. The
> > > * later synchronization does not depend on this captured identity
> > > * remaining valid, as it re-opens the sequence and tolerates concurrent
> > > * changes. Releasing early also avoids holding one lock per sequence,
> > > * which could exhaust the lock table.
> > > */
> >
> > LGTM.
> >
>
> Thanks, pushed now.
>
FYI, somehow the "rather than" wording of the above comment became a
"rathen then" typo in the master code [1].
======
[1] https://github.com/postgres/postgres/commit/c12c101b0846b1e6488f2dc986a852fbc6bf2e3b#diff-e188109d85...
Kind Regards,
Peter Smith.
Fujitsu Australia
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-28 07:02 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-28 12:21 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 03:05 ` RE: sequencesync worker race with REFRESH SEQUENCES Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-29 06:35 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 22:36 ` Re: sequencesync worker race with REFRESH SEQUENCES Peter Smith <smithpb2250@gmail.com>
@ 2026-07-30 10:46 ` vignesh C <vignesh21@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: vignesh C @ 2026-07-30 10:46 UTC (permalink / raw)
To: Peter Smith <smithpb2250@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>; Noah Misch <noah@leadboat.com>; pgsql-hackers
On Thu, 30 Jul 2026 at 04:07, Peter Smith <smithpb2250@gmail.com> wrote:
>
> FYI, somehow the "rather than" wording of the above comment became a
> "rathen then" typo in the master code [1].
Thanks for reporting this. The attached patch addresses the issue.
In addition, it also addresses the last remaining finding (Finding 13:
"REFRESH SEQUENCES warning misattributes a `copy_data` request the
command cannot express"). The warning emitted by
check_publications_origin_sequences() is triggered when a subscription
with origin = NONE synchronizes sequence values that may have
originated from another subscription. However, the existing warning is
phrased in terms of copy_data and copying data, which is appropriate
for table synchronization but misleading for sequence synchronization.
The patch rewords the warning, detail, and hint to describe sequence
synchronization and the associated origin = NONE semantics more
accurately.
Regards,
Vignesh
Attachments:
[application/octet-stream] 0001-Improve-wording-of-sequence-origin-warning-in-logica.patch (3.2K, ../../CALDaNm0iuFC6PJ=b0jx-u3SC4qEJMjNbU7K4MaZMM8fW30t3WA@mail.gmail.com/2-0001-Improve-wording-of-sequence-origin-warning-in-logica.patch)
download | inline diff:
From 010e4f82ae727283ee7783399d131113dd02a1d2 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Thu, 30 Jul 2026 15:45:04 +0530
Subject: [PATCH] Improve wording of sequence origin warning in logical
replication
check_publications_origin_sequences() warns when a subscription with
origin = NONE synchronizes sequence values that may have originated from
another subscription. The existing warning is phrased in terms of
copy_data and copying data, which is appropriate for table
synchronization but misleading for sequence synchronization.
Reword the warning, detail, and hint to describe sequence
synchronization and the associated origin = NONE semantics more
accurately.
Also fix a typo ("rathen" -> "rather") in a comment in
sequencesync.c.
---
src/backend/commands/subscriptioncmds.c | 8 ++++----
src/backend/replication/logical/sequencesync.c | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 013ac46db07..67f5699b2c7 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -3334,12 +3334,12 @@ check_publications_origin_sequences(WalReceiverConn *wrconn, List *publications,
ereport(WARNING,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin",
+ errmsg("subscription \"%s\" requested origin = NONE but might synchronize sequence values that had a different origin",
subname),
- errdetail_plural("The subscription subscribes to a publication (%s) that contains sequences that are written to by other subscriptions.",
- "The subscription subscribes to publications (%s) that contain sequences that are written to by other subscriptions.",
+ errdetail_plural("The subscription subscribes to a publication (%s) that contains sequences that are synchronized from other subscriptions.",
+ "The subscription subscribes to publications (%s) that contain sequences that are synchronized from other subscriptions.",
list_length(publist), pubnames.data),
- errhint("Verify that initial data copied from the publisher sequences did not come from other origins."));
+ errhint("Verify that the initial values copied from the publisher sequences did not come from other origins."));
}
ExecDropSingleTupleTableSlot(slot);
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 0423745a428..ea24827aa9e 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -755,7 +755,7 @@ LogicalRepSyncSequences(void)
/*
* Lock the sequence so its identity (namespace and name) cannot
* change under us via a concurrent DROP, RENAME or SET SCHEMA. The
- * lock is released immediately rathen than at the transaction end.
+ * lock is released immediately rather than at the transaction end.
* The later synchronization does not depend on this captured identity
* remaining valid, as it re-opens the sequence and tolerates
* concurrent changes. Releasing early also avoids holding one lock
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-07-31 05:35 ` vignesh C <vignesh21@gmail.com>
2026-07-31 09:25 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
6 siblings, 2 replies; 82+ messages in thread
From: vignesh C @ 2026-07-31 05:35 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: amit.kapila16@gmail.com; pgsql-hackers
On Fri, 10 Jul 2026 at 10:22, Noah Misch <noah@leadboat.com> wrote:
>
> A Fable 5 review of logical replication of sequences found a way to get
> subscribed sequences into READY state despite the subscriber side having data
> older than the last REFRESH SEQUENCES. I'm attaching the test case it wrote.
> I reviewed the test, and I think it identifies a genuine defect.
>
> Fable 5 also wrote a lot more that neither it nor I confirmed by test case
> construction. I'm attaching the report; feel free to disregard.
Here's the summary of the findings:
Finding 1: Refresh paths race the in-flight sequencesync worker -
committed (45cf7b1e5bf923ca48dfd9aa5001bdd0630d11c3)
Finding 2: `default_transaction_read_only=on` permanently blocks
sequence sync - committed (4cd02d552263ed8d4b148aaefd24cd9dd8af57ed)
Finding 3: `run_as_owner=false`: one un-SET-ROLE-able sequence owner
stalls all sequence sync, unattributed - Rejected at [1]
Finding 4: Gathering scan holds a lock on every INIT relation in one
unbounded transaction - committed
(c12c101b0846b1e6488f2dc986a852fbc6bf2e3b)
Finding 5: Sequencesync worker starved by tablesync workers; REFRESH
SEQUENCES silently deferred - Rejected at [1]
Finding 6: No publisher-version guard: pre-PG19 publisher causes a
perpetual, cryptic error loop - committed
(57e7c52c0fe43fff36d0f4440eea13cf5668b160)
Finding 7: `has_sequence_privilege()` NULL on concurrent publisher
DROP: Assert crash / wrong diagnosis - committed
(55f518684895bfe43e667acf59284b49707ee685)
Finding 8: Permission-denied sequences double-reported as "missing on
publisher" - committed (13b7a8a0ef56d9decae284b4983894175c17d217)
Finding 9: ALTER SUBSCRIPTION ... DISABLE never stops a running
sequencesync worker - committed
(b8d9cf512c1259f97f9896593cc1c8352c1118ac)
Finding 10: REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE:
internal XX000 errors - Rejected at [1]
Finding 11: Sequence-removal loop runs after irreversible tablesync
slot drops - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
Finding 12: Batch query mixes MVCC definition columns with live
`last_value`: validation bypass - this is a base code issue, which is.
being tracked at [2]
Finding 13: REFRESH SEQUENCES warning misattributes a `copy_data`
request the command cannot express - committed
(00b3e50054dad648e74d19cc199555381ab366d2)
Finding 14: psql tab-completion regression after `REFRESH PUBLICATION`
- committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
Finding 15: pg_stat_subscription row for the sequencesync worker:
fabricated times, undocumented NULLs - committed
(b8d9cf512c1259f97f9896593cc1c8352c1118ac)
Finding 16: catalogs.sgml `srsublsn` description wrong for sequence
rows - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
Finding 17: Docs omit the PG19+ publisher requirement for sequence
sync; REFRESH SEQUENCES silently no-ops - committed
(57e7c52c0fe43fff36d0f4440eea13cf5668b160)
Finding 18: Restrictions bullet still claims only tables can be
replicated - Rejected at [1]
With all agreed issues now committed, the remaining four findings
rejected by consensus, and Finding 12 being tracked separately as a
pre-existing base code issue, I believe all items have been addressed.
I felt this thread can now be marked as closed.
[1] - https://www.postgresql.org/message-id/CALDaNm30Cr9n9YLHLnVDMbmUT%3DsegVQF2p0J3NMhUjrE1Mm9cw%40mail.g...
[2] - https://www.postgresql.org/message-id/CALDaNm3s1EmdEd%2BRO%2BTt8aWadZedxwZmE0hTiNSmO6ks4vdhSg%40mail...
Regards,
Vignesh
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-07-31 09:25 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-31 15:55 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
1 sibling, 1 reply; 82+ messages in thread
From: Amit Kapila @ 2026-07-31 09:25 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; pgsql-hackers
On Fri, Jul 31, 2026 at 11:06 AM vignesh C <vignesh21@gmail.com> wrote:
>
> Here's the summary of the findings:
> Finding 1: Refresh paths race the in-flight sequencesync worker -
> committed (45cf7b1e5bf923ca48dfd9aa5001bdd0630d11c3)
> Finding 2: `default_transaction_read_only=on` permanently blocks
> sequence sync - committed (4cd02d552263ed8d4b148aaefd24cd9dd8af57ed)
> Finding 3: `run_as_owner=false`: one un-SET-ROLE-able sequence owner
> stalls all sequence sync, unattributed - Rejected at [1]
> Finding 4: Gathering scan holds a lock on every INIT relation in one
> unbounded transaction - committed
> (c12c101b0846b1e6488f2dc986a852fbc6bf2e3b)
> Finding 5: Sequencesync worker starved by tablesync workers; REFRESH
> SEQUENCES silently deferred - Rejected at [1]
> Finding 6: No publisher-version guard: pre-PG19 publisher causes a
> perpetual, cryptic error loop - committed
> (57e7c52c0fe43fff36d0f4440eea13cf5668b160)
> Finding 7: `has_sequence_privilege()` NULL on concurrent publisher
> DROP: Assert crash / wrong diagnosis - committed
> (55f518684895bfe43e667acf59284b49707ee685)
> Finding 8: Permission-denied sequences double-reported as "missing on
> publisher" - committed (13b7a8a0ef56d9decae284b4983894175c17d217)
> Finding 9: ALTER SUBSCRIPTION ... DISABLE never stops a running
> sequencesync worker - committed
> (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> Finding 10: REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE:
> internal XX000 errors - Rejected at [1]
> Finding 11: Sequence-removal loop runs after irreversible tablesync
> slot drops - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> Finding 12: Batch query mixes MVCC definition columns with live
> `last_value`: validation bypass - this is a base code issue, which is.
> being tracked at [2]
> Finding 13: REFRESH SEQUENCES warning misattributes a `copy_data`
> request the command cannot express - committed
> (00b3e50054dad648e74d19cc199555381ab366d2)
> Finding 14: psql tab-completion regression after `REFRESH PUBLICATION`
> - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> Finding 15: pg_stat_subscription row for the sequencesync worker:
> fabricated times, undocumented NULLs - committed
> (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> Finding 16: catalogs.sgml `srsublsn` description wrong for sequence
> rows - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> Finding 17: Docs omit the PG19+ publisher requirement for sequence
> sync; REFRESH SEQUENCES silently no-ops - committed
> (57e7c52c0fe43fff36d0f4440eea13cf5668b160)
> Finding 18: Restrictions bullet still claims only tables can be
> replicated - Rejected at [1]
>
> With all agreed issues now committed, the remaining four findings
> rejected by consensus, and Finding 12 being tracked separately as a
> pre-existing base code issue, I believe all items have been addressed.
> I felt this thread can now be marked as closed.
>
I agree with your analysis of the reported issues and will close the
corresponding open item [1] early next week unless someone thinks
otherwise. Noah, would you like to cross-verify?
[1] - https://wiki.postgresql.org/wiki/PostgreSQL_19_Open_Items
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-31 09:25 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
@ 2026-07-31 15:55 ` Noah Misch <noah@leadboat.com>
2026-08-03 06:22 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Noah Misch @ 2026-07-31 15:55 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: vignesh C <vignesh21@gmail.com>; pgsql-hackers
On Fri, Jul 31, 2026 at 02:55:33PM +0530, Amit Kapila wrote:
> On Fri, Jul 31, 2026 at 11:06 AM vignesh C <vignesh21@gmail.com> wrote:
> > Here's the summary of the findings:
> > Finding 1: Refresh paths race the in-flight sequencesync worker -
> > committed (45cf7b1e5bf923ca48dfd9aa5001bdd0630d11c3)
> > Finding 2: `default_transaction_read_only=on` permanently blocks
> > sequence sync - committed (4cd02d552263ed8d4b148aaefd24cd9dd8af57ed)
> > Finding 3: `run_as_owner=false`: one un-SET-ROLE-able sequence owner
> > stalls all sequence sync, unattributed - Rejected at [1]
> > Finding 4: Gathering scan holds a lock on every INIT relation in one
> > unbounded transaction - committed
> > (c12c101b0846b1e6488f2dc986a852fbc6bf2e3b)
> > Finding 5: Sequencesync worker starved by tablesync workers; REFRESH
> > SEQUENCES silently deferred - Rejected at [1]
> > Finding 6: No publisher-version guard: pre-PG19 publisher causes a
> > perpetual, cryptic error loop - committed
> > (57e7c52c0fe43fff36d0f4440eea13cf5668b160)
> > Finding 7: `has_sequence_privilege()` NULL on concurrent publisher
> > DROP: Assert crash / wrong diagnosis - committed
> > (55f518684895bfe43e667acf59284b49707ee685)
> > Finding 8: Permission-denied sequences double-reported as "missing on
> > publisher" - committed (13b7a8a0ef56d9decae284b4983894175c17d217)
> > Finding 9: ALTER SUBSCRIPTION ... DISABLE never stops a running
> > sequencesync worker - committed
> > (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> > Finding 10: REFRESH SEQUENCES vs concurrent subscriber DROP SEQUENCE:
> > internal XX000 errors - Rejected at [1]
> > Finding 11: Sequence-removal loop runs after irreversible tablesync
> > slot drops - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> > Finding 12: Batch query mixes MVCC definition columns with live
> > `last_value`: validation bypass - this is a base code issue, which is.
> > being tracked at [2]
> > Finding 13: REFRESH SEQUENCES warning misattributes a `copy_data`
> > request the command cannot express - committed
> > (00b3e50054dad648e74d19cc199555381ab366d2)
> > Finding 14: psql tab-completion regression after `REFRESH PUBLICATION`
> > - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> > Finding 15: pg_stat_subscription row for the sequencesync worker:
> > fabricated times, undocumented NULLs - committed
> > (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> > Finding 16: catalogs.sgml `srsublsn` description wrong for sequence
> > rows - committed (b8d9cf512c1259f97f9896593cc1c8352c1118ac)
> > Finding 17: Docs omit the PG19+ publisher requirement for sequence
> > sync; REFRESH SEQUENCES silently no-ops - committed
> > (57e7c52c0fe43fff36d0f4440eea13cf5668b160)
> > Finding 18: Restrictions bullet still claims only tables can be
> > replicated - Rejected at [1]
> >
> > With all agreed issues now committed, the remaining four findings
> > rejected by consensus, and Finding 12 being tracked separately as a
> > pre-existing base code issue, I believe all items have been addressed.
> > I felt this thread can now be marked as closed.
>
> I agree with your analysis of the reported issues and will close the
> corresponding open item [1] early next week unless someone thinks
> otherwise. Noah, would you like to cross-verify?
No need to involve me at that level of detail. Thanks. I'm glad some of the
lesser findings also deserved changes.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-07-31 09:25 ` Re: sequencesync worker race with REFRESH SEQUENCES Amit Kapila <amit.kapila16@gmail.com>
2026-07-31 15:55 ` Re: sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
@ 2026-08-03 06:22 ` Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: Amit Kapila @ 2026-08-03 06:22 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: vignesh C <vignesh21@gmail.com>; pgsql-hackers
On Fri, Jul 31, 2026 at 9:25 PM Noah Misch <noah@leadboat.com> wrote:
>
> No need to involve me at that level of detail. Thanks. I'm glad some of the
> lesser findings also deserved changes.
>
I have closed the open item corresponding to this thread.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-09-22 03:12 ` Nikolay Samokhvalov <nik@postgres.ai>
2026-09-22 08:05 ` Re: sequencesync worker race with REFRESH SEQUENCES Andrey Borodin <x4mmm@yandex-team.ru>
2026-09-22 08:27 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
2026-09-22 09:33 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
1 sibling, 3 replies; 82+ messages in thread
From: Nikolay Samokhvalov @ 2026-09-22 03:12 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com; pgsql-hackers
On Fri, Jul 31, 2026, vignesh C <vignesh21@gmail.com> wrote:
> Here's the summary of the findings:
> Finding 1: Refresh paths race the in-flight sequencesync worker -
> committed (45cf7b1e5bf923ca48dfd9aa5001bdd0630d11c3)
The PG19 open-items page still lists this item under "resolved before
19beta3". I left that entry unchanged; please decide whether it should
be reopened.
Part (B) of that finding is still there on REL_19_STABLE (b73d13c3)
and master (d39fda1c). 45cf7b1e5bf stops the worker in
AlterSubscription_refresh_seq(), but the sequence-removal loop in
AlterSubscription_refresh() still only takes AccessExclusiveLock on
pg_subscription_rel and calls RemoveSubscriptionRel(). Unlike the table
loop right above it, it does not stop the sequencesync worker. A worker
that already has the sequence in its INIT list fails once the refresh
commits:
ERROR: subscription relation 16390 in subscription 16392 does not exist
The batch is rolled back and sync_seq_error_count goes up. With
disable_on_error = true the whole subscription is disabled, tables
included. The worker has also already applied the publisher value to the
local sequence, which is no longer subscribed.
Deterministic reproducer on REL_19_STABLE, no injection points:
publisher:
create table t (id int primary key);
create sequence s1; create sequence s2;
create publication pub_seq for all sequences;
create publication pub_tab for table t;
subscriber:
create table t (id int primary key);
create sequence s1; create sequence s2;
create subscription sub1 connection '...' publication pub_seq
with (disable_on_error = true, enabled = false);
publisher, session A, keep it open:
begin; drop sequence s1;
subscriber:
alter subscription sub1 enable;
-- wait until the sequencesync worker's batch query is blocked on
-- the publisher: pg_locks shows a not-granted AccessShareLock on s1
alter subscription sub1 set publication pub_tab;
publisher, session A:
rollback;
subscriber, once the worker has exited:
select subenabled from pg_subscription; -- f
select sync_seq_error_count from pg_stat_subscription_stats; -- 1
The attached patch stops the sequencesync worker in the removal loop,
as the tablesync loop and AlterSubscription_refresh_seq() do, with the
same lock argument. It adds a test to 036_sequences.pl using the
publisher-side blocking trick that file already uses. The test fails on
unpatched REL_19_STABLE with the error above and the subscription
disabled, and passes with the fix.
My AI harness found this while re-checking the fixes from this thread
and prepared the patch; I have not fully reviewed it by hand. With the
patch on b73d13c3 (cassert), the subscription TAP suite (39 files, 594
tests) and the core regression suite (239 tests) pass.
Nik
Attachments:
[application/x-patch] 0001-Stop-the-sequencesync-worker-when-a-refresh-removes-.patch (5.6K, ../../CAM527d9mL-bOofX-G7Sp441vA3Y_fa_7JBh03m-ZPduikn_XUg@mail.gmail.com/2-0001-Stop-the-sequencesync-worker-when-a-refresh-removes-.patch)
download | inline diff:
From cfce2e4cc9a78998702ef03e822d19fb8a3facb5 Mon Sep 17 00:00:00 2001
From: Nik Samokhvalov <nik@postgres.ai>
Date: Sun, 20 Sep 2026 23:00:52 -0700
Subject: [PATCH] Stop the sequencesync worker when a refresh removes
sequences.
ALTER SUBSCRIPTION ... REFRESH PUBLICATION, and SET/ADD/DROP PUBLICATION
with refresh, remove the pg_subscription_rel rows of sequences that are
no longer published. Unlike the table path, it did not stop a running
sequence synchronization worker. A worker that had already captured such
a sequence in its list of INIT sequences later failed in
UpdateSubscriptionRelState() with "subscription relation %u in
subscription %u does not exist" when marking the sequence READY. The
batch was rolled back and retried, sync_seq_error_count was incremented,
and with disable_on_error the whole subscription was disabled. The
worker had also already applied the publisher's value to the local
sequence, which is no longer subscribed.
Fix by stopping the sequence sync worker when removing a sequence, as is
done for tablesync workers and in AlterSubscription_refresh_seq(). This
is race-free for the same reason as there: the worker's catalog update
takes AccessShareLock on the subscription object, which
AlterSubscription() holds in AccessExclusiveLock mode until commit.
Add a test.
---
src/backend/commands/subscriptioncmds.c | 15 +++++
src/test/subscription/t/036_sequences.pl | 73 ++++++++++++++++++++++++
2 files changed, 88 insertions(+)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4f682cf0e..a22ec208f 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1282,6 +1282,21 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
RemoveSubscriptionRel(sub->oid, relid);
+ /*
+ * A sequence sync worker may already be running with this
+ * sequence in its to-do list. If it has fetched the value
+ * from the publisher but not yet marked the sequence READY,
+ * its UpdateSubscriptionRelState() would fail once we commit,
+ * as the row no longer exists. So stop the worker, as we do
+ * for tablesync workers above, and as
+ * AlterSubscription_refresh_seq() does. The worker's update
+ * is blocked by the AccessExclusiveLock on the subscription
+ * object until we commit, see AlterSubscription_refresh_seq()
+ * for why this is race-free.
+ */
+ logicalrep_worker_stop(WORKERTYPE_SEQUENCESYNC, sub->oid,
+ InvalidOid);
+
ereport(DEBUG1,
errmsg_internal("sequence \"%s.%s\" removed from subscription \"%s\"",
get_namespace_name(get_rel_namespace(relid)),
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index dd6fa515d..875ec14d2 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -290,6 +290,79 @@ $node_publisher->safe_psql(
));
# Wait for the recreated sequence to be synced.
+$node_subscriber->poll_query_until('postgres', $synced_query)
+ or die "Timed out while waiting for subscriber to synchronize data";
+
+##########
+# ALTER SUBSCRIPTION ... SET PUBLICATION (or REFRESH PUBLICATION) removing a
+# sequence must stop a running sequencesync worker, as it does for tablesync
+# workers. Otherwise the worker, which has already captured the sequence in
+# its to-do list, fails with an internal error when it tries to mark the
+# removed sequence as READY, and with disable_on_error the whole subscription
+# gets disabled.
+##########
+
+$node_publisher->safe_psql('postgres',
+ "CREATE PUBLICATION regress_seq_pub_empty");
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub SET (disable_on_error = true)");
+
+# Block the sequencesync worker's batch query on the publisher, after the
+# worker has captured its list of sequences to sync.
+$pub_session = $node_publisher->background_psql('postgres');
+$pub_session->query_safe(
+ qq(
+ BEGIN;
+ DROP SEQUENCE regress_s1;
+));
+
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+$node_publisher->poll_query_until(
+ 'postgres', qq(
+ SELECT EXISTS (
+ SELECT 1 FROM pg_locks
+ WHERE relation = 'regress_s1'::regclass
+ AND mode = 'AccessShareLock'
+ AND NOT granted);
+)) or die "timed out waiting for sequencesync worker to block on publisher";
+
+# Remove all sequences from the subscription while the worker is blocked.
+$node_subscriber->safe_psql('postgres',
+ "ALTER SUBSCRIPTION regress_seq_sub SET PUBLICATION regress_seq_pub_empty"
+);
+
+# Let the worker continue.
+$pub_session->query_safe("ROLLBACK");
+$pub_session->quit;
+
+# Wait for the sequencesync worker to exit, then verify that the subscription
+# was not disabled.
+$node_subscriber->poll_query_until(
+ 'postgres', qq(
+ SELECT NOT EXISTS (
+ SELECT 1 FROM pg_stat_subscription
+ WHERE subname = 'regress_seq_sub'
+ AND worker_type = 'sequence synchronization');
+)) or die "timed out waiting for sequencesync worker to exit";
+
+is( $node_subscriber->safe_psql(
+ 'postgres',
+ "SELECT subenabled FROM pg_subscription WHERE subname = 'regress_seq_sub'"
+ ),
+ 't',
+ 'subscription stays enabled after sequences are removed during sync');
+
+# Restore the original publication and settings, and wait for the sequences
+# to be synced again.
+$node_subscriber->safe_psql(
+ 'postgres', qq(
+ ALTER SUBSCRIPTION regress_seq_sub SET (disable_on_error = false);
+ ALTER SUBSCRIPTION regress_seq_sub SET PUBLICATION regress_seq_pub;
+));
+
$node_subscriber->poll_query_until('postgres', $synced_query)
or die "Timed out while waiting for subscriber to synchronize data";
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
@ 2026-09-22 08:05 ` Andrey Borodin <x4mmm@yandex-team.ru>
2 siblings, 0 replies; 82+ messages in thread
From: Andrey Borodin @ 2026-09-22 08:05 UTC (permalink / raw)
To: Nikolay Samokhvalov <nik@postgres.ai>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; Amit Kapila <amit.kapila16@gmail.com>; pgsql-hackers
On 22 Sep 2026, Nikolay Samokhvalov wrote:
> The attached patch stops the sequencesync worker in the removal loop,
I reviewed the worker stop/start interlock and reproduced the failure on
master. Your test disables the subscription without the fix and passes
with it.
I also tested REFRESH PUBLICATION removing just one of two sequences
while the worker was blocked on the publisher. The remaining sequence
reached READY, the unsubscribed local sequence kept its old value, and
table replication continued with disable_on_error = true. There were no
sequence synchronization errors.
The patch looks good.
Thank you!
Best regards, Andrey Borodin.
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
@ 2026-09-22 08:27 ` shveta malik <shveta.malik@gmail.com>
2 siblings, 0 replies; 82+ messages in thread
From: shveta malik @ 2026-09-22 08:27 UTC (permalink / raw)
To: Nikolay Samokhvalov <nik@postgres.ai>; +Cc: vignesh C <vignesh21@gmail.com>; Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com, pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Tue, Sep 22, 2026 at 8:42 AM Nikolay Samokhvalov <nik@postgres.ai> wrote:
>
> On Fri, Jul 31, 2026, vignesh C <vignesh21@gmail.com> wrote:
> > Here's the summary of the findings:
> > Finding 1: Refresh paths race the in-flight sequencesync worker -
> > committed (45cf7b1e5bf923ca48dfd9aa5001bdd0630d11c3)
>
> The PG19 open-items page still lists this item under "resolved before
> 19beta3". I left that entry unchanged; please decide whether it should
> be reopened.
>
> Part (B) of that finding is still there on REL_19_STABLE (b73d13c3)
> and master (d39fda1c). 45cf7b1e5bf stops the worker in
> AlterSubscription_refresh_seq(), but the sequence-removal loop in
> AlterSubscription_refresh() still only takes AccessExclusiveLock on
> pg_subscription_rel and calls RemoveSubscriptionRel(). Unlike the table
> loop right above it, it does not stop the sequencesync worker. A worker
> that already has the sequence in its INIT list fails once the refresh
> commits:
>
> ERROR: subscription relation 16390 in subscription 16392 does not exist
>
> The batch is rolled back and sync_seq_error_count goes up. With
> disable_on_error = true the whole subscription is disabled, tables
> included. The worker has also already applied the publisher value to the
> local sequence, which is no longer subscribed.
>
> Deterministic reproducer on REL_19_STABLE, no injection points:
>
> publisher:
> create table t (id int primary key);
> create sequence s1; create sequence s2;
> create publication pub_seq for all sequences;
> create publication pub_tab for table t;
>
> subscriber:
> create table t (id int primary key);
> create sequence s1; create sequence s2;
> create subscription sub1 connection '...' publication pub_seq
> with (disable_on_error = true, enabled = false);
>
> publisher, session A, keep it open:
> begin; drop sequence s1;
>
> subscriber:
> alter subscription sub1 enable;
> -- wait until the sequencesync worker's batch query is blocked on
> -- the publisher: pg_locks shows a not-granted AccessShareLock on s1
> alter subscription sub1 set publication pub_tab;
>
> publisher, session A:
> rollback;
>
> subscriber, once the worker has exited:
> select subenabled from pg_subscription; -- f
> select sync_seq_error_count from pg_stat_subscription_stats; -- 1
>
> The attached patch stops the sequencesync worker in the removal loop,
> as the tablesync loop and AlterSubscription_refresh_seq() do, with the
> same lock argument. It adds a test to 036_sequences.pl using the
> publisher-side blocking trick that file already uses. The test fails on
> unpatched REL_19_STABLE with the error above and the subscription
> disabled, and passes with the fix.
>
> My AI harness found this while re-checking the fixes from this thread
> and prepared the patch; I have not fully reviewed it by hand. With the
> patch on b73d13c3 (cassert), the subscription TAP suite (39 files, 594
> tests) and the core regression suite (239 tests) pass.
>
I had a look at the patch. The sequence sync worker is not per relid,
unlike the tablesync worker. So stopping the sequence worker when the
relid (one or a few) is removed is not quite the right analogy, since
a single sequence worker could be handling a large number of
sequences.
Additionally, the same race condition can be reproduced by using the
'alter sub..refresh pub' command. The following test reproduces it
using 'alter sub..refresh pub'. So whichever fix we decide on should
address both the scenarios:
Pub:
create sequence s1;
create sequence s2;
create publication pub_seq for all sequences;
Sub:
create sequence s1;
create sequence s2;
create subscription sub1 connection '...' publication pub_seq with
(disable_on_error = true, enabled = false);
--pg_subscription_rel populated with both s1 and s2 in 'i' state.
Pub's Session A:
begin;
alter sequence s2 rename to s2_tmp;
Sub:
alter subscription sub1 enable;
--The worker started by above will wait on AccessExclusiveLock lock on
pub while fetching seq s2 information.
Pub's Session B:
drop sequence s1;
Sub: refresh while worker is still stuck on s2 on pub.
alter subscription sub1 refresh publication;
--This will remove s1's entry on sub from pg_subscription_rel
Pub's Session A:
rollback;
Seq-worker proceeds and errors out as s1 is not found in
pg_subscription_rel, it disables subscription
[12132] ERROR: subscription relation 16384 in subscription 16386 does not exist
thanks
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
@ 2026-09-22 09:33 ` vignesh C <vignesh21@gmail.com>
2026-09-22 13:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Zhijie Hou <houzhijie22@gmail.com>
2 siblings, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-09-22 09:33 UTC (permalink / raw)
To: Nikolay Samokhvalov <nik@postgres.ai>; +Cc: Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com; pgsql-hackers
On Tue, 22 Sept 2026 at 08:42, Nikolay Samokhvalov <nik@postgres.ai> wrote:
>
> On Fri, Jul 31, 2026, vignesh C <vignesh21@gmail.com> wrote:
> > Here's the summary of the findings:
> > Finding 1: Refresh paths race the in-flight sequencesync worker -
> > committed (45cf7b1e5bf923ca48dfd9aa5001bdd0630d11c3)
>
> The PG19 open-items page still lists this item under "resolved before
> 19beta3". I left that entry unchanged; please decide whether it should
> be reopened.
I will open an item after a couple of days, once we agree that this
needs to be fixed, unless you think otherwise.
> Part (B) of that finding is still there on REL_19_STABLE (b73d13c3)
> and master (d39fda1c). 45cf7b1e5bf stops the worker in
> AlterSubscription_refresh_seq(), but the sequence-removal loop in
> AlterSubscription_refresh() still only takes AccessExclusiveLock on
> pg_subscription_rel and calls RemoveSubscriptionRel(). Unlike the table
> loop right above it, it does not stop the sequencesync worker. A worker
> that already has the sequence in its INIT list fails once the refresh
> commits:
>
> ERROR: subscription relation 16390 in subscription 16392 does not exist
>
> The batch is rolled back and sync_seq_error_count goes up. With
> disable_on_error = true the whole subscription is disabled, tables
> included. The worker has also already applied the publisher value to the
> local sequence, which is no longer subscribed.
>
> Deterministic reproducer on REL_19_STABLE, no injection points:
>
> publisher:
> create table t (id int primary key);
> create sequence s1; create sequence s2;
> create publication pub_seq for all sequences;
> create publication pub_tab for table t;
>
> subscriber:
> create table t (id int primary key);
> create sequence s1; create sequence s2;
> create subscription sub1 connection '...' publication pub_seq
> with (disable_on_error = true, enabled = false);
>
> publisher, session A, keep it open:
> begin; drop sequence s1;
>
> subscriber:
> alter subscription sub1 enable;
> -- wait until the sequencesync worker's batch query is blocked on
> -- the publisher: pg_locks shows a not-granted AccessShareLock on s1
> alter subscription sub1 set publication pub_tab;
>
> publisher, session A:
> rollback;
>
> subscriber, once the worker has exited:
> select subenabled from pg_subscription; -- f
> select sync_seq_error_count from pg_stat_subscription_stats; -- 1
>
> The attached patch stops the sequencesync worker in the removal loop,
> as the tablesync loop and AlterSubscription_refresh_seq() do, with the
> same lock argument. It adds a test to 036_sequences.pl using the
> publisher-side blocking trick that file already uses. The test fails on
> unpatched REL_19_STABLE with the error above and the subscription
> disabled, and passes with the fix.
Thanks Nik for reporting this.
Attached is v2, which takes a slightly different approach to the same
problem. Instead of stopping the sequence sync worker when a refresh
removes a sequence, copy_sequence() now checks whether the sequence is
still part of the subscription before updating it and skips it if it
is no longer subscribed.
I preferred the v2 approach for the following reasons: a) It avoids
discarding progress for other sequences in the batch. Sequences are
synchronized in batches of up to MAX_SEQUENCES_SYNC_PER_BATCH (100) in
a single transaction. Stopping the worker because one sequence was
removed discards the catalog progress for up to 99 other sequences.
Those sequences return to INIT and have to be fetched and synchronized
again by the next worker. b) The cost of stopping the worker depends
on the batch size. If the batch size is increased in the future, v1
would silently make this case more expensive. c) It can stop the
worker unnecessarily. AlterSubscription_refresh() removes the
pg_subscription_rel row regardless of its current state. Thus, even
removing a sequence that was already marked READY can stop the worker
while it is processing an unrelated set of sequences.
With v2, the check is made at the point where the sequence is about to
be updated, so a sequence that is no longer part of the subscription
is simply skipped.
A test for this is possible using the same publisher-side uncommitted
DROP as in the test above, but I don't think it is necessary. The
scenario requires two subscription DDLs to overlap with an ongoing
sequence synchronization, which is rare in practice, and the result is
now just a skipped sequence and a log message. Adding such a test
would require another background session, two polling loops, and a
full re-synchronization on every run of 036_sequences.pl. I don't
think the extra test is necessary for such a rare case.
The attached v2 version patch has the changes for the same.
Regards,
Vignesh
Attachments:
[application/octet-stream] v2-0001-Skip-sequences-removed-by-a-concurrent-subscripti.patch (5.2K, ../../CALDaNm1ibcTZym5jbZOZWo9pydiwzy2yKxcpcFqjQKpWP+XkKw@mail.gmail.com/2-v2-0001-Skip-sequences-removed-by-a-concurrent-subscripti.patch)
download | inline diff:
From f73d6f46d2b98bb33b3a28399df9a2d64658d008 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 22 Sep 2026 13:44:48 +0530
Subject: [PATCH v2] Skip sequences removed by a concurrent subscription
refresh
A sequence synchronization worker can capture a sequence
before a concurrent REFRESH PUBLICATION removes it from
pg_subscription_rel. Previously, the worker could update
the local sequence and then fail when trying to mark the
sequence as READY, aborting the batch.
Check that the sequence is still part of the subscription
before updating it and skip it if it has been removed. This
avoids updating sequences that are no longer subscribed and
prevents the failure from affecting other sequences in the
batch.
---
src/backend/commands/subscriptioncmds.c | 6 ++
.../replication/logical/sequencesync.c | 56 ++++++++++++++++++-
2 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 22a61dca65d..36bb20a1f6e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1341,6 +1341,12 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
RemoveSubscriptionRel(sub->oid, relid);
+ /*
+ * A sequence sync worker may already be running with this
+ * sequence in its to-do list. It does not have to be stopped.
+ * It notices that the sequence is no longer part of the
+ * subscription and skips it, see copy_sequence().
+ */
ereport(DEBUG1,
errmsg_internal("sequence \"%s.%s\" removed from subscription \"%s\"",
get_namespace_name(get_rel_namespace(relid)),
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 6d551d45791..1a0cd5852ff 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -60,6 +60,7 @@
#include "postmaster/interrupt.h"
#include "replication/logicalworker.h"
#include "replication/worker_internal.h"
+#include "storage/lmgr.h"
#include "storage/lwlock.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -80,7 +81,8 @@ typedef enum CopySeqResult
COPYSEQ_MISMATCH,
COPYSEQ_SUBSCRIBER_INSUFFICIENT_PERM,
COPYSEQ_PUBLISHER_INSUFFICIENT_PERM,
- COPYSEQ_SKIPPED
+ COPYSEQ_SKIPPED,
+ COPYSEQ_NOT_SUBSCRIBED
} CopySeqResult;
static List *seqinfos = NIL;
@@ -403,6 +405,36 @@ copy_sequence(LogicalRepSequenceInfo *seqinfo, Oid seqowner)
AclResult aclresult;
bool run_as_owner = MySubscription->runasowner;
Oid seqoid = seqinfo->localrelid;
+ XLogRecPtr statelsn;
+ Relation rel;
+
+ /*
+ * Acquire the locks that UpdateSubscriptionRelState() requires before
+ * checking whether this sequence is still part of the subscription, and
+ * hold them until the state has been updated below.
+ *
+ * ALTER SUBSCRIPTION ... REFRESH PUBLICATION can remove the sequence's
+ * pg_subscription_rel row while we are synchronizing it. It holds both of
+ * these locks in exclusive mode until it commits, see AlterSubscription()
+ * and AlterSubscription_refresh(). Holding them here means the row cannot
+ * disappear between the check below and the update at the end of this
+ * function.
+ */
+ LockSharedObject(SubscriptionRelationId, MySubscription->oid, 0,
+ AccessShareLock);
+ rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
+
+ /*
+ * The sequence may no longer be part of the subscription. There is
+ * nothing left to synchronize, so leave the local sequence alone and let
+ * the caller skip it.
+ */
+ if (GetSubscriptionRelState(MySubscription->oid, seqoid,
+ &statelsn) == SUBREL_STATE_UNKNOWN)
+ {
+ table_close(rel, NoLock);
+ return COPYSEQ_NOT_SUBSCRIBED;
+ }
/*
* If the user did not opt to run as the owner of the subscription
@@ -418,6 +450,8 @@ copy_sequence(LogicalRepSequenceInfo *seqinfo, Oid seqowner)
if (!run_as_owner)
RestoreUserContext(&ucxt);
+ table_close(rel, NoLock);
+
return COPYSEQ_SUBSCRIBER_INSUFFICIENT_PERM;
}
@@ -436,10 +470,13 @@ copy_sequence(LogicalRepSequenceInfo *seqinfo, Oid seqowner)
/*
* Record the remote sequence's LSN in pg_subscription_rel and mark the
- * sequence as READY.
+ * sequence as READY. The locks taken above are the ones this needs, so
+ * tell it they are already held.
*/
UpdateSubscriptionRelState(MySubscription->oid, seqoid, SUBREL_STATE_READY,
- seqinfo->page_lsn, false);
+ seqinfo->page_lsn, true);
+
+ table_close(rel, NoLock);
return COPYSEQ_SUCCESS;
}
@@ -655,6 +692,19 @@ copy_sequences(WalReceiverConn *conn)
batch_skipped_count++;
}
break;
+ case COPYSEQ_NOT_SUBSCRIBED:
+
+ /*
+ * A concurrent refresh removed this sequence from the
+ * subscription. Skipping it is the only sensible action,
+ * and it must not be treated as an error.
+ */
+ ereport(LOG,
+ errmsg("skip synchronization of sequence \"%s.%s\" because it is no longer part of subscription \"%s\"",
+ seqinfo->nspname, seqinfo->seqname,
+ MySubscription->name));
+ batch_skipped_count++;
+ break;
}
if (sequence_rel)
--
2.55.0
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
2026-09-22 09:33 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-09-22 13:51 ` Zhijie Hou <houzhijie22@gmail.com>
2026-09-22 17:58 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: Zhijie Hou @ 2026-09-22 13:51 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Nikolay Samokhvalov <nik@postgres.ai>; Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com; pgsql-hackers
Hi,
On Tue, Sep 22, 2026 at 5:34 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Tue, 22 Sept 2026 at 08:42, Nikolay Samokhvalov <nik@postgres.ai> wrote:
> > The attached patch stops the sequencesync worker in the removal loop,
> > as the tablesync loop and AlterSubscription_refresh_seq() do, with the
> > same lock argument. It adds a test to 036_sequences.pl using the
> > publisher-side blocking trick that file already uses. The test fails on
> > unpatched REL_19_STABLE with the error above and the subscription
> > disabled, and passes with the fix.
>
> Thanks Nik for reporting this.
>
> Attached is v2, which takes a slightly different approach to the same
> problem. Instead of stopping the sequence sync worker when a refresh
> removes a sequence, copy_sequence() now checks whether the sequence is
> still part of the subscription before updating it and skips it if it
> is no longer subscribed.
This approach makes sense to me.
I didn't find any major issues in the patch, but I have a few questions:
1.
+ /*
+ * The sequence may no longer be part of the subscription. There is
+ * nothing left to synchronize, so leave the local sequence alone and let
+ * the caller skip it.
+ */
+ if (GetSubscriptionRelState(MySubscription->oid, seqoid,
+ &statelsn) == SUBREL_STATE_UNKNOWN)
Can we simply use SearchSysCacheExists2 to check for the subrel entry here ?
2.
+ rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
...
+ table_close(rel, NoLock);
+ table_close(rel, NoLock);
+ table_close(rel, NoLock);
The patch adds 3 table_close calls in each return branch. Would it be possible
to delay the table_open to just before UpdateSubscriptionRelState, so that only
one close call is needed?
Best Regards,
Zhijie Hou
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
2026-09-22 09:33 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 13:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Zhijie Hou <houzhijie22@gmail.com>
@ 2026-09-22 17:58 ` vignesh C <vignesh21@gmail.com>
2026-09-23 04:35 ` Re: sequencesync worker race with REFRESH SEQUENCES shveta malik <shveta.malik@gmail.com>
0 siblings, 1 reply; 82+ messages in thread
From: vignesh C @ 2026-09-22 17:58 UTC (permalink / raw)
To: Zhijie Hou <houzhijie22@gmail.com>; +Cc: Nikolay Samokhvalov <nik@postgres.ai>; Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com; pgsql-hackers
On Tue, 22 Sept 2026 at 19:21, Zhijie Hou <houzhijie22@gmail.com> wrote:
> >
> > Attached is v2, which takes a slightly different approach to the same
> > problem. Instead of stopping the sequence sync worker when a refresh
> > removes a sequence, copy_sequence() now checks whether the sequence is
> > still part of the subscription before updating it and skips it if it
> > is no longer subscribed.
>
> This approach makes sense to me.
>
> I didn't find any major issues in the patch, but I have a few questions:
>
> 1.
>
> + /*
> + * The sequence may no longer be part of the subscription. There is
> + * nothing left to synchronize, so leave the local sequence alone and let
> + * the caller skip it.
> + */
> + if (GetSubscriptionRelState(MySubscription->oid, seqoid,
> + &statelsn) == SUBREL_STATE_UNKNOWN)
>
> Can we simply use SearchSysCacheExists2 to check for the subrel entry here ?
Yes, it can be used.
> 2.
>
> + rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
> ...
> + table_close(rel, NoLock);
> + table_close(rel, NoLock);
> + table_close(rel, NoLock);
>
> The patch adds 3 table_close calls in each return branch. Would it be possible
> to delay the table_open to just before UpdateSubscriptionRelState, so that only
> one close call is needed?
Modified
The attached v3 version patch has the changes for the same.
Regards,
Vignesh
Attachments:
[application/octet-stream] v3-0001-Skip-sequences-removed-by-a-concurrent-subscripti.patch (5.0K, ../../CALDaNm3sT09YXdkXR1f3nDR5J+UhOO+kB+YbZQFnVpLJBi7W7Q@mail.gmail.com/2-v3-0001-Skip-sequences-removed-by-a-concurrent-subscripti.patch)
download | inline diff:
From 8f542c28ac658c4a05b7b7cbe943b79d73f3e4c6 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Tue, 22 Sep 2026 13:44:48 +0530
Subject: [PATCH v3] Skip sequences removed by a concurrent subscription
refresh
A sequence synchronization worker can capture a sequence
before a concurrent REFRESH PUBLICATION removes it from
pg_subscription_rel. Previously, the worker could update
the local sequence and then fail when trying to mark the
sequence as READY, aborting the batch. With disable_on_error
the subscription was disabled, tables included.
Check that the sequence is still part of the subscription
before updating it and skip it if it has been removed. This
avoids updating sequences that are no longer subscribed and
prevents the failure from affecting other sequences in the
batch.
---
src/backend/commands/subscriptioncmds.c | 6 +++
.../replication/logical/sequencesync.c | 50 +++++++++++++++++--
2 files changed, 53 insertions(+), 3 deletions(-)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 22a61dca65d..36bb20a1f6e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1341,6 +1341,12 @@ AlterSubscription_refresh(Subscription *sub, bool copy_data,
RemoveSubscriptionRel(sub->oid, relid);
+ /*
+ * A sequence sync worker may already be running with this
+ * sequence in its to-do list. It does not have to be stopped.
+ * It notices that the sequence is no longer part of the
+ * subscription and skips it, see copy_sequence().
+ */
ereport(DEBUG1,
errmsg_internal("sequence \"%s.%s\" removed from subscription \"%s\"",
get_namespace_name(get_rel_namespace(relid)),
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 6d551d45791..64c29d7b437 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -60,6 +60,7 @@
#include "postmaster/interrupt.h"
#include "replication/logicalworker.h"
#include "replication/worker_internal.h"
+#include "storage/lmgr.h"
#include "storage/lwlock.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -80,7 +81,8 @@ typedef enum CopySeqResult
COPYSEQ_MISMATCH,
COPYSEQ_SUBSCRIBER_INSUFFICIENT_PERM,
COPYSEQ_PUBLISHER_INSUFFICIENT_PERM,
- COPYSEQ_SKIPPED
+ COPYSEQ_SKIPPED,
+ COPYSEQ_NOT_SUBSCRIBED
} CopySeqResult;
static List *seqinfos = NIL;
@@ -403,6 +405,29 @@ copy_sequence(LogicalRepSequenceInfo *seqinfo, Oid seqowner)
AclResult aclresult;
bool run_as_owner = MySubscription->runasowner;
Oid seqoid = seqinfo->localrelid;
+ Relation rel;
+
+ /*
+ * Take the subscription object lock before checking whether this sequence
+ * is still part of the subscription. The lock is held until the end of
+ * the transaction, so the check and the state update below are protected
+ * from a concurrent ALTER SUBSCRIPTION ... REFRESH PUBLICATION.
+ *
+ * AlterSubscription() takes this lock in AccessExclusiveLock mode while
+ * removing pg_subscription_rel rows, so the row cannot be removed between
+ * the check and the state update.
+ */
+ LockSharedObject(SubscriptionRelationId, MySubscription->oid, 0,
+ AccessShareLock);
+
+ /*
+ * The sequence may no longer be part of the subscription, in which case
+ * there is nothing to synchronize and the caller just skips it.
+ */
+ if (!SearchSysCacheExists2(SUBSCRIPTIONRELMAP,
+ ObjectIdGetDatum(seqoid),
+ ObjectIdGetDatum(MySubscription->oid)))
+ return COPYSEQ_NOT_SUBSCRIBED;
/*
* If the user did not opt to run as the owner of the subscription
@@ -434,12 +459,18 @@ copy_sequence(LogicalRepSequenceInfo *seqinfo, Oid seqowner)
if (!run_as_owner)
RestoreUserContext(&ucxt);
+ rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
+
/*
* Record the remote sequence's LSN in pg_subscription_rel and mark the
- * sequence as READY.
+ * sequence as READY. Both locks it needs are held already, the object
+ * lock from further up and the relation lock just taken, so say so rather
+ * than have it take and release them again.
*/
UpdateSubscriptionRelState(MySubscription->oid, seqoid, SUBREL_STATE_READY,
- seqinfo->page_lsn, false);
+ seqinfo->page_lsn, true);
+
+ table_close(rel, NoLock);
return COPYSEQ_SUCCESS;
}
@@ -655,6 +686,19 @@ copy_sequences(WalReceiverConn *conn)
batch_skipped_count++;
}
break;
+ case COPYSEQ_NOT_SUBSCRIBED:
+
+ /*
+ * A concurrent refresh removed this sequence from the
+ * subscription. Skipping it is the only sensible action,
+ * and it must not be treated as an error.
+ */
+ ereport(LOG,
+ errmsg("skip synchronization of sequence \"%s.%s\" because it is no longer part of subscription \"%s\"",
+ seqinfo->nspname, seqinfo->seqname,
+ MySubscription->name));
+ batch_skipped_count++;
+ break;
}
if (sequence_rel)
--
2.55.0
^ permalink raw reply [nested|flat] 82+ messages in thread
* Re: sequencesync worker race with REFRESH SEQUENCES
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-31 05:35 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 03:12 ` Re: sequencesync worker race with REFRESH SEQUENCES Nikolay Samokhvalov <nik@postgres.ai>
2026-09-22 09:33 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
2026-09-22 13:51 ` Re: sequencesync worker race with REFRESH SEQUENCES Zhijie Hou <houzhijie22@gmail.com>
2026-09-22 17:58 ` Re: sequencesync worker race with REFRESH SEQUENCES vignesh C <vignesh21@gmail.com>
@ 2026-09-23 04:35 ` shveta malik <shveta.malik@gmail.com>
0 siblings, 0 replies; 82+ messages in thread
From: shveta malik @ 2026-09-23 04:35 UTC (permalink / raw)
To: vignesh C <vignesh21@gmail.com>; +Cc: Zhijie Hou <houzhijie22@gmail.com>; Nikolay Samokhvalov <nik@postgres.ai>; Noah Misch <noah@leadboat.com>; amit.kapila16@gmail.com, pgsql-hackers@postgresql.org, shveta malik <shveta.malik@gmail.com>
On Tue, Sep 22, 2026 at 11:29 PM vignesh C <vignesh21@gmail.com> wrote:
>
> On Tue, 22 Sept 2026 at 19:21, Zhijie Hou <houzhijie22@gmail.com> wrote:
> > >
> > > Attached is v2, which takes a slightly different approach to the same
> > > problem. Instead of stopping the sequence sync worker when a refresh
> > > removes a sequence, copy_sequence() now checks whether the sequence is
> > > still part of the subscription before updating it and skips it if it
> > > is no longer subscribed.
> >
> > This approach makes sense to me.
> >
> > I didn't find any major issues in the patch, but I have a few questions:
> >
> > 1.
> >
> > + /*
> > + * The sequence may no longer be part of the subscription. There is
> > + * nothing left to synchronize, so leave the local sequence alone and let
> > + * the caller skip it.
> > + */
> > + if (GetSubscriptionRelState(MySubscription->oid, seqoid,
> > + &statelsn) == SUBREL_STATE_UNKNOWN)
> >
> > Can we simply use SearchSysCacheExists2 to check for the subrel entry here ?
>
> Yes, it can be used.
>
> > 2.
> >
> > + rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
> > ...
> > + table_close(rel, NoLock);
> > + table_close(rel, NoLock);
> > + table_close(rel, NoLock);
> >
> > The patch adds 3 table_close calls in each return branch. Would it be possible
> > to delay the table_open to just before UpdateSubscriptionRelState, so that only
> > one close call is needed?
>
> Modified
>
> The attached v3 version patch has the changes for the same.
>
I liked the patch's idea; I verified it, and it works well. I have no
further comments.
Thanks.
Shveta
^ permalink raw reply [nested|flat] 82+ messages in thread
end of thread, other threads:[~2026-09-23 04:35 UTC | newest]
Thread overview: 82+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-07-10 04:52 sequencesync worker race with REFRESH SEQUENCES Noah Misch <noah@leadboat.com>
2026-07-10 07:00 ` vignesh C <vignesh21@gmail.com>
2026-07-10 08:18 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-10 08:40 ` shveta malik <shveta.malik@gmail.com>
2026-07-10 12:36 ` vignesh C <vignesh21@gmail.com>
2026-07-11 05:01 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 07:15 ` vignesh C <vignesh21@gmail.com>
2026-07-13 04:05 ` shveta malik <shveta.malik@gmail.com>
2026-07-13 04:43 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-13 05:37 ` shveta malik <shveta.malik@gmail.com>
2026-07-13 11:08 ` vignesh C <vignesh21@gmail.com>
2026-07-13 12:15 ` shveta malik <shveta.malik@gmail.com>
2026-07-14 02:51 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 03:32 ` shveta malik <shveta.malik@gmail.com>
2026-07-14 13:16 ` vignesh C <vignesh21@gmail.com>
2026-07-15 01:31 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-15 09:13 ` vignesh C <vignesh21@gmail.com>
2026-07-21 04:44 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-21 05:18 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-21 05:34 ` vignesh C <vignesh21@gmail.com>
2026-07-21 07:19 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 02:46 ` vignesh C <vignesh21@gmail.com>
2026-07-22 03:28 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 03:39 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 04:49 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-22 05:42 ` shveta malik <shveta.malik@gmail.com>
2026-07-22 06:28 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-22 10:46 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-23 04:14 ` vignesh C <vignesh21@gmail.com>
2026-07-21 11:37 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-14 03:24 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-14 04:54 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-11 20:14 ` Noah Misch <noah@leadboat.com>
2026-07-13 03:23 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-13 03:35 ` Noah Misch <noah@leadboat.com>
2026-07-13 10:07 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 02:58 ` Noah Misch <noah@leadboat.com>
2026-07-15 11:53 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-15 11:56 ` Noah Misch <noah@leadboat.com>
2026-07-16 00:51 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 02:41 ` vignesh C <vignesh21@gmail.com>
2026-07-16 02:58 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-07-16 04:28 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 05:17 ` vignesh C <vignesh21@gmail.com>
2026-07-17 05:33 ` Tom Lane <tgl@sss.pgh.pa.us>
2026-07-17 06:44 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-17 11:53 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 03:35 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-18 14:40 ` vignesh C <vignesh21@gmail.com>
2026-07-20 05:16 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-20 05:48 ` vignesh C <vignesh21@gmail.com>
2026-07-20 04:46 ` shveta malik <shveta.malik@gmail.com>
2026-07-20 05:50 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-20 06:41 ` vignesh C <vignesh21@gmail.com>
2026-07-21 04:17 ` shveta malik <shveta.malik@gmail.com>
2026-07-21 04:35 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-13 12:52 ` vignesh C <vignesh21@gmail.com>
2026-07-24 04:07 ` vignesh C <vignesh21@gmail.com>
2026-07-24 05:19 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-24 12:38 ` vignesh C <vignesh21@gmail.com>
2026-07-24 13:31 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-27 05:26 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 06:55 ` vignesh C <vignesh21@gmail.com>
2026-07-30 08:22 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-28 07:02 ` vignesh C <vignesh21@gmail.com>
2026-07-28 10:44 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-28 12:21 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 03:05 ` Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com>
2026-07-29 06:35 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-29 22:36 ` Peter Smith <smithpb2250@gmail.com>
2026-07-30 10:46 ` vignesh C <vignesh21@gmail.com>
2026-07-31 05:35 ` vignesh C <vignesh21@gmail.com>
2026-07-31 09:25 ` Amit Kapila <amit.kapila16@gmail.com>
2026-07-31 15:55 ` Noah Misch <noah@leadboat.com>
2026-08-03 06:22 ` Amit Kapila <amit.kapila16@gmail.com>
2026-09-22 03:12 ` Nikolay Samokhvalov <nik@postgres.ai>
2026-09-22 08:05 ` Andrey Borodin <x4mmm@yandex-team.ru>
2026-09-22 08:27 ` shveta malik <shveta.malik@gmail.com>
2026-09-22 09:33 ` vignesh C <vignesh21@gmail.com>
2026-09-22 13:51 ` Zhijie Hou <houzhijie22@gmail.com>
2026-09-22 17:58 ` vignesh C <vignesh21@gmail.com>
2026-09-23 04:35 ` shveta malik <shveta.malik@gmail.com>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox