From c1d6a412a943189372cc733b1041bff96e8c8e76 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Thu, 19 Mar 2026 12:32:34 -0400 Subject: [PATCH v16 04/17] Add VISITED_PAGES_LIMIT to heapam --- src/include/access/relscan.h | 7 +++++++ src/backend/access/heap/heapam_handler.c | 22 ++++++++++++++++++++++ src/backend/access/index/genam.c | 1 + src/backend/utils/adt/selfuncs.c | 9 +++++---- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 9c1563c88..13370b1c9 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -372,6 +372,13 @@ typedef struct IndexScanDescData /* parallel index scan information, in shared memory */ struct ParallelIndexScanDescData *parallel_scan; + + /* + * Limit on distinct heap pages visited before giving up (0 = no limit). + * Used by selfuncs.c to bound the cost of get_actual_variable_endpoint(). + */ + uint8 xs_visited_pages_limit; + } IndexScanDescData; /* Generic structure for parallel scans */ diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index b8cc3d39c..1b58db199 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -824,6 +824,7 @@ heapam_index_only_getnext_slot(IndexScanDesc scan, ScanDirection direction, heap_continue = false; Assert(scan->xs_want_itup); + Assert(scan->xs_visited_pages_limit == 0); for (;;) { @@ -906,6 +907,9 @@ heapam_index_only_batch_getnext_slot(IndexScanDesc scan, ScanDirection direction ItemPointer tid; bool all_visible, heap_continue = false; + BlockNumber last_visited_block = InvalidBlockNumber; + uint8 n_visited_pages = 0, + xs_visited_pages_limit = scan->xs_visited_pages_limit; Assert(scan->xs_want_itup); @@ -936,7 +940,25 @@ heapam_index_only_batch_getnext_slot(IndexScanDesc scan, ScanDirection direction scan->instrument->nheapfetches++; if (!heapam_index_fetch_heap(scan, slot, &heap_continue, true)) + { + /* + * No visible tuple. If caller set a visited-pages limit + * (only selfuncs.c does this), count distinct heap pages and + * give up once we've visited too many. + */ + if (unlikely(xs_visited_pages_limit > 0)) + { + Assert(hscan->xs_blk == ItemPointerGetBlockNumber(tid)); + + if (hscan->xs_blk != last_visited_block) + { + last_visited_block = hscan->xs_blk; + if (++n_visited_pages > xs_visited_pages_limit) + return false; /* give up */ + } + } continue; /* no visible tuple, try next index entry */ + } ExecClearTuple(slot); diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index 6e87169c2..d0b68f38e 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -126,6 +126,7 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys) scan->xs_itupdesc = NULL; scan->xs_hitup = NULL; scan->xs_hitupdesc = NULL; + scan->xs_visited_pages_limit = 0; scan->batch_index_opaque_size = 0; scan->batch_tuples_workspace = 0; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 1f8b0e684..768581cf7 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -7168,11 +7168,11 @@ get_actual_variable_endpoint(Relation heapRel, * pages. When we fail for that reason, the caller will end up using * whatever extremal value is recorded in pg_statistic. * - * XXX This can't work with the new table_index_getnext_slot interface, - * which simply won't return a tuple that isn't visible to our snapshot. - * table_index_getnext_slot will need some kind of callback that provides - * a way for the scan to give up when the costs start to get out of hand. + * We set xs_visited_pages_limit to tell the table AM to count distinct + * heap pages visited for non-visible tuples and give up after the limit + * is exceeded. */ +#define VISITED_PAGES_LIMIT 100 InitNonVacuumableSnapshot(SnapshotNonVacuumable, GlobalVisTestFor(heapRel)); @@ -7180,6 +7180,7 @@ get_actual_variable_endpoint(Relation heapRel, &SnapshotNonVacuumable, NULL, 1, 0); Assert(index_scan->xs_want_itup); + index_scan->xs_visited_pages_limit = VISITED_PAGES_LIMIT; index_rescan(index_scan, scankeys, 1, NULL, 0); /* Fetch first/next tuple in specified direction */ -- 2.53.0