Re: Introduce XID age based replication slot invalidation

From: Bharath Rupireddy <bharath(dot)rupireddyforpostgres(at)gmail(dot)com>
To: Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com>
Cc: 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-07-07 06:34:06
Message-ID: CALj2ACWmrz0+9YUv6gfxim4f+WbuR-K820e8OWY4G=4Mt7wr3g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On Thu, Jul 2, 2026 at 4:18 PM Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> wrote:
>
> > Thank you Sawada-san. I've been thinking more about it and I agree we
> > need to address this. While I still think the scenario is unlikely in
> > practice (SIGTERM would have to take a long time, the slot's xmin
> > would have to be very old while the walsender is still running, etc.),
> > I think it's worth handling.
> >
> > I can think of a couple of approaches:
> >
> > 1. Use ConditionVariableTimedSleep instead of ConditionVariableSleep
> > when called from an autovacuum worker. Workers don't block forever,
> > but they still wait for the timeout duration, still send redundant
> > SIGTERMs, and a correct timeout value needs to be chosen. When it
> > expires, the worker either retries (still stuck) or gives up (same as
> > approach 2).
> >
> > 2. Make the vacuum path non-blocking when another process is already
> > invalidating the same slot. The first process to attempt invalidation
> > proceeds normally: it sends SIGTERM and waits on
> > ConditionVariableSleep for the process holding the slot to exit. But
> > if a subsequent autovacuum worker finds that another process has
> > already initiated invalidation of this slot, it skips the slot and
> > proceeds with vacuum instead of waiting on the same
> > ConditionVariableSleep.
> >
> > I think approach 2 is simple. If another process is already
> > invalidating the slot, there's no reason for the autovacuum worker to
> > also block. The tradeoff is that this vacuum cycle's OldestXmin won't
> > move forward and it will need another cycle for this relation. But
> > that's fine given that the scenario as explained above is unlikely to
> > happen in practice.
> >
> > Please let me know if my thinking sounds reasonable. I'm open to other
> > ideas too.
>
> The third idea I came up with is that (auto)vacuum behaves differently
> in terms of XID-aged slot invalidation depending on the slot being
> used or not; (auto)vacuum invalidate the XID-aged slot if no one is
> holding the slot, and it just wakes up the checkpointer to invalidate
> the slot if a process is still holding the slot. If the XID-aged slot
> is not held by any process, (auto)vacuum simply invalidates the slot.
> I believe that while the former case happens in most cases in
> practice, delegating the checkpointer to invalidate XID-aged slots
> might help avoid vacuum from being blocked.
>
> What do you think about the above idea?

I see two cases here.

1/ No one is holding the slot. The connection is gone for whatever
reason. Here vacuum just acquires and invalidates the slot right away,
no signal, no waiting, and this already works.

2/ The slot is held but has aged out, due to a slow connection,
replica lag, or slow decoding/apply. This is the case that involves
terminating the holder and waiting, which is where blocking can
happen.

I partially agree with your suggestion for case 2. My preference is to
not add any blockers for vacuum. It does opportunistic XID-age
invalidation, invalidates the slots it can take without waiting, and
leaves the held ones to the checkpointer, which is the guaranteed
path. It is also easier to reason about and to explain to users.

The tradeoff is that vacuum won't invalidate a held slot in the same
pass, so the relation being vacuumed right then doesn't get the
advanced horizon. Once the checkpointer has invalidated the slot,
later relations pick it up, which may well be in the same vacuum cycle
or next. I am fine with that.

Given this, I do not think 0003 is needed anymore. Its whole purpose
was to stop multiple vacuum processes piling onto the same slot's
condition variable. If the vacuum never waits there, that contention
cannot happen. There is only one checkpointer. The unheld case is
already safe. The check runs under the slot's spinlock, so the first
process marks the slot invalidated and any others see it is done and
move on. No signal, no waiting. I will drop 0003.

I would skip having the vacuum explicitly wake the checkpointer when
it sees a held slot. Imagine one walsender holding the slot's xmin and
8 autovacuum workers running. Each worker hits that same held slot and
would signal the checkpointer, so we would get a signal per worker for
a single slot, repeated every cycle until the checkpointer acts. If
the checkpointer is already running, another request gets queued, and
this can happen repeatedly, creating a flood of checkpoint requests.
Since 0001 already invalidates aged slots during every checkpoint, the
slot gets cleaned up on the checkpointer's regular pass anyway. I
would rather rely on that than send extra signals to perform
checkpoints.

Thoughts?

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

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Michael Paquier 2026-07-07 06:36:09 Re: Use HostsFileName everywhere
Previous Message Peter Smith 2026-07-07 06:20:23 Re: Include sequences in publications created by pg_createsubscriber