Hi,

On Tue, Aug 25, 2026 at 12:58 AM Bertrand Drouvot
<[email protected]> wrote:
>
> Hi,
>
> On Fri, Aug 21, 2026 at 03:05:00PM -0700, Bharath Rupireddy wrote:
> > Please find the attached v14 patches. 0003 now adds support for
> > invalidating XID-aged synced replication slots on standbys.
>
> Thanks for the new version!
>
> I have a few comments:

Thanks for reviewing it and thanks for the off-list conversation.

> === 1
>
> +static inline bool
> +CanInvalidateXidAgedSlot(ReplicationSlot *s)
> +{
> +   return (max_slot_xid_age != 0 &&
> +           (TransactionIdIsValid(s->data.xmin) ||
> +            TransactionIdIsValid(s->data.catalog_xmin)) &&
> +           !(RecoveryInProgress() && s->data.synced));
> +}
>
> AFAICS this can also invalidate the reserved pg_conflict_detection slot. The
> checkpoint terminates its logical replication launcher, but the restarted
> launcher reacquires the invalid slot while subscriptions and workers still
> consider conflict information retention active.
>
> I wonder if invalidating this slot should also transition the affected 
> subscriptions
> out of conflict information retention, same as stop_conflict_info_retention() 
> is
> doing?

Since the pg_conflict_detection slot is internally created and
maintained for the user, I don't think invalidating it (by terminating
the launcher and asking every apply worker to stop conflict retention)
is the right approach. After invalidating, the slot cannot be dropped,
and the conflict detection feature will no longer be working
correctly, and the launcher will not error out for invalidated slots.
The user doesn't even have an easy way to deal with this situation.
They would have already set retain_dead_tuples and
max_retention_duration for the subscription, but they now have no way
to get conflict detection to work again.

That said, one of the design goals for the XID-age based slot
invalidation feature is that no slot blocks vacuum indefinitely.

My proposal is to check the age of each worker's
oldest_nonremovable_xid against the XID-age GUC in each apply worker
in should_stop_conflict_info_retention(). If it is aged, stop the
retention. This mechanism is simple to reason about and fits well with
the backoff we already have with max_retention_duration. This keeps
the slot valid but backs off from holding the vacuum.

> Worth adding a test combining retain_dead_tuples with max_slot_xid_age?

Sure, I will add one once we have consensus on the approach.

> === 2
>
> proves that a slot holds OldestXmin, but InvalidateObsoleteReplicationSlots()
> then invalidates every inactive slot older than xid_limit.
>
> For example, suppose slot A is the oldest horizon, a running transaction is 
> next,
> and slot B is newer than that transaction. Both slots exceed max_slot_xid_age.
>
> The function invalidates A and B, although invalidating B cannot advance the
> current vacuum cutoff.
>
> I wonder if vacuum should invalidate only the slot or slots holding the 
> current
> cutoff, recompute it, and repeat, while doing so advances the cutoff?

Just to be clear, logical slots are not invalidated when vacuuming
non-catalog tables. This is detected by checking whether the oldest
xmin computed comes from the slot's catalog_xmin or not (this covers
both shared and non-shared catalogs), to avoid additional non-relevant
work.

Also, vacuum never blocks if the slot is already held (i.e., active)
but XID-aged. This was to avoid vacuum workers waiting.

Invalidating only one slot at a time may not help much. For example,
if slots s1, s2, s3 are all XID-aged, invalidating only s1 means the
next cutoff recomputation finds s2 as the new blocker, and vacuum
won't get much benefit from that cycle. Invalidating all aged slots in
one pass lets vacuum get the full benefit. This keeps the code simple
and the behavior easy to explain, and is consistent with other
invalidation mechanisms we have today.

> === 3
>
> +        from pruning dead catalog rows and freezing XIDs. Invalidating such a
> +        slot on the standby advances the <literal>catalog_xmin</literal> 
> held by
> +        the primary's physical slot, letting vacuum there proceed.
>
> This does not seem guaranteed with wal_receiver_status_interval = 0.
>
> And I just realized that this is a pre existing behavior: removing a synced 
> slot
> or ordinary logical slot with wal_receiver_status_interval = 0 leaves the old
> catalog_xmin on the primary physical slot.
>
> I wonder if removing or invalidating a logical slot on a standby should 
> request
> immediate hot standby feedback, or if logical slots on standbys should require
> wal_receiver_status_interval > 0 or if we should document the side effects of
> setting it to zero? That's probably worth a dedicated thread though.

I will update the docs to: "Invalidating such a slot on the standby
allows the primary's catalog_xmin to advance once updated HS feedback
is sent. With wal_receiver_status_interval = 0, such feedback is not
sent periodically."

Thoughts?

--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com


Reply via email to