From aec7f6858d2117fe190f6852509c70fa6b9f6853 Mon Sep 17 00:00:00 2001 From: Alexandre Felipe Date: Tue, 15 Sep 2026 21:59:01 +0100 Subject: [PATCH-v1 1/4] nbtree: skip insertion of existing tuples This patch checks whether the tuple being inserted is present in the index. A new field was added to BTInsertState struct, that field takes space previously used by padding (assuming sizeof(bool) == 1 and sizeof(OffsetNumber) == 2). This flag is set when searching for the insertion point in _bt_binsrch_posting and _bt_binsrch_insert, then check at _bt_doinsert. An alternative implementation could check the duplicate at _bt_doinsert using the insertstate offsets, without the requiring the new, but during the search the duplicate appears naturally, while rechecking at _bt_doinsert would require some new code, and halding posting lists separate from regular tuples. Removed a cmpval that made the code less readable in _bt_binsrch_insert. --- src/backend/access/nbtree/nbtinsert.c | 11 +++++++++- src/backend/access/nbtree/nbtsearch.c | 29 ++++++++++++++++++--------- src/include/access/nbtree.h | 1 + 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 3b945342d83..8d3e2a6501c 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -157,6 +157,7 @@ _bt_doinsert(Relation rel, IndexTuple itup, insertstate.itemsz = MAXALIGN(IndexTupleSize(itup)); insertstate.itup_key = itup_key; insertstate.bounds_valid = false; + insertstate.is_duplicate = false; insertstate.buf = InvalidBuffer; insertstate.postingoff = 0; @@ -260,7 +261,15 @@ search: */ newitemoff = _bt_findinsertloc(rel, &insertstate, checkingunique, indexUnchanged, stack, heapRel); - _bt_insertonpg(rel, heapRel, itup_key, insertstate.buf, InvalidBuffer, + + if (insertstate.is_duplicate) + elog(DEBUG1, + "btree duplicate index tuple: index \"%s\" TID (%u,%u)", + RelationGetRelationName(rel), + ItemPointerGetBlockNumber(&itup->t_tid), + ItemPointerGetOffsetNumber(&itup->t_tid)); + else + _bt_insertonpg(rel, heapRel, itup_key, insertstate.buf, InvalidBuffer, stack, itup, insertstate.itemsz, newitemoff, insertstate.postingoff, false); } diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 5964bc9195e..2082a2d1a7c 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -33,7 +33,7 @@ static Buffer _bt_moveright(Relation rel, Relation heaprel, BTScanInsert key, Buffer buf, bool forupdate, BTStack stack, int access); static OffsetNumber _bt_binsrch(Relation rel, BTScanInsert key, Buffer buf); -static int _bt_binsrch_posting(BTScanInsert key, Page page, +static int _bt_binsrch_posting(BTInsertState insertstate, Page page, OffsetNumber offnum); static inline void _bt_returnitem(IndexScanDesc scan, BTScanOpaque so); static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir); @@ -482,8 +482,7 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) OffsetNumber low, high, stricthigh; - int32 result, - cmpval; + int32 result; page = BufferGetPage(insertstate->buf); opaque = BTPageGetOpaque(page); @@ -491,6 +490,7 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) Assert(P_ISLEAF(opaque)); Assert(!key->nextkey); Assert(insertstate->postingoff == 0); + insertstate->is_duplicate = false; if (!insertstate->bounds_valid) { @@ -529,8 +529,6 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) high++; /* establish the loop invariant for high */ stricthigh = high; /* high initially strictly higher */ - cmpval = 1; /* !nextkey comparison value */ - while (high > low) { OffsetNumber mid = low + ((high - low) / 2); @@ -539,7 +537,7 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) result = _bt_compare(rel, key, page, mid); - if (result >= cmpval) + if (result > 0) low = mid + 1; else { @@ -547,7 +545,6 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) if (result != 0) stricthigh = high; } - /* * If tuple at offset located by binary search is a posting list whose * TID range overlaps with caller's scantid, perform posting list @@ -572,7 +569,7 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) BufferGetBlockNumber(insertstate->buf), RelationGetRelationName(rel)))); - insertstate->postingoff = _bt_binsrch_posting(key, page, mid); + insertstate->postingoff = _bt_binsrch_posting(insertstate, page, mid); } } @@ -602,8 +599,9 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate) *---------- */ static int -_bt_binsrch_posting(BTScanInsert key, Page page, OffsetNumber offnum) +_bt_binsrch_posting(BTInsertState insertstate, Page page, OffsetNumber offnum) { + BTScanInsert key = insertstate->itup_key; IndexTuple itup; ItemId itemid; int low, @@ -624,7 +622,17 @@ _bt_binsrch_posting(BTScanInsert key, Page page, OffsetNumber offnum) itemid = PageGetItemId(page, offnum); itup = (IndexTuple) PageGetItem(page, itemid); if (!BTreeTupleIsPosting(itup)) + { + /* + * Let K be the tuple (scankey, tid) being inserted. + * For this search there is is at least one tuple x such that + * L <= x <= K <= H + * Since itup is not a posting list, the search space shrinks + * to L = H, forcing x = K. + */ + insertstate->is_duplicate = true; return 0; + } Assert(key->heapkeyspace && key->allequalimage); @@ -653,7 +661,10 @@ _bt_binsrch_posting(BTScanInsert key, Page page, OffsetNumber offnum) else if (res < 0) high = mid; else + { + insertstate->is_duplicate = true; return mid; + } } /* Exact match not found */ diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 3097e9bb1af..db92d90bb86 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -832,6 +832,7 @@ typedef struct BTInsertStateData * _bt_findinsertloc for details. */ bool bounds_valid; + bool is_duplicate; OffsetNumber low; OffsetNumber stricthigh; -- 2.53.0