From 38eae48e941e3e96774b4507a97187128ae7df24 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Thu, 19 Mar 2026 12:32:34 -0400 Subject: [PATCH v17 04/18] Implement VISITED_PAGES_LIMIT within heapam. The new slot-based interface added by commit XXX to enable index prefetching made it impossible to enforce VISITED_PAGES_LIMIT. Fix the problem by implementing generalizing VISITED_PAGES_LIMIT into a table AM utility, implemented by heapam's index-only scan slot callbacks. Author: Peter Geoghegan Reviewed-By: Andres Freund Discussion: https://postgr.es/m/CAH2-Wz=HJc+QV2AZ9mUY43aKL+n+a1JQ-7OGE=MOkqSAtoKJug@mail.gmail.com --- src/include/access/relscan.h | 7 +++++++ src/backend/access/heap/heapam_handler.c | 24 ++++++++++++++++++++++++ src/backend/access/index/genam.c | 1 + src/backend/utils/adt/selfuncs.c | 7 +++++-- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 301afa7b6..10f350b6f 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -381,6 +381,13 @@ typedef struct IndexScanDescData bool *xs_orderbynulls; bool xs_recheckorderby; + /* + * An approximate limit on the amount of work, measured in pages touched, + * imposed on the index scan. The default, 0, means no limit. Used by + * selfuncs.c to bound the cost of get_actual_variable_endpoint(). + */ + uint8 xs_visited_pages_limit; + /* parallel index scan information, in shared memory */ struct ParallelIndexScanDescData *parallel_scan; } IndexScanDescData; diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 7d3f81d74..a40f7fe32 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -657,6 +657,12 @@ heapam_index_getnext_slot(IndexScanDesc scan, ScanDirection direction, ItemPointer tid; bool heap_continue = false; bool all_visible = false; + BlockNumber last_visited_block = InvalidBlockNumber; + uint8 n_visited_pages = 0, + xs_visited_pages_limit = 0; + + if (index_only) + xs_visited_pages_limit = scan->xs_visited_pages_limit; for (;;) { @@ -702,7 +708,25 @@ heapam_index_getnext_slot(IndexScanDesc scan, ScanDirection direction, if (!heapam_index_fetch_heap(scan, slot, &heap_continue, amgetbatch)) + { + /* + * 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 1b21d421e..294f2ca47 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 3ff5d0c1d..9f623d97b 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -7168,9 +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. + * 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)); @@ -7178,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