Re: BUG #17257: (auto)vacuum hangs within lazy_scan_prune() - Mailing list pgsql-bugs
From | Peter Geoghegan |
---|---|
Subject | Re: BUG #17257: (auto)vacuum hangs within lazy_scan_prune() |
Date | |
Msg-id | CAH2-WznKABiqgDDM2mgYmy4Pk2j-UrT9+PwSmHB3C59D5xtpqQ@mail.gmail.com Whole thread Raw |
In response to | Re: BUG #17257: (auto)vacuum hangs within lazy_scan_prune() (Noah Misch <noah@leadboat.com>) |
Responses |
Re: BUG #17257: (auto)vacuum hangs within lazy_scan_prune()
|
List | pgsql-bugs |
On Sat, Jan 6, 2024 at 5:44 PM Noah Misch <noah@leadboat.com> wrote: > Tied to that decision is the choice of semantics when the xmin horizon moves > backward during one VACUUM, e.g. when a new walsender xmin does so. Options: > > 1. Continue to remove tuples based on the OldestXmin from VACUUM's start. We > could have already removed some of those tuples, so the walsender xmin > won't achieve a guarantee anyway. (VACUUM would want ratchet-like behavior > in GlobalVisState, possibly by sharing OldestXmin with pruneheap like you > say.) > > 2. Move OldestXmin backward, to reflect the latest xmin horizon. (Perhaps > VACUUM would just pass GlobalVisState to a function that returns the > compatible OldestXmin.) > > Which way is better? I suppose that a hybrid of these two approaches makes the most sense. A design that's a lot closer to your #1 than to your #2. Under this scheme, pruneheap.c would be explicitly aware of OldestXmin, and would promise to respect the exact invariant that we need to avoid getting stuck in lazy_scan_prune's loop (or avoid confused NewRelfrozenXid tracking on HEAD, which no longer has this loop). But it'd be limited to that exact invariant; we'd still avoid unduly imposing any requirements on pruning-away deleted tuples whose xmax was >= OldestXmin. lazy_scan_prune/vacuumlazy.c shouldn't care if we prune away any "extra" heap tuples, just because we can (or just because it's convenient to the implementation). Andres has in the past placed a lot of emphasis on being able to update the GlobalVisState-wise bounds on the fly. Not sure that it's really that important that VACUUM does that, but there is no reason to not allow it. So we can keep that property (as well as the aforementioned high-level OldestXmin immutability property). More importantly (at least to me), this scheme allows vacuumlazy.c to continue to treat OldestXmin as an immutable cutoff for both pruning and freezing -- the high level design doesn't need any revisions. We already "freeze away" multixact member XIDs >= OldestXmin in certain rare cases (i.e. we remove lockers that are determined to no longer be running in FreezeMultiXactId's "second pass" slow path), so there is nothing fundamentally novel about the idea of removing some extra XIDs >= OldestXmin in passing, just because it happens to be convenient to some low-level piece of code that's external to vacuumlazy.c. What do you think of that general approach? I see no reason why it matters if OldestXmin goes backwards across two VACUUM operations, so I haven't tried to avoid that. > On Sat, Jan 06, 2024 at 01:41:23PM -0800, Peter Geoghegan wrote: > > What do you think of the idea of adding a defensive "can't happen" > > error to lazy_scan_prune() that will catch DEAD or RECENTLY_DEAD > > tuples with storage that still contain an xmax < OldestXmin? This > > probably won't catch every possible problem, but I suspect it'll work > > well enough. > > So before the "goto retry", ERROR if the tuple header suggests this mismatch > is happening? That, or limiting the retry count, could help. When I wrote this code, my understanding was that the sole reason for needing to loop back was a concurrently-aborted xact. In principle we ought to be able to test the tuple to detect if it's that exact case (the only truly valid case), and then throw an error if we somehow got it wrong. That kind of hardening would at least be correct according to my original understanding of things. There is an obvious practical concern with adding such hardening now: what if the current loop is accidentally protective, in whatever way? That seems quite possible. I seem to recall that Andres supposed at some point that the loop's purpose wasn't limited to the concurrently-aborted-inserter case that I believed was the only relevant case back when I worked on what became commit 8523492d4e ("Remove tupgone special case from vacuumlazy.c"). I don't have a reference for that, but I'm pretty sure it was said at some point around the release of 14. -- Peter Geoghegan
pgsql-bugs by date: