Re: Fix for gistchoose - Mailing list pgsql-hackers
| From | Tom Lane |
|---|---|
| Subject | Re: Fix for gistchoose |
| Date | |
| Msg-id | 974.1346359725@sss.pgh.pa.us Whole thread Raw |
| In response to | Re: Fix for gistchoose (Robert Haas <robertmhaas@gmail.com>) |
| Responses |
Re: Fix for gistchoose
|
| List | pgsql-hackers |
Robert Haas <robertmhaas@gmail.com> writes:
> On Thu, Aug 30, 2012 at 4:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Yeah, the idea of replacing sum_grow with a boolean just occurred to me
>> too. As is, I think the code is making some less-than-portable
>> assumptions about what will happen if sum_grow overflows; which can
>> definitely happen, seeing that gistpenalty and its callees intentionally
>> return infinity in some cases. I'd rather it didn't attempt to add
>> column penalties together, and I think there's a case to be made that
>> not doing so is a back-patchable bug fix.
> Keep in mind that the worst case outcome is the index quality is worse
> than it otherwise would have been, so it's not like
> OMG-PostgreSQ-eats-your-data.
Agreed, but we've seen plenty of complaining about bloated gist indexes,
and this might be the cause.
>> I'll take a shot at improving this some more.
> Okey dokey.
Attached is a revised version of gistchoose(); I've not yet transposed
the changes into gistRelocateBuildBuffersOnSplit(). It looks a lot
more readable to me now. Objections?
regards, tom lane
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 7c6ce8495caac1e9d7c99afdb513689de93beea5..48d70d98e25363d8425020be7607f20348a0456a 100644
*** a/src/backend/access/gist/gistutil.c
--- b/src/backend/access/gist/gistutil.c
*************** gistgetadjusted(Relation r, IndexTuple o
*** 363,475 ****
}
/*
! * Search a page for the entry with lowest penalty.
*
! * The index may have multiple columns, and there's a penalty value for each column.
! * The penalty associated with a column which appears earlier in the index definition is
! * strictly more important than the penalty of column which appears later in the index
! * definition.
*/
OffsetNumber
gistchoose(Relation r, Page p, IndexTuple it, /* it has compressed entry */
GISTSTATE *giststate)
{
OffsetNumber maxoff;
OffsetNumber i;
! OffsetNumber which;
! float sum_grow,
! which_grow[INDEX_MAX_KEYS];
GISTENTRY entry,
identry[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
! maxoff = PageGetMaxOffsetNumber(p);
! *which_grow = -1.0;
! which = InvalidOffsetNumber;
! sum_grow = 1;
gistDeCompressAtt(giststate, r,
it, NULL, (OffsetNumber) 0,
identry, isnull);
! Assert(maxoff >= FirstOffsetNumber);
! Assert(!GistPageIsLeaf(p));
/*
! * Loop over tuples on page.
*
! * We'll exit early if we find an index key that can accommodate the new key with no
! * penalty on any column. sum_grow is used to track this condition. Normally, it is the
! * sum of the penalties we've seen for this column so far, which is not a very useful
! * quantity in general because the penalties for each column are only considered
! * independently, but all we really care about is whether or not it's greater than zero.
! * Since penalties can't be negative, the sum of the penalties will be greater than
! * zero if and only if at least one penalty was greater than zero. To make things just
! * a bit more complicated, we arbitrarily set sum_grow to 1.0 whenever we want to force
! * the at least one more iteration of this outer loop. Any non-zero value would serve
! * just as well.
*/
! for (i = FirstOffsetNumber; i <= maxoff && sum_grow; i = OffsetNumberNext(i))
{
- int j;
IndexTuple itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
! sum_grow = 0;
! /* Loop over indexed attribtues. */
for (j = 0; j < r->rd_att->natts; j++)
{
Datum datum;
float usize;
bool IsNull;
datum = index_getattr(itup, j + 1, giststate->tupdesc, &IsNull);
gistdentryinit(giststate, j, &entry, datum, r, p, i,
FALSE, IsNull);
usize = gistpenalty(giststate, j, &entry, IsNull,
&identry[j], isnull[j]);
! if (which_grow[j] < 0 || usize < which_grow[j])
{
/*
! * We get here in two cases. First, we may have just discovered that the
! * current tuple is the best one we've seen so far; that is, for the first
! * column for which the penalty is not equal to the best tuple seen so far,
! * this one has a lower penalty than the previously-seen one. But, when
! * a new best tuple is found, we must record the best penalty value for
! * all the remaining columns. We'll end up here for each remaining index
! * column in that case, too.
*/
! which = i;
! which_grow[j] = usize;
if (j < r->rd_att->natts - 1)
! which_grow[j + 1] = -1;
! sum_grow += which_grow[j];
}
! else if (which_grow[j] == usize)
{
/*
! * The current tuple is exactly as good for this column as the best tuple
! * seen so far. The next iteration of this loop will compare the next
! * column.
*/
- sum_grow += usize;
}
else
{
/*
! * The current tuple is worse for this column than the best tuple seen so
! * far. Skip the remaining columns and move on to the next tuple, if any.
*/
! sum_grow = 1;
break;
}
}
- }
! if (which == InvalidOffsetNumber)
! which = FirstOffsetNumber;
! return which;
}
/*
--- 363,482 ----
}
/*
! * Search an upper index page for the entry with lowest penalty for insertion
! * of the new index key contained in "it".
*
! * Returns the index of the page entry to insert into.
*/
OffsetNumber
gistchoose(Relation r, Page p, IndexTuple it, /* it has compressed entry */
GISTSTATE *giststate)
{
+ OffsetNumber result;
OffsetNumber maxoff;
OffsetNumber i;
! float best_penalty[INDEX_MAX_KEYS];
GISTENTRY entry,
identry[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
! Assert(!GistPageIsLeaf(p));
!
gistDeCompressAtt(giststate, r,
it, NULL, (OffsetNumber) 0,
identry, isnull);
! /* we'll return FirstOffsetNumber if page is empty (shouldn't happen) */
! result = FirstOffsetNumber;
/*
! * The index may have multiple columns, and there's a penalty value for
! * each column. The penalty associated with a column which appears
! * earlier in the index definition is strictly more important than the
! * penalty of a column which appears later in the index definition.
*
! * best_penalty[j] is the best penalty we have seen so far for column j,
! * or -1 when we haven't yet examined column j. Array entries to the
! * right of the first -1 are undefined.
*/
! best_penalty[0] = -1;
!
! /*
! * Loop over tuples on page.
! */
! maxoff = PageGetMaxOffsetNumber(p);
! Assert(maxoff >= FirstOffsetNumber);
!
! for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
{
IndexTuple itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
+ bool zero_penalty;
+ int j;
! zero_penalty = true;
! /* Loop over indexed attributes within this tuple. */
for (j = 0; j < r->rd_att->natts; j++)
{
Datum datum;
float usize;
bool IsNull;
+ /* Compute penalty for this column. */
datum = index_getattr(itup, j + 1, giststate->tupdesc, &IsNull);
gistdentryinit(giststate, j, &entry, datum, r, p, i,
FALSE, IsNull);
usize = gistpenalty(giststate, j, &entry, IsNull,
&identry[j], isnull[j]);
+ if (usize > 0)
+ zero_penalty = false;
! if (best_penalty[j] < 0 || usize < best_penalty[j])
{
/*
! * New best penalty for column. Tentatively select this tuple
! * as the target, and record the best penalty. Then reset the
! * next column's penalty to "unknown" (and indirectly, the
! * same for all the ones to its right). This will force us
! * to adopt this tuple's penalty values as the best for all
! * the remaining columns during subsequent loop iterations.
*/
! result = i;
! best_penalty[j] = usize;
!
if (j < r->rd_att->natts - 1)
! best_penalty[j + 1] = -1;
}
! else if (best_penalty[j] == usize)
{
/*
! * The current tuple is exactly as good for this column as the
! * best tuple seen so far. The next iteration of this loop
! * will compare the next column.
*/
}
else
{
/*
! * The current tuple is worse for this column than the best
! * tuple seen so far. Skip the remaining columns and move on
! * to the next tuple, if any.
*/
! zero_penalty = false; /* be sure outer loop won't exit */
break;
}
}
! /*
! * If we find a tuple with zero penalty for all columns, there's no
! * need to examine remaining tuples; just break out of the loop and
! * return it.
! */
! if (zero_penalty)
! break;
! }
! return result;
}
/*
pgsql-hackers by date: