diff options
author | Peter Geoghegan | 2025-03-05 14:36:48 +0000 |
---|---|---|
committer | Peter Geoghegan | 2025-03-05 14:36:48 +0000 |
commit | 5ead85fbc81162ab1594f656b036a22e814f96b3 (patch) | |
tree | 2ad8afc5c2709eb41eca0e1b8176e729adabdcc2 /src/backend | |
parent | 635f580120b99f6df71d7c12749b22acde61c5ad (diff) |
Show index search count in EXPLAIN ANALYZE.
Expose the count of index searches/index descents in EXPLAIN ANALYZE's
output for index scan nodes. This information is particularly useful
with scans that use ScalarArrayOp quals, where the number of index scans
isn't predictable in advance (at least not with optimizations like the
one added to nbtree by Postgres 17 commit 5bf748b8). It will also be
useful when EXPLAIN ANALYZE shows details of an nbtree index scan that
uses skip scan optimizations set to be introduced by an upcoming patch.
The instrumentation works by teaching index AMs to increment a new
nsearches counter whenever a new index search begins. The counter is
incremented at exactly the same point that index AMs must already
increment the index's pg_stat_*_indexes.idx_scan counter (we're counting
the same event, but at the scan level rather than the relation level).
The new counter is stored in the scan descriptor (IndexScanDescData),
which explain.c reaches by going through the scan node's PlanState.
This approach doesn't match the approach used when tracking other index
scan specific costs (e.g., "Rows Removed by Filter:"). It is similar to
the approach used in other cases where we must track costs that are only
readily accessible inside an access method, and not from the executor
(e.g., "Heap Blocks:" output for a Bitmap Heap Scan). It is inherently
necessary to maintain a counter that can be incremented multiple times
during a single amgettuple call (or amgetbitmap call), and directly
exposing PlanState.instrument to index access methods seems unappealing.
Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Tomas Vondra <tomas@vondra.me>
Reviewed-By: Robert Haas <robertmhaas@gmail.com>
Reviewed-By: Masahiro Ikeda <ikedamsh@oss.nttdata.com>
Reviewed-By: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/CAH2-Wz=PKR6rB7qbx+Vnd7eqeB5VTcrW=iJvAsTsKbdG+kW_UA@mail.gmail.com
Discussion: https://postgr.es/m/CAH2-WzkRqvaqR2CTNqTZP0z6FuL4-3ED6eQB0yx38XBNj1v-4Q@mail.gmail.com
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/access/brin/brin.c | 1 | ||||
-rw-r--r-- | src/backend/access/gin/ginscan.c | 1 | ||||
-rw-r--r-- | src/backend/access/gist/gistget.c | 2 | ||||
-rw-r--r-- | src/backend/access/hash/hashsearch.c | 1 | ||||
-rw-r--r-- | src/backend/access/index/genam.c | 1 | ||||
-rw-r--r-- | src/backend/access/nbtree/nbtree.c | 15 | ||||
-rw-r--r-- | src/backend/access/nbtree/nbtsearch.c | 1 | ||||
-rw-r--r-- | src/backend/access/spgist/spgscan.c | 1 | ||||
-rw-r--r-- | src/backend/commands/explain.c | 40 |
9 files changed, 63 insertions, 0 deletions
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 75a65ec9c75..9f146c12a85 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -591,6 +591,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) opaque = (BrinOpaque *) scan->opaque; bdesc = opaque->bo_bdesc; pgstat_count_index_scan(idxRel); + scan->nsearches++; /* * We need to know the size of the table so that we know how long to diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index 63ded6301e2..8c1bbf36644 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -437,6 +437,7 @@ ginNewScanKey(IndexScanDesc scan) MemoryContextSwitchTo(oldCtx); pgstat_count_index_scan(scan->indexRelation); + scan->nsearches++; } void diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c index cc40e928e0a..609e85fdae1 100644 --- a/src/backend/access/gist/gistget.c +++ b/src/backend/access/gist/gistget.c @@ -625,6 +625,7 @@ gistgettuple(IndexScanDesc scan, ScanDirection dir) GISTSearchItem fakeItem; pgstat_count_index_scan(scan->indexRelation); + scan->nsearches++; so->firstCall = false; so->curPageData = so->nPageData = 0; @@ -750,6 +751,7 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm) return 0; pgstat_count_index_scan(scan->indexRelation); + scan->nsearches++; /* Begin the scan by processing the root page */ so->curPageData = so->nPageData = 0; diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c index a3a1fccf33b..c4f73043772 100644 --- a/src/backend/access/hash/hashsearch.c +++ b/src/backend/access/hash/hashsearch.c @@ -298,6 +298,7 @@ _hash_first(IndexScanDesc scan, ScanDirection dir) HashScanPosItem *currItem; pgstat_count_index_scan(rel); + scan->nsearches++; /* * We do not support hash scans with no index qualification, because we diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index 07bae342e25..0cabfa5de5d 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -119,6 +119,7 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys) scan->ignore_killed_tuples = !scan->xactStartedInRecovery; scan->opaque = NULL; + scan->nsearches = 0; scan->xs_itup = NULL; scan->xs_itupdesc = NULL; diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 45ea6afba1d..eabd54b8c3d 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -70,6 +70,7 @@ typedef struct BTParallelScanDescData BTPS_State btps_pageStatus; /* indicates whether next page is * available for scan. see above for * possible states of parallel scan. */ + uint64 btps_nsearches; /* tracked for IndexScanDescData.nsearches */ slock_t btps_mutex; /* protects above variables, btps_arrElems */ ConditionVariable btps_cv; /* used to synchronize parallel scan */ @@ -557,6 +558,7 @@ btinitparallelscan(void *target) bt_target->btps_nextScanPage = InvalidBlockNumber; bt_target->btps_lastCurrPage = InvalidBlockNumber; bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED; + bt_target->btps_nsearches = 0; ConditionVariableInit(&bt_target->btps_cv); } @@ -583,6 +585,7 @@ btparallelrescan(IndexScanDesc scan) btscan->btps_nextScanPage = InvalidBlockNumber; btscan->btps_lastCurrPage = InvalidBlockNumber; btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED; + /* deliberately don't reset btps_nsearches (matches index_rescan) */ SpinLockRelease(&btscan->btps_mutex); } @@ -676,6 +679,7 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *next_scan_page, { /* Can start scheduled primitive scan right away, so do so */ btscan->btps_pageStatus = BTPARALLEL_ADVANCING; + btscan->btps_nsearches++; for (int i = 0; i < so->numArrayKeys; i++) { BTArrayKeyInfo *array = &so->arrayKeys[i]; @@ -712,6 +716,11 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *next_scan_page, */ btscan->btps_pageStatus = BTPARALLEL_ADVANCING; Assert(btscan->btps_nextScanPage != P_NONE); + if (btscan->btps_nextScanPage == InvalidBlockNumber) + { + Assert(first); + btscan->btps_nsearches++; + } *next_scan_page = btscan->btps_nextScanPage; *last_curr_page = btscan->btps_lastCurrPage; exit_loop = true; @@ -810,6 +819,12 @@ _bt_parallel_done(IndexScanDesc scan) btscan->btps_pageStatus = BTPARALLEL_DONE; status_changed = true; } + + /* + * Don't use local nsearches counter -- overwrite it with the nsearches + * counter that we've been maintaining in shared memory + */ + scan->nsearches = btscan->btps_nsearches; SpinLockRelease(&btscan->btps_mutex); /* wake up all the workers associated with this parallel scan */ diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 472ce06f190..941b4eaaf18 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -950,6 +950,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * _bt_search/_bt_endpoint below */ pgstat_count_index_scan(rel); + scan->nsearches++; /*---------- * Examine the scan keys to discover where we need to start the scan. diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c index 53f910e9d89..8554b453555 100644 --- a/src/backend/access/spgist/spgscan.c +++ b/src/backend/access/spgist/spgscan.c @@ -421,6 +421,7 @@ spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, /* count an indexscan for stats */ pgstat_count_index_scan(scan->indexRelation); + scan->nsearches++; } void diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index d8a7232cedb..78d5d0b67c4 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "access/relscan.h" #include "access/xact.h" #include "catalog/pg_type.h" #include "commands/createas.h" @@ -125,6 +126,7 @@ static void show_recursive_union_info(RecursiveUnionState *rstate, static void show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es); static void show_hashagg_info(AggState *aggstate, ExplainState *es); +static void show_indexsearches_info(PlanState *planstate, ExplainState *es); static void show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es); static void show_instrumentation_count(const char *qlabel, int which, @@ -2096,6 +2098,7 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); + show_indexsearches_info(planstate, es); break; case T_IndexOnlyScan: show_scan_qual(((IndexOnlyScan *) plan)->indexqual, @@ -2112,10 +2115,12 @@ ExplainNode(PlanState *planstate, List *ancestors, if (es->analyze) ExplainPropertyFloat("Heap Fetches", NULL, planstate->instrument->ntuples2, 0, es); + show_indexsearches_info(planstate, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, "Index Cond", planstate, ancestors, es); + show_indexsearches_info(planstate, es); break; case T_BitmapHeapScan: show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, @@ -3856,6 +3861,41 @@ show_hashagg_info(AggState *aggstate, ExplainState *es) } /* + * Show the total number of index searches performed by a + * IndexScan/IndexOnlyScan/BitmapIndexScan node + */ +static void +show_indexsearches_info(PlanState *planstate, ExplainState *es) +{ + Plan *plan = planstate->plan; + struct IndexScanDescData *scanDesc = NULL; + uint64 nsearches = 0; + + if (!es->analyze) + return; + + switch (nodeTag(plan)) + { + case T_IndexScan: + scanDesc = ((IndexScanState *) planstate)->iss_ScanDesc; + break; + case T_IndexOnlyScan: + scanDesc = ((IndexOnlyScanState *) planstate)->ioss_ScanDesc; + break; + case T_BitmapIndexScan: + scanDesc = ((BitmapIndexScanState *) planstate)->biss_ScanDesc; + break; + default: + break; + } + + if (scanDesc) + nsearches = scanDesc->nsearches; + + ExplainPropertyUInteger("Index Searches", NULL, nsearches, es); +} + +/* * Show exact/lossy pages for a BitmapHeapScan node */ static void |