Re: ERROR: found unexpected null value in index - Mailing list pgsql-bugs

From Tom Lane
Subject Re: ERROR: found unexpected null value in index
Date
Msg-id 12489.1562788970@sss.pgh.pa.us
Whole thread Raw
In response to Re: ERROR: found unexpected null value in index  (Peter Geoghegan <pg@bowt.ie>)
Responses Re: ERROR: found unexpected null value in index
List pgsql-bugs
Peter Geoghegan <pg@bowt.ie> writes:
> On Tue, Jul 9, 2019 at 7:47 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Hm.  So maybe we need to teach it to ignore tuples that are not the tips
>> of their HOT chains?

> An approach like that was the first thing that I thought of, but I'll
> have to study the problem some more before coming up with a firm
> opinion.

I experimented with the attached.  It solves the reported problem and
passes check-world.  I made it just ignore any tuple for which
HeapTupleHeaderIsHotUpdated is true.  It might seem like there's a risk
of ignoring valid data, if the end tuple of a HOT chain is dead due to
a transaction abort, but since HeapTupleHeaderIsHotUpdated checks for
xmax validity I judge that the risk of that is small enough to be
acceptable.

A bigger problem with this is that in the tableam world, this seems
like a complete disaster modularity-wise.  I think it won't actually
fail --- non-heap AMs are probably not returning
BufferHeapTupleTableSlots, and even if they are, they shouldn't be
marking tuples HOT_UPDATED unless that concept applies to them.
But it sure seems like this leaves get_actual_variable_range() knowing
way more than it ought to about storage-level concerns.

Should we try to transpose some of this logic to below the AM API,
and if so, what should that look like?

            regards, tom lane

diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d7e3f09..b51cbed 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5232,8 +5232,6 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
                                    InvalidOid,    /* no reg proc for this */
                                    (Datum) 0);    /* constant */

-            have_data = true;
-
             /* If min is requested ... */
             if (min)
             {
@@ -5262,15 +5260,33 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
                  * get_actual_variable_range() call will not have to visit
                  * that heap entry.  In this way we avoid repetitive work when
                  * this function is used a lot during planning.
+                 *
+                 * However, all is not perfect, because if the index was
+                 * recently created it may have entries pointing to broken HOT
+                 * chains that contain tuples that don't match the index entry
+                 * but aren't yet known-dead either.  We need to ignore such
+                 * tuples, since they might not be representative of the index
+                 * extremal values, indeed could even contain NULLs.  We
+                 * approximate this by ignoring any hot-updated tuples we see
+                 * and continuing to scan.
                  */
                 index_scan = index_beginscan(heapRel, indexRel,
                                              &SnapshotNonVacuumable,
                                              1, 0);
                 index_rescan(index_scan, scankeys, 1, NULL, 0);

-                /* Fetch first tuple in sortop's direction */
-                if (index_getnext_slot(index_scan, indexscandir, slot))
+                /* Fetch first/next tuple in sortop's direction */
+                while (index_getnext_slot(index_scan, indexscandir, slot))
                 {
+                    /* Ignore hot-updated tuples, per comment above */
+                    if (TTS_IS_BUFFERTUPLE(slot))
+                    {
+                        BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+                        if (HeapTupleHeaderIsHotUpdated(bslot->base.tupdata.t_data))
+                            continue;
+                    }
+
                     /* Extract the index column values from the slot */
                     FormIndexDatum(indexInfo, slot, estate,
                                    values, isnull);
@@ -5284,24 +5300,40 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
                     MemoryContextSwitchTo(oldcontext);
                     *min = datumCopy(values[0], typByVal, typLen);
                     MemoryContextSwitchTo(tmpcontext);
+                    have_data = true;
+                    break;
                 }
-                else
-                    have_data = false;

                 index_endscan(index_scan);
             }
+            else
+            {
+                /* If min not requested, assume index has data */
+                have_data = true;
+            }

             /* If max is requested, and we didn't find the index is empty */
             if (max && have_data)
             {
+                have_data = false;
+
                 index_scan = index_beginscan(heapRel, indexRel,
                                              &SnapshotNonVacuumable,
                                              1, 0);
                 index_rescan(index_scan, scankeys, 1, NULL, 0);

-                /* Fetch first tuple in reverse direction */
-                if (index_getnext_slot(index_scan, -indexscandir, slot))
+                /* Fetch first/next tuple in reverse direction */
+                while (index_getnext_slot(index_scan, -indexscandir, slot))
                 {
+                    /* Ignore hot-updated tuples, per comment above */
+                    if (TTS_IS_BUFFERTUPLE(slot))
+                    {
+                        BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+                        if (HeapTupleHeaderIsHotUpdated(bslot->base.tupdata.t_data))
+                            continue;
+                    }
+
                     /* Extract the index column values from the slot */
                     FormIndexDatum(indexInfo, slot, estate,
                                    values, isnull);
@@ -5315,9 +5347,9 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
                     MemoryContextSwitchTo(oldcontext);
                     *max = datumCopy(values[0], typByVal, typLen);
                     MemoryContextSwitchTo(tmpcontext);
+                    have_data = true;
+                    break;
                 }
-                else
-                    have_data = false;

                 index_endscan(index_scan);
             }

pgsql-bugs by date:

Previous
From: Juan José Santamaría Flecha
Date:
Subject: Re: BUG #15889: PostgreSQL failed to build due to error MSB8020 withMSVC on windows
Next
From: Alvaro Herrera
Date:
Subject: Re: ERROR: negative bitmapset member not allowed in SELECT