Re: Introduce XID age based replication slot invalidation

From: Bharath Rupireddy <bharath(dot)rupireddyforpostgres(at)gmail(dot)com>
To: Nisha Moond <nisha(dot)moond412(at)gmail(dot)com>
Cc: Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>, Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com>, Srinath Reddy Sadipiralla <srinath2133(at)gmail(dot)com>, SATYANARAYANA NARLAPURAM <satyanarlapuram(at)gmail(dot)com>, "Hayato Kuroda (Fujitsu)" <kuroda(dot)hayato(at)fujitsu(dot)com>, John H <johnhyvr(at)gmail(dot)com>, PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Introduce XID age based replication slot invalidation
Date: 2026-10-21 18:15:00
Message-ID: CALj2ACXUBz0dkMAjc9286D1RLSMjsPhsHjZWy=E+=npQFdHxyw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On Tue, Oct 6, 2026 at 11:31 PM Nisha Moond <nisha(dot)moond412(at)gmail(dot)com> wrote:
>
> Found one issue: a long-running transaction on the publisher can get
> every healthy logical slot invalidated, without vacuum gaining
> anything.
>
> IMO, a slot shouldn't be invalidated for XID age when it isn't what's
> holding the horizon back, i.e. invalidate a slot only if its aged
> xmin/catalog_xmin precedes the oldest running XID (e.g.
> GetOldestActiveTransactionId()).
>
> Thoughts?

Nice catch! Yes, you are right. Invalidating the slots in this case
seems unnecessary. But I see a problem with using just the oldest
running XID. It doesn't cover a backend that holds only a snapshot
xmin, e.g., a read-only query running for hours, or a repeatable-read
transaction that keeps its snapshot for the whole transaction [1].
Such a backend has no XID, so the oldest running XID doesn't see it,
but it still holds the catalog horizon back.

So I think we need a new function that tells whether a slot is the one
holding the vacuum back right now. It would loop over the proc-array
and do what the xid horizons computation does, but without folding in
the slot xmins, and take a database oid so the checkpointer can ask
for a logical slot's database without being connected to it. This
function gets called only for a slot that has already aged past the
limit, so no proc-array costs in a normal pass over slots. On a
standby, it also covers the primary's transactions from
KnownAssignedXids.

This way, only the slots that really hold the vacuum back get invalidated.

Thoughts?

[1]
-- max_slot_xid_age = 100
-- Session 1, holds a snapshot with no XID assigned
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT 1;

-- Session 2, make some dead rows in pg_class
CREATE TABLE foo(a int);
DROP TABLE foo;

CALL consume_xid(20);
SELECT pg_create_logical_replication_slot('s1', 'test_decoding');
CALL consume_xid(120);

-- Slot gets invalidated but vacuum wont' advance
CHECKPOINT;
SELECT slot_name, catalog_xmin, invalidation_reason FROM pg_replication_slots;
VACUUM (VERBOSE) pg_class;

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

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Masahiko Sawada 2026-10-21 18:18:50 Re: Reject COPY TO of unlogged relations during recovery
Previous Message Tom Lane 2026-10-21 18:00:40 Re: [DEPARSE] EXPLAIN prints another ModifyTable's RETURNING aliases in a SubPlan