Re: recovering from "found xmin ... from before relfrozenxid ..."

Andres Freund <andres@anarazel.de>

From: Andres Freund <andres@anarazel.de>
To: Robert Haas <robertmhaas@gmail.com>
Cc: Tom Lane <tgl@sss.pgh.pa.us>, Amit Kapila <amit.kapila16@gmail.com>, Ashutosh Sharma <ashu.coek88@gmail.com>, Mark Dilger <mark.dilger@enterprisedb.com>, Masahiko Sawada <masahiko.sawada@2ndquadrant.com>, "Andrey M. Borodin" <x4mmm@yandex-team.ru>, MBeena Emerson <mbeena.emerson@gmail.com>, PostgreSQL Hackers <pgsql-hackers@lists.postgresql.org>, Peter Eisentraut <peter.eisentraut@2ndquadrant.com>, Stephen Frost <sfrost@snowman.net>
Date: 2020-09-21T20:11:20Z
Lists: pgsql-hackers
Hi,

On 2020-09-21 16:02:29 -0400, Robert Haas wrote:
> On Mon, Sep 21, 2020 at 2:21 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Right, but what we end up with is that the very same tuple xmin and
> > xmax might result in pruning/deletion, or not, depending on whether
> > it's part of a HOT chain or not.  That's at best pretty weird, and
> > at worst it means that corner-case bugs in other places are triggered
> > in only one of the two scenarios ... which is what we have here.
> 
> I'm not sure I really understand how that's happening, because surely
> HOT chains and non-HOT chains are pruned by the same code, but it
> doesn't sound good.

Not necessarily, unfortunately:

        case HEAPTUPLE_DEAD:

            /*
             * Ordinarily, DEAD tuples would have been removed by
             * heap_page_prune(), but it's possible that the tuple
             * state changed since heap_page_prune() looked.  In
             * particular an INSERT_IN_PROGRESS tuple could have
             * changed to DEAD if the inserter aborted.  So this
             * cannot be considered an error condition.
             *
             * If the tuple is HOT-updated then it must only be
             * removed by a prune operation; so we keep it just as if
             * it were RECENTLY_DEAD.  Also, if it's a heap-only
             * tuple, we choose to keep it, because it'll be a lot
             * cheaper to get rid of it in the next pruning pass than
             * to treat it like an indexed tuple. Finally, if index
             * cleanup is disabled, the second heap pass will not
             * execute, and the tuple will not get removed, so we must
             * treat it like any other dead tuple that we choose to
             * keep.
             *
             * If this were to happen for a tuple that actually needed
             * to be deleted, we'd be in trouble, because it'd
             * possibly leave a tuple below the relation's xmin
             * horizon alive.  heap_prepare_freeze_tuple() is prepared
             * to detect that case and abort the transaction,
             * preventing corruption.
             */
            if (HeapTupleIsHotUpdated(&tuple) ||
                HeapTupleIsHeapOnly(&tuple) ||
                params->index_cleanup == VACOPT_TERNARY_DISABLED)
                nkeep += 1;
            else
                tupgone = true; /* we can delete the tuple */
            all_visible = false;


So if e.g. a transaction aborts between the heap_page_prune and this
check the pruning behaviour depends on whether the tuple is part of a
HOT chain or not.

Greetings,

Andres Freund



Commits

  1. Fix wrong data table horizon computation during backend startup.

  2. Centralize horizon determination for temp tables, fixing bug due to skew.

  3. pg_surgery: Try to stabilize regression tests.

  4. New contrib module, pg_surgery, with heap surgery functions.

  5. Set cutoff xmin more aggressively when vacuuming a temporary table.

  6. snapshot scalability: Don't compute global horizons while building snapshots.

  7. Introduce vacuum errcontext to display additional information.