typedefenum
{ /* strategy for searching through materialized list of split points */
SPLIT_DEFAULT, /* give some weight to truncation */
SPLIT_MANY_DUPLICATES, /* find minimally distinguishing point */
SPLIT_SINGLE_VALUE, /* leave left page almost full */
} FindSplitStrat;
typedefstruct
{ /* details of free space left by split */
int16 curdelta; /* current leftfree/rightfree delta */
int16 leftfree; /* space left on left page post-split */
int16 rightfree; /* space left on right page post-split */
/* split point identifying fields (returned by _bt_findsplitloc) */
OffsetNumber firstrightoff; /* first origpage item on rightpage */ bool newitemonleft; /* new item goes on left, or right? */
} SplitPoint;
typedefstruct
{ /* context data for _bt_recsplitloc */
Relation rel; /* index relation */
Page origpage; /* page undergoing split */
IndexTuple newitem; /* new item (cause of page split) */
Size newitemsz; /* size of newitem (includes line pointer) */ bool is_leaf; /* T if splitting a leaf page */ bool is_rightmost; /* T if splitting rightmost page on level */
OffsetNumber newitemoff; /* where the new item is to be inserted */ int leftspace; /* space available for items on left page */ int rightspace; /* space available for items on right page */ int olddataitemstotal; /* space taken by old items */
Size minfirstrightsz; /* smallest firstright size */
/* candidate split point data */ int maxsplits; /* maximum number of splits */ int nsplits; /* current number of splits */
SplitPoint *splits; /* all candidate split points for page */ int interval; /* current range of acceptable split points */
} FindSplitData;
/* Total free space available on a btree page, after fixed overhead */
leftspace = rightspace =
PageGetPageSize(origpage) - SizeOfPageHeaderData -
MAXALIGN(sizeof(BTPageOpaqueData));
/* The right page will have the same high key as the old page */ if (!P_RIGHTMOST(opaque))
{
itemid = PageGetItemId(origpage, P_HIKEY);
rightspace -= (int) (MAXALIGN(ItemIdGetLength(itemid)) + sizeof(ItemIdData));
}
/* Count up total space in data items before actually scanning 'em */
olddataitemstotal = rightspace - (int) PageGetExactFreeSpace(origpage);
leaffillfactor = BTGetFillFactor(rel);
/* Passed-in newitemsz is MAXALIGNED but does not include line pointer */
newitemsz += sizeof(ItemIdData);
state.rel = rel;
state.origpage = origpage;
state.newitem = newitem;
state.newitemsz = newitemsz;
state.is_leaf = P_ISLEAF(opaque);
state.is_rightmost = P_RIGHTMOST(opaque);
state.leftspace = leftspace;
state.rightspace = rightspace;
state.olddataitemstotal = olddataitemstotal;
state.minfirstrightsz = SIZE_MAX;
state.newitemoff = newitemoff;
/* newitem cannot be a posting list item */
Assert(!BTreeTupleIsPosting(newitem));
/* *Ibelieveitisnotpossibletofailtofindafeasiblesplit,butjust *incase...
*/ if (state.nsplits == 0)
elog(ERROR, "could not find a feasible split point for index \"%s\"",
RelationGetRelationName(rel));
/* *Startsearchforasplitpointamonglistoflegalsplitpoints.Give *primaryconsiderationtoequalizingavailablefreespaceineachhalf *ofthesplitinitially(startwithdefaultstrategy),whileapplying *rightmostandsplit-after-new-itemoptimizationswhereappropriate. *Eitherofthetwootherfallbackstrategiesmayberequiredforcases *withalargenumberofduplicatesaroundtheoriginal/space-optimal *splitpoint. * *Defaultstrategygivessomeweighttosuffixtruncationindecidinga *splitpointonleafpages.Itattemptstoselectasplitpointwherea *distinguishingattributeappearsearlierinthenewhighkeyforthe *leftsideofthesplit,inordertomaximizethenumberoftrailing *attributesthatcanbetruncatedaway.Onlycandidatesplitpoints *thatimplyanacceptablebalanceoffreespaceoneachsideare *considered.See_bt_defaultinterval().
*/ if (!state.is_leaf)
{ /* fillfactormult only used on rightmost page */
usemult = state.is_rightmost;
fillfactormult = BTREE_NONLEAF_FILLFACTOR / 100.0;
} elseif (state.is_rightmost)
{ /* Rightmost leaf page -- fillfactormult always used */
usemult = true;
fillfactormult = leaffillfactor / 100.0;
} elseif (_bt_afternewitemoff(&state, maxoff, leaffillfactor, &usemult))
{ /* *Newiteminsertedatrightmostpointamongalocalizedgroupingon *aleafpage--apply"splitafternewitem"optimization,eitherby *applyingleaffillfactormultiplier,orbychoosingtheexactsplit *pointthatleavesnewitemaslastleft.(usemultissetforus.)
*/ if (usemult)
{ /* fillfactormult should be set based on leaf fillfactor */
fillfactormult = leaffillfactor / 100.0;
} else
{ /* find precise split point after newitemoff */ for (int i = 0; i < state.nsplits; i++)
{
SplitPoint *split = state.splits + i;
if (strategy == SPLIT_DEFAULT)
{ /* *Defaultstrategyworkedout(alwaysworksoutwithinternalpage). *Originalsplitintervalstillstands.
*/
}
/* *ManyduplicatesstrategyisusedwhenaheapTIDwouldotherwisebe *appended,butthepageisn'tcompletelyfulloflogicalduplicates. * *Thesplitintervaliswidenedtoincludealllegalcandidatesplit *points.Theremightbeafewastwodistinctvaluesinthewhole-page *splitinterval,thoughit'salsopossiblethatmostofthevalueson *thepageareunique.Thefinalsplitpointwilleitherbetothe *immediateleftortotheimmediaterightofthegroupofduplicate *tuplesthatenclosethefirst/delta-optimalsplitpoint(perfect *penaltywassetsothatthelowestdeltasplitpointthatavoids *appendingaheapTIDwillbechosen).Maximizingthenumberof *attributesthatcanbetruncatedawayisnotagoalofthemany *duplicatesstrategy. * *Singlevaluestrategyisusedwhenitisimpossibletoavoidappending *aheapTID.Itarrangestoleavetheleftpageveryfull.This *maximizesspaceutilizationincaseswheretupleswiththesame *attributevaluesspanmanypages.Newlyinsertedduplicateswilltend *tohavehigherheapTIDvalues,sowe'llendupsplittingtotheright *consistently.(Singlevaluestrategyisharmlessthoughnot *particularlyusefulwith!heapkeyspaceindexes.)
*/ elseif (strategy == SPLIT_MANY_DUPLICATES)
{
Assert(state.is_leaf); /* Shouldn't try to truncate away extra user attributes */
Assert(perfectpenalty ==
IndexRelationGetNumberOfKeyAttributes(state.rel)); /* No need to resort splits -- no change in fillfactormult/deltas */
state.interval = state.nsplits;
} elseif (strategy == SPLIT_SINGLE_VALUE)
{
Assert(state.is_leaf); /* Split near the end of the page */
usemult = true;
fillfactormult = BTREE_SINGLEVAL_FILLFACTOR / 100.0; /* Resort split points with new delta */
_bt_deltasortsplits(&state, fillfactormult, usemult); /* Appending a heap TID is unavoidable, so interval of 1 is fine */
state.interval = 1;
}
if (BTreeTupleIsPosting(newhighkey))
postingsz = IndexTupleSize(newhighkey) -
BTreeTupleGetPostingOffset(newhighkey);
}
}
/* Account for all the old tuples */
leftfree = state->leftspace - olddataitemstoleft;
rightfree = state->rightspace -
(state->olddataitemstotal - olddataitemstoleft);
/*
* Subroutine for determining if two heap TIDS are "adjacent".
*
* Adjacent means that the high TID is very likely to have been inserted into
* heap relation immediately after the low TID, probably during the current
* transaction.
*/
static bool
_bt_adjacenthtid(ItemPointer lowhtid, ItemPointer highhtid)
{
BlockNumber lowblk,
highblk;
/* Make optimistic assumption of adjacency when heap blocks match */
if (lowblk == highblk)
return true;
/* When heap block one up, second offset should be FirstOffsetNumber */
if (lowblk + 1 == highblk &&
ItemPointerGetOffsetNumber(highhtid) == FirstOffsetNumber)
return true;
return false;
}
/*
* Subroutine to find the "best" split point among candidate split points.
* The best split point is the split point with the lowest penalty among split
* points that fall within current/final split interval. Penalty is an
* abstract score, with a definition that varies depending on whether we're
* splitting a leaf page or an internal page. See _bt_split_penalty() for
* details.
*
* "perfectpenalty" is assumed to be the lowest possible penalty among
* candidate split points. This allows us to return early without wasting
* cycles on calculating the first differing attribute for all candidate
* splits when that clearly cannot improve our choice (or when we only want a
* minimally distinguishing split point, and don't want to make the split any
* more unbalanced than is necessary).
*
* We return the index of the first existing tuple that should go on the right
* page, plus a boolean indicating if new item is on left of split point.
*/
static OffsetNumber
_bt_bestsplitloc(FindSplitData *state, int perfectpenalty,
bool *newitemonleft, FindSplitStrat strategy)
{
int bestpenalty,
lowsplit;
int highsplit = Min(state->interval, state->nsplits);
SplitPoint *final;
bestpenalty = INT_MAX;
lowsplit = 0;
for (int i = lowsplit; i < highsplit; i++)
{
int penalty;
/*
* There is a risk that the "many duplicates" strategy will repeatedly do
* the wrong thing when there are monotonically decreasing insertions to
* the right of a large group of duplicates. Repeated splits could leave
* a succession of right half pages with free space that can never be
* used. This must be avoided.
*
* Consider the example of the leftmost page in a single integer attribute
* NULLS FIRST index which is almost filled with NULLs. Monotonically
* decreasing integer insertions might cause the same leftmost page to
* split repeatedly at the same point. Each split derives its new high
* key from the lowest current value to the immediate right of the large
* group of NULLs, which will always be higher than all future integer
* insertions, directing all future integer insertions to the same
* leftmost page.
*/
if (strategy == SPLIT_MANY_DUPLICATES && !state->is_rightmost &&
!final->newitemonleft && final->firstrightoff >= state->newitemoff &&
final->firstrightoff < state->newitemoff + 9)
{
/*
* Avoid the problem by performing a 50:50 split when the new item is
* just to the right of the would-be "many duplicates" split point.
* (Note that the test used for an insert that is "just to the right"
* of the split point is conservative.)
*/
final = &state->splits[0];
}
/*
* Return a split interval to use for the default strategy. This is a limit
* on the number of candidate split points to give further consideration to.
* Only a fraction of all candidate splits points (those located at the start
* of the now-sorted splits array) fall within the split interval. Split
* interval is applied within _bt_bestsplitloc().
*
* Split interval represents an acceptable range of split points -- those that
* have leftfree and rightfree values that are acceptably balanced. The final
* split point chosen is the split point with the lowest "penalty" among split
* points in this split interval (unless we change our entire strategy, in
* which case the interval also changes -- see _bt_strategy()).
*
* The "Prefix B-Trees" paper calls split interval sigma l for leaf splits,
* and sigma b for internal ("branch") splits. It's hard to provide a
* theoretical justification for the size of the split interval, though it's
* clear that a small split interval can make tuples on level L+1 much smaller
* on average, without noticeably affecting space utilization on level L.
* (Note that the way that we calculate split interval might need to change if
* suffix truncation is taught to truncate tuples "within" the last
* attribute/datum for data types like text, which is more or less how it is
* assumed to work in the paper.)
*/
static int
_bt_defaultinterval(FindSplitData *state)
{
SplitPoint *spaceoptimal;
int16 tolerance,
lowleftfree,
lowrightfree,
highleftfree,
highrightfree;
/*
* Determine leftfree and rightfree values that are higher and lower than
* we're willing to tolerate. Note that the final split interval will be
* about 10% of nsplits in the common case where all non-pivot tuples
* (data items) from a leaf page are uniformly sized. We're a bit more
* aggressive when splitting internal pages.
*/
if (state->is_leaf)
tolerance = state->olddataitemstotal * LEAF_SPLIT_DISTANCE;
else
tolerance = state->olddataitemstotal * INTERNAL_SPLIT_DISTANCE;
/* First candidate split point is the most evenly balanced */
spaceoptimal = state->splits;
lowleftfree = spaceoptimal->leftfree - tolerance;
lowrightfree = spaceoptimal->rightfree - tolerance;
highleftfree = spaceoptimal->leftfree + tolerance;
highrightfree = spaceoptimal->rightfree + tolerance;
/*
* Iterate through split points, starting from the split immediately after
* 'spaceoptimal'. Find the first split point that divides free space so
* unevenly that including it in the split interval would be unacceptable.
*/
for (int i = 1; i < state->nsplits; i++)
{
SplitPoint *split = state->splits + i;
/* Cannot use curdelta here, since its value is often weighted */
if (split->leftfree < lowleftfree || split->rightfree < lowrightfree ||
split->leftfree > highleftfree || split->rightfree > highrightfree)
return i;
}
return state->nsplits;
}
/*
* Subroutine to decide whether split should use default strategy/initial
* split interval, or whether it should finish splitting the page using
* alternative strategies (this is only possible with leaf pages).
*
* Caller uses alternative strategy (or sticks with default strategy) based
* on how *strategy is set here. Return value is "perfect penalty", which is
* passed to _bt_bestsplitloc() as a final constraint on how far caller is
* willing to go to avoid appending a heap TID when using the many duplicates
* strategy (it also saves _bt_bestsplitloc() useless cycles).
*/
static int
_bt_strategy(FindSplitData *state, SplitPoint *leftpage,
SplitPoint *rightpage, FindSplitStrat *strategy)
{
IndexTuple leftmost,
rightmost;
SplitPoint *leftinterval,
*rightinterval;
int perfectpenalty;
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
/* Assume that alternative strategy won't be used for now */
*strategy = SPLIT_DEFAULT;
/*
* Use smallest observed firstright item size for entire page (actually,
* entire imaginary version of page that includes newitem) as perfect
* penalty on internal pages. This can save cycles in the common case
* where most or all splits (not just splits within interval) have
* firstright tuples that are the same size.
*/
if (!state->is_leaf)
return state->minfirstrightsz;
/*
* Use leftmost and rightmost tuples from leftmost and rightmost splits in
* current split interval
*/
_bt_interval_edges(state, &leftinterval, &rightinterval);
leftmost = _bt_split_lastleft(state, leftinterval);
rightmost = _bt_split_firstright(state, rightinterval);
/*
* If initial split interval can produce a split point that will at least
* avoid appending a heap TID in new high key, we're done. Finish split
* with default strategy and initial split interval.
*/
perfectpenalty = _bt_keep_natts_fast(state->rel, leftmost, rightmost);
if (perfectpenalty <= indnkeyatts)
return perfectpenalty;
/*
* Work out how caller should finish split when even their "perfect"
* penalty for initial/default split interval indicates that the interval
* does not contain even a single split that avoids appending a heap TID.
*
* Use the leftmost split's lastleft tuple and the rightmost split's
* firstright tuple to assess every possible split.
*/
leftmost = _bt_split_lastleft(state, leftpage);
rightmost = _bt_split_firstright(state, rightpage);
/*
* If page (including new item) has many duplicates but is not entirely
* full of duplicates, a many duplicates strategy split will be performed.
* If page is entirely full of duplicates, a single value strategy split
* will be performed.
*/
perfectpenalty = _bt_keep_natts_fast(state->rel, leftmost, rightmost);
if (perfectpenalty <= indnkeyatts)
{
*strategy = SPLIT_MANY_DUPLICATES;
/*
* Many duplicates strategy should split at either side the group of
* duplicates that enclose the delta-optimal split point. Return
* indnkeyatts rather than the true perfect penalty to make that
* happen. (If perfectpenalty was returned here then low cardinality
* composite indexes could have continual unbalanced splits.)
*
* Note that caller won't go through with a many duplicates split in
* rare cases where it looks like there are ever-decreasing insertions
* to the immediate right of the split point. This must happen just
* before a final decision is made, within _bt_bestsplitloc().
*/
return indnkeyatts;
}
/*
* Single value strategy is only appropriate with ever-increasing heap
* TIDs; otherwise, original default strategy split should proceed to
* avoid pathological performance. Use page high key to infer if this is
* the rightmost page among pages that store the same duplicate value.
* This should not prevent insertions of heap TIDs that are slightly out
* of order from using single value strategy, since that's expected with
* concurrent inserters of the same duplicate value.
*/
else if (state->is_rightmost)
*strategy = SPLIT_SINGLE_VALUE;
else
{
ItemId itemid;
IndexTuple hikey;
itemid = PageGetItemId(state->origpage, P_HIKEY);
hikey = (IndexTuple) PageGetItem(state->origpage, itemid);
perfectpenalty = _bt_keep_natts_fast(state->rel, hikey,
state->newitem);
if (perfectpenalty <= indnkeyatts)
*strategy = SPLIT_SINGLE_VALUE;
else
{
/*
* Have caller finish split using default strategy, since page
* does not appear to be the rightmost page for duplicates of the
* value the page is filled with
*/
}
}
return perfectpenalty;
}
/*
* Subroutine to locate leftmost and rightmost splits for current/default
* split interval. Note that it will be the same split iff there is only one
* split in interval.
*/
static void
_bt_interval_edges(FindSplitData *state, SplitPoint **leftinterval,
SplitPoint **rightinterval)
{
int highsplit = Min(state->interval, state->nsplits);
SplitPoint *deltaoptimal;
/*
* Delta is an absolute distance to optimal split point, so both the
* leftmost and rightmost split point will usually be at the end of the
* array
*/
for (int i = highsplit - 1; i >= 0; i--)
{
SplitPoint *distant = state->splits + i;
if (distant->firstrightoff < deltaoptimal->firstrightoff)
{
if (*leftinterval == NULL)
*leftinterval = distant;
}
else if (distant->firstrightoff > deltaoptimal->firstrightoff)
{
if (*rightinterval == NULL)
*rightinterval = distant;
}
else if (!distant->newitemonleft && deltaoptimal->newitemonleft)
{
/*
* "incoming tuple will become firstright" (distant) is to the
* left of "incoming tuple will become lastleft" (delta-optimal)
*/
Assert(distant->firstrightoff == state->newitemoff);
if (*leftinterval == NULL)
*leftinterval = distant;
}
else if (distant->newitemonleft && !deltaoptimal->newitemonleft)
{
/*
* "incoming tuple will become lastleft" (distant) is to the right
* of "incoming tuple will become firstright" (delta-optimal)
*/
Assert(distant->firstrightoff == state->newitemoff);
if (*rightinterval == NULL)
*rightinterval = distant;
}
else
{
/* There was only one or two splits in initial split interval */
Assert(distant == deltaoptimal);
if (*leftinterval == NULL)
*leftinterval = distant;
if (*rightinterval == NULL)
*rightinterval = distant;
}
if (*leftinterval && *rightinterval)
return;
}
Assert(false);
}
/*
* Subroutine to find penalty for caller's candidate split point.
*
* On leaf pages, penalty is the attribute number that distinguishes each side
* of a split. It's the last attribute that needs to be included in new high
* key for left page. It can be greater than the number of key attributes in
* cases where a heap TID will need to be appended during truncation.
*
* On internal pages, penalty is simply the size of the firstright tuple for
* the split (including line pointer overhead). This tuple will become the
* new high key for the left page.
*/
static inline int
_bt_split_penalty(FindSplitData *state, SplitPoint *split)
{
IndexTuple lastleft;
IndexTuple firstright;
if (!state->is_leaf)
{
ItemId itemid;
if (!split->newitemonleft &&
split->firstrightoff == state->newitemoff)
return state->newitemsz;
/*
* Subroutine to get a lastleft IndexTuple for a split point
*/
static inline IndexTuple
_bt_split_lastleft(FindSplitData *state, SplitPoint *split)
{
ItemId itemid;
if (split->newitemonleft && split->firstrightoff == state->newitemoff)
return state->newitem;
/*
* Subroutine to get a firstright IndexTuple for a split point
*/
static inline IndexTuple
_bt_split_firstright(FindSplitData *state, SplitPoint *split)
{
ItemId itemid;
if (!split->newitemonleft && split->firstrightoff == state->newitemoff)
return state->newitem;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.