]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
nbtree: Avoid allocating _bt_search stack.
authorPeter Geoghegan <pg@bowt.ie>
Thu, 12 Mar 2026 17:22:36 +0000 (13:22 -0400)
committerPeter Geoghegan <pg@bowt.ie>
Thu, 12 Mar 2026 17:22:36 +0000 (13:22 -0400)
Avoid allocating memory for an nbtree descent stack during index scans.
We only require a descent stack during inserts, when it is used to
determine where to insert a new pivot tuple/downlink into the target
leaf page's parent page in the event of a page split.  (Page deletion's
first phase also performs a _bt_search that requires a descent stack.)

This optimization improves performance by minimizing palloc churn.  It
speeds up index scans that call _bt_search frequently/descend the index
many times, especially when the cost of scanning the index dominates
(e.g., with index-only skip scans).  Testing has shown that the
underlying issue causes performance problems for an upcoming patch that
will replace btgettuple with a new btgetbatch interface to enable I/O
prefetching.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Tomas Vondra <tomas@vondra.me>
Discussion: https://postgr.es/m/CAH2-Wzmy7NMba9k8m_VZ-XNDZJEUQBU8TeLEeL960-rAKb-+tQ@mail.gmail.com

contrib/amcheck/verify_nbtree.c
src/backend/access/nbtree/nbtinsert.c
src/backend/access/nbtree/nbtpage.c
src/backend/access/nbtree/nbtsearch.c
src/backend/access/nbtree/nbtutils.c
src/include/access/nbtree.h

index e04b7ca694ebb093710515cce6576eb128d6e830..b74ab5f7a057a94385888d991d7358f5c4b4a682 100644 (file)
@@ -3009,7 +3009,6 @@ static bool
 bt_rootdescend(BtreeCheckState *state, IndexTuple itup)
 {
        BTScanInsert key;
-       BTStack         stack;
        Buffer          lbuf;
        bool            exists;
 
@@ -3026,7 +3025,7 @@ bt_rootdescend(BtreeCheckState *state, IndexTuple itup)
         */
        Assert(state->readonly && state->rootdescend);
        exists = false;
-       stack = _bt_search(state->rel, NULL, key, &lbuf, BT_READ);
+       _bt_search(state->rel, NULL, key, &lbuf, BT_READ, false);
 
        if (BufferIsValid(lbuf))
        {
@@ -3053,7 +3052,6 @@ bt_rootdescend(BtreeCheckState *state, IndexTuple itup)
                _bt_relbuf(state->rel, lbuf);
        }
 
-       _bt_freestack(stack);
        pfree(key);
 
        return exists;
index 796e1513ddf9605078f643273ea3f7b8ba471829..91bb37d66553ae424be8707a3cc6dd9159385797 100644 (file)
@@ -61,6 +61,7 @@ static Buffer _bt_split(Relation rel, Relation heaprel, BTScanInsert itup_key,
                                                IndexTuple nposting, uint16 postingoff);
 static void _bt_insert_parent(Relation rel, Relation heaprel, Buffer buf,
                                                          Buffer rbuf, BTStack stack, bool isroot, bool isonly);
+static void _bt_freestack(BTStack stack);
 static Buffer _bt_newlevel(Relation rel, Relation heaprel, Buffer lbuf, Buffer rbuf);
 static inline bool _bt_pgaddtup(Page page, Size itemsize, const IndexTupleData *itup,
                                                                OffsetNumber itup_off, bool newfirstdataitem);
@@ -380,7 +381,7 @@ _bt_search_insert(Relation rel, Relation heaprel, BTInsertState insertstate)
 
        /* Cannot use optimization -- descend tree, return proper descent stack */
        return _bt_search(rel, heaprel, insertstate->itup_key, &insertstate->buf,
-                                         BT_WRITE);
+                                         BT_WRITE, true);
 }
 
 /*
@@ -2449,6 +2450,22 @@ _bt_getstackbuf(Relation rel, Relation heaprel, BTStack stack, BlockNumber child
        }
 }
 
+/*
+ * _bt_freestack() -- free a retracement stack made by _bt_search_insert.
+ */
+static void
+_bt_freestack(BTStack stack)
+{
+       BTStack         ostack;
+
+       while (stack != NULL)
+       {
+               ostack = stack;
+               stack = stack->bts_parent;
+               pfree(ostack);
+       }
+}
+
 /*
  *     _bt_newlevel() -- Create a new level above root page.
  *
index 4125c185e8bee66af7589e585c9b029073f4513d..9aa78068a030bbc8f39c7de5e64a19625b2acbb0 100644 (file)
@@ -1966,7 +1966,7 @@ _bt_pagedel(Relation rel, Buffer leafbuf, BTVacState *vstate)
                                /* Set up a BTLessStrategyNumber-like insertion scan key */
                                itup_key->nextkey = false;
                                itup_key->backward = true;
-                               stack = _bt_search(rel, NULL, itup_key, &sleafbuf, BT_READ);
+                               stack = _bt_search(rel, NULL, itup_key, &sleafbuf, BT_READ, true);
                                /* won't need a second lock or pin on leafbuf */
                                _bt_relbuf(rel, sleafbuf);
 
index 32ae0bda892f1096650eaaf2d9184325c144a82a..ab452c7b02645e820db71040d49959fe3f8d4b86 100644 (file)
@@ -80,10 +80,13 @@ _bt_drop_lock_and_maybe_pin(Relation rel, BTScanOpaque so)
  * The passed scankey is an insertion-type scankey (see nbtree/README),
  * but it can omit the rightmost column(s) of the index.
  *
- * Return value is a stack of parent-page pointers (i.e. there is no entry for
- * the leaf level/page).  *bufP is set to the address of the leaf-page buffer,
- * which is locked and pinned.  No locks are held on the parent pages,
- * however!
+ * If returnstack is true, return value is a stack of parent-page pointers
+ * (i.e. there is no entry for the leaf level/page).  If returnstack is false,
+ * we just return NULL.  This scheme allows callers that don't need a descent
+ * stack to avoid palloc churn.
+ *
+ * When we return, *bufP is set to the address of the leaf-page buffer, which
+ * is locked and pinned.  No locks are held on the parent pages, however!
  *
  * The returned buffer is locked according to access parameter.  Additionally,
  * access = BT_WRITE will allow an empty root page to be created and returned.
@@ -96,7 +99,7 @@ _bt_drop_lock_and_maybe_pin(Relation rel, BTScanOpaque so)
  */
 BTStack
 _bt_search(Relation rel, Relation heaprel, BTScanInsert key, Buffer *bufP,
-                  int access)
+                  int access, bool returnstack)
 {
        BTStack         stack_in = NULL;
        int                     page_access = BT_READ;
@@ -160,10 +163,14 @@ _bt_search(Relation rel, Relation heaprel, BTScanInsert key, Buffer *bufP,
                 * page one level down, it usually ends up inserting a new pivot
                 * tuple/downlink immediately after the location recorded here.
                 */
-               new_stack = (BTStack) palloc_object(BTStackData);
-               new_stack->bts_blkno = BufferGetBlockNumber(*bufP);
-               new_stack->bts_offset = offnum;
-               new_stack->bts_parent = stack_in;
+               if (returnstack)
+               {
+                       new_stack = (BTStack) palloc_object(BTStackData);
+                       new_stack->bts_blkno = BufferGetBlockNumber(*bufP);
+                       new_stack->bts_offset = offnum;
+                       new_stack->bts_parent = stack_in;
+                       stack_in = new_stack;
+               }
 
                /*
                 * Page level 1 is lowest non-leaf page level prior to leaves.  So, if
@@ -177,7 +184,6 @@ _bt_search(Relation rel, Relation heaprel, BTScanInsert key, Buffer *bufP,
                *bufP = _bt_relandgetbuf(rel, *bufP, child, page_access);
 
                /* okay, all set to move down a level */
-               stack_in = new_stack;
        }
 
        /*
@@ -879,7 +885,6 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 {
        Relation        rel = scan->indexRelation;
        BTScanOpaque so = (BTScanOpaque) scan->opaque;
-       BTStack         stack;
        OffsetNumber offnum;
        BTScanInsertData inskey;
        ScanKey         startKeys[INDEX_MAX_KEYS];
@@ -1506,10 +1511,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
         * position ourselves on the target leaf page.
         */
        Assert(ScanDirectionIsBackward(dir) == inskey.backward);
-       stack = _bt_search(rel, NULL, &inskey, &so->currPos.buf, BT_READ);
-
-       /* don't need to keep the stack around... */
-       _bt_freestack(stack);
+       _bt_search(rel, NULL, &inskey, &so->currPos.buf, BT_READ, false);
 
        if (!BufferIsValid(so->currPos.buf))
        {
@@ -1527,8 +1529,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
                if (IsolationIsSerializable())
                {
                        PredicateLockRelation(rel, scan->xs_snapshot);
-                       stack = _bt_search(rel, NULL, &inskey, &so->currPos.buf, BT_READ);
-                       _bt_freestack(stack);
+                       _bt_search(rel, NULL, &inskey, &so->currPos.buf, BT_READ, false);
                }
 
                if (!BufferIsValid(so->currPos.buf))
index f14ff95cb2b76345a4d8a53f752b962c0d3fc04f..32d94e2366bebb6cfd5a1f3f2384233ef7cba40e 100644 (file)
@@ -144,22 +144,6 @@ _bt_mkscankey(Relation rel, IndexTuple itup)
        return key;
 }
 
-/*
- * free a retracement stack made by _bt_search.
- */
-void
-_bt_freestack(BTStack stack)
-{
-       BTStack         ostack;
-
-       while (stack != NULL)
-       {
-               ostack = stack;
-               stack = stack->bts_parent;
-               pfree(ostack);
-       }
-}
-
 /*
  * qsort comparison function for int arrays
  */
index 772248596859359fa55d362d0372a2045a506ff4..da7503c57b6f65267f90365a586de22cd4cb283f 100644 (file)
@@ -1284,7 +1284,7 @@ extern int        _bt_binsrch_array_skey(FmgrInfo *orderproc,
  * prototypes for functions in nbtsearch.c
  */
 extern BTStack _bt_search(Relation rel, Relation heaprel, BTScanInsert key,
-                                                 Buffer *bufP, int access);
+                                                 Buffer *bufP, int access, bool returnstack);
 extern OffsetNumber _bt_binsrch_insert(Relation rel, BTInsertState insertstate);
 extern int32 _bt_compare(Relation rel, BTScanInsert key, Page page, OffsetNumber offnum);
 extern bool _bt_first(IndexScanDesc scan, ScanDirection dir);
@@ -1295,7 +1295,6 @@ extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost);
  * prototypes for functions in nbtutils.c
  */
 extern BTScanInsert _bt_mkscankey(Relation rel, IndexTuple itup);
-extern void _bt_freestack(BTStack stack);
 extern void _bt_killitems(IndexScanDesc scan);
 extern BTCycleId _bt_vacuum_cycleid(Relation rel);
 extern BTCycleId _bt_start_vacuum(Relation rel);