From 1508450e79cb6e1e22404e47021771ee2dfa51ec Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Wed, 25 Mar 2026 16:58:09 -0400 Subject: [PATCH v27 6/9] heapam: Add index scan I/O prefetching. This commit implements I/O prefetching for index scans (and index-only scans that require heap fetches). This was made possible by the recent addition of batching interfaces to both the table AM and index AM APIs. The amgetbatch index AM interface provides batches of matching TIDs (rather than one tuple at a time), each of which must be taken from index tuples that appear together on a single index page. This allows multiple batches to be held open simultaneously. Giving the table AM an explicit understanding of index AM concepts/index page boundaries allows it to consider all of the relevant costs and benefits. Prefetching is implemented using a prefetching position under the control of the table AM. This is closely related to the scan position added by commit FIXME, which introduced the amgetbatch interface. A read stream callback advances the read stream as needed to provide sufficiently many heap block numbers to maintain the read stream's target prefetch distance. Testing has shown that index prefetching can make index scans much faster. Large range scans that return many tuples can be as much as 30x faster with local SSDs when buffered I/O is used, and 50x faster or more with higher-latency storage such as network-attached block devices, where the benefit of hiding I/O latency through prefetching is even greater. A new GUC (enable_indexscan_prefetch) controls the use of index prefetching. The default setting is 'on', so all plain index scans use prefetching where support exists. All index-only scans will also use prefetching automatically where supported (once the scan starts to require a significant number of heap fetches). An important goal of the amgetbatch design is to enable the table AM's read stream callback to advance its prefetch position using TIDs that appear on a leaf page that's ahead of the current scan position's leaf page. This is crucial with scans of indexes where each leaf page happens to have relatively few distinct heap blocks among its matching TIDs (as well as with scans with leaf pages that have relatively few total matching items). Index scans can have as many as 64 open batches, which testing has shown to be about the maximum number that can ever be useful. Batches are maintained in scan order using a simple ring buffer data structure. In rare cases where the scan exceeds this quasi-arbitrary limit of 64, the read stream is temporarily paused using the read stream pausing mechanism added by commit 38229cb9. Prefetching (via the read stream) is resumed only after the scan position advances beyond its current open batch and then frees and removes the batch from the scan's batch ring buffer. Testing has shown that it isn't very common for scans to hold open more than about 10 batches to get the desired I/O prefetch distance. The heuristic used to decide when to begin prefetching delays initialization of the scan's read stream until the scan transitions from its first batch to its second batch. Each batch corresponds to matching TIDs from a single index leaf page, so prefetching only begins once the scan reads from its second leaf page containing at least one matching item. A selective index scan that touches only one leaf page never reaches the second batch, so the heuristic correctly avoids prefetching overhead. The picture for more complicated cases is mixed. The same principle applies to nestloop inner index scans with very tight limits (e.g., a correlated subquery with LIMIT 1), where each rescan reads from only a single leaf page: the heuristic avoids the cost of repeatedly resetting a read stream across many rescans. On the other hand, some selective scans that access randomly-ordered heap pages would genuinely benefit from prefetching, but never get as far as reaching their second batch -- a missed opportunity, where the heuristic is overly cautious. Conversely, the heuristic is not cautious enough with slightly less selective nestloop inner scans (e.g., LIMIT 3 within a LATERAL join). These rescans may span two leaf pages, just barely crossing the second-batch threshold, while still only needing to fetch two or three heap pages -- not enough for prefetching to realistically help or pay for itself on any individual rescan. Such queries are regressed by the work from this commit (relative to PostgreSQL 18), though only when the scan has to read heap pages from storage. Adding a smarter heuristic that addresses both shortcomings remains as work for a future release. Passing down an ExecSetTupleBound style hint and using that hint to influence how the read stream ramps up its distance seems like a promising approach. Author: Tomas Vondra Author: Peter Geoghegan Reviewed-by: Andres Freund Reviewed-by: Thomas Munro Discussion: https://postgr.es/m/cf85f46f-b02f-05b2-5248-5000b894ebab@enterprisedb.com --- src/include/access/heapam.h | 13 + src/include/access/indexbatch.h | 224 +++++++++- src/include/access/relscan.h | 7 + src/include/optimizer/cost.h | 1 + src/backend/access/heap/heapam_indexscan.c | 420 +++++++++++++++++- src/backend/access/index/indexbatch.c | 36 +- src/backend/optimizer/path/costsize.c | 1 + src/backend/utils/misc/guc_parameters.dat | 7 + src/backend/utils/misc/postgresql.conf.sample | 1 + doc/src/sgml/config.sgml | 16 + doc/src/sgml/indexam.sgml | 95 +++- doc/src/sgml/tableam.sgml | 8 + src/test/regress/expected/sysviews.out | 3 +- src/tools/pgindent/typedefs.list | 1 + 14 files changed, 812 insertions(+), 21 deletions(-) diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 12f8bdabb..080135449 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -135,6 +135,19 @@ typedef struct IndexScanHeapData /* Plain index scan xs_lastinblock optimization */ bool xs_lastinblock; /* last TID on this block in current batch? */ + /* + * Read stream state for prefetching (only used during amgetbatch scans). + * + * The read stream moves ahead of the scan's current position using its + * own prefetching position (per the tableam_util_prefetchpos_* + * conventions from indexbatch.h). The read stream is allocated early in + * the scan, and reset on rescan (and when the scan direction changes). + */ + bool xs_paused; /* paused until next batch is read? */ + ScanDirection xs_read_stream_dir; /* index scan direction */ + BlockNumber xs_prefetch_block; /* last block returned to xs_read_stream */ + ReadStream *xs_read_stream; /* prefetching read stream */ + /* Per-tuple context for padding "name" columns during index-only scans */ MemoryContext xs_itup_cxt; } IndexScanHeapData; diff --git a/src/include/access/indexbatch.h b/src/include/access/indexbatch.h index b22e02a52..8990c5a08 100644 --- a/src/include/access/indexbatch.h +++ b/src/include/access/indexbatch.h @@ -209,6 +209,41 @@ index_scan_batch_index_opaque_dyn(IndexScanDesc scan, IndexScanBatch batch) * ---------------------------------------------------------------------------- */ +/* + * Compare two batch ring positions in the given scan direction. + * + * Returns negative if pos1 is behind pos2, 0 if equal, positive if pos1 is + * ahead of pos2. + */ +static inline int +index_scan_pos_cmp(BatchRingItemPos *pos1, BatchRingItemPos *pos2, + ScanDirection direction) +{ + int8 batchdiff; + + Assert(pos1->valid && pos2->valid); + + batchdiff = (int8) (pos1->batch - pos2->batch); + + Assert(batchdiff > -INDEX_SCAN_MAX_BATCHES && + batchdiff < INDEX_SCAN_MAX_BATCHES); + + if (batchdiff != 0) + { + /* Resolve comparison using differing batch offsets */ + return batchdiff; + } + + /* + * Resolve comparison using items[]-wise indexes from caller's positions, + * since both positions point to the same ring buffer batch + */ + if (ScanDirectionIsForward(direction)) + return pos1->item - pos2->item; + else + return pos2->item - pos1->item; +} + /* * Advance position to its next item in the batch. * @@ -310,6 +345,7 @@ tableam_util_batchscan_init(IndexScanDesc scan) Assert(scan->indexRelation->rd_indam->amgetbatch != NULL); scan->batchringbuf.scanPos.valid = false; + scan->batchringbuf.prefetchPos.valid = false; scan->batchringbuf.markPos.valid = false; scan->batchringbuf.markBatch = NULL; @@ -359,7 +395,7 @@ tableam_util_scanpos_advance(IndexScanDesc scan, ScanDirection direction, /* * scanPos is valid, so scanBatch must already be loaded in batch ring - * buffer. We rely on that here. + * buffer. We rely on that here (can't do this with prefetchBatch). */ pg_assume(batchringbuf->headBatch == scanPos->batch); @@ -371,9 +407,9 @@ tableam_util_scanpos_advance(IndexScanDesc scan, ScanDirection direction, /* * Fetch the next batch of matching items for the scan (or the first). * - * Called when caller's current batch (passed to us as priorBatch) has no more - * matching items in the given scan direction. Caller passes a NULL - * priorBatch on the first call here for the scan. + * Called when caller's current scanBatch/prefetchBatch (passed to us as + * priorBatch) has no more matching items in the given scan direction. Caller + * passes a NULL priorBatch on the first call here for the scan. * * Returns the next batch to be processed by caller in the given scan * direction, or NULL when there are no more matches in that direction. @@ -387,7 +423,7 @@ tableam_util_scanpos_advance(IndexScanDesc scan, ScanDirection direction, * free any batches here, though; that is a separate step (callers shouldn't * free a batch until they're definitely done with it, which is completely * independent of needing the next batch in line). The caller is responsible - * for advancing their own position. + * for advancing their own scanPos/prefetchPos position. */ static pg_attribute_always_inline IndexScanBatch tableam_util_fetch_next_batch(IndexScanDesc scan, ScanDirection direction, @@ -499,15 +535,21 @@ tableam_util_fetch_next_batch(IndexScanDesc scan, ScanDirection direction, * Called after tableam_util_fetch_next_batch returns newScanBatch, the next * batch that scanPos will consume matching items from. We release the * now-obsolescent old scanBatch (the ring buffer's head batch), freeing up - * its ring buffer slot. (When newScanBatch is the scan's first batch, there - * is no old scanBatch for us to release.) + * its ring buffer slot. + * + * Returns true when a previously occupied ring buffer slot was freed. A + * table AM that paused its prefetch mechanism because the ring buffer was + * full (see tableam_util_prefetchpos_advance) can resume it now that there's + * space to store one more batch. Returns false when newScanBatch is the + * scan's first batch (i.e. there was no old scanBatch for us to release). */ -static pg_attribute_always_inline void +static pg_attribute_always_inline bool tableam_util_scanpos_nextbatch(IndexScanDesc scan, ScanDirection direction, IndexScanBatch newScanBatch) { BatchRingBuffer *batchringbuf = &scan->batchringbuf; BatchRingItemPos *scanPos = &batchringbuf->scanPos; + BatchRingItemPos *prefetchPos = &batchringbuf->prefetchPos; bool releaseOldHeadBatch = scanPos->valid; IndexScanBatch headBatch; @@ -519,7 +561,7 @@ tableam_util_scanpos_nextbatch(IndexScanDesc scan, ScanDirection direction, { /* newScanBatch is the scan's first and only batch */ Assert(batchringbuf->headBatch == scanPos->batch); - return; + return false; } headBatch = index_scan_batch(scan, batchringbuf->headBatch); @@ -530,12 +572,176 @@ tableam_util_scanpos_nextbatch(IndexScanDesc scan, ScanDirection direction, /* free obsolescent head batch (unless it is scan's markBatch) */ tableam_util_release_batch(scan, headBatch); + /* + * If we're about to release the batch that prefetchPos currently points + * to, just invalidate prefetchPos. This keeps prefetchPos from ever + * falling behind scanPos at the batch granularity, which + * tableam_util_prefetchpos_catchup relies on. + */ + if (prefetchPos->valid && + prefetchPos->batch == batchringbuf->headBatch) + prefetchPos->valid = false; + /* Remove the batch from the ring buffer (even if it's markBatch) */ batchringbuf->headBatch++; /* Postconditions for having freed up a ring buffer slot */ + Assert(!prefetchPos->valid || + index_scan_batch_loaded(scan, prefetchPos->batch)); Assert(!index_scan_batch_full(scan)); Assert(batchringbuf->headBatch == scanPos->batch); + + return true; +} + +/* + * Handle initialization of the scan's prefetchPos, when prefetchPos isn't + * yet valid (also handles the prefetchPos < scanPos edge case). + * + * Called at the start of each table AM prefetch callback call. Returns true + * after setting prefetchPos to the scan's current scanPos. That's a special + * case: the prefetch callback should process the very item that the scan is + * on directly (e.g., by returning that item's table block to its read + * stream), rather than reading ahead of the scan. Returns false when + * prefetchPos is ahead of (or equal to) scanPos, in which case the prefetch + * callback picks up from where its last call left off. + */ +static inline bool +tableam_util_prefetchpos_catchup(IndexScanDesc scan, ScanDirection direction) +{ + BatchRingBuffer *batchringbuf = &scan->batchringbuf; + BatchRingItemPos *scanPos = &batchringbuf->scanPos; + BatchRingItemPos *prefetchPos = &batchringbuf->prefetchPos; + + /* + * scanPos must always be valid when prefetching takes place. There has + * to be at least one batch, loaded as the scan's scanBatch. + */ + Assert(index_scan_batch_count(scan) > 0); + Assert(scanPos->valid && index_scan_batch_loaded(scan, scanPos->batch)); + + /* + * prefetchPos can "fall behind" scanPos at the item granularity: the + * prefetch callback only runs on demand, so scanPos can overtake + * prefetchPos whenever the scan consumes items without the callback being + * called (e.g., runs of adjacent matching items whose TIDs all point to + * the same table block). We handle that case using exactly the same + * steps as initialization. + * + * prefetchPos can never fall behind scanPos at the batch granularity, + * since tableam_util_scanpos_nextbatch invalidates prefetchPos before + * releasing the batch that prefetchPos points to. There is therefore no + * danger of prefetchPos.batch falling so far behind scanPos.batch that it + * wraps around (and appears to be ahead of scanPos instead of behind it). + */ + if (!prefetchPos->valid || + index_scan_pos_cmp(scanPos, prefetchPos, direction) > 0) + { + *prefetchPos = *scanPos; + return true; + } + + /* Picking up prefetching from where the last callback call left off */ + Assert(index_scan_pos_cmp(scanPos, prefetchPos, direction) <= 0); + return false; +} + +/* + * Result of a tableam_util_prefetchpos_advance call + */ +typedef enum BatchPosAdvanceResult +{ + BATCH_POS_ADVANCED, /* advanced to next item in current batch */ + BATCH_POS_BATCH_ADVANCED, /* advanced to first item of new batch */ + BATCH_POS_DONE, /* no further matching items in direction */ + BATCH_POS_RING_FULL, /* couldn't advance; ring buffer full */ +} BatchPosAdvanceResult; + +/* + * Advance the scan's prefetchPos to the next item that the table AM's + * prefetch callback should consider reading ahead, moving in the given scan + * direction. + * + * On entry, *prefetchBatch must be the batch that prefetchPos points to. + * Advances prefetchPos to the next item within *prefetchBatch when possible + * (returns BATCH_POS_ADVANCED). Otherwise tries to advance to the scan's + * next batch, setting *prefetchBatch to the new batch and positioning + * prefetchPos at its first item in the scan direction (returns + * BATCH_POS_BATCH_ADVANCED). Callers must use the returned result (never + * compare *prefetchBatch against its earlier value) to detect this case; + * batch recycling can reuse the memory of a recently released batch. + * + * Returns BATCH_POS_DONE when there are no further matching items in the + * given scan direction (*prefetchBatch is set to NULL). + * + * Returns BATCH_POS_RING_FULL when the next batch couldn't be loaded because + * all available ring buffer batch slots are currently in use (prefetchPos + * and *prefetchBatch are left unchanged). Caller responds by momentarily + * pausing its read-ahead mechanism; it can be resumed once + * tableam_util_scanpos_nextbatch reports that the scan freed up a slot + * (which'll happen only after scanPos has consumed all remaining items from + * the scan's current scanBatch). + */ +static inline BatchPosAdvanceResult +tableam_util_prefetchpos_advance(IndexScanDesc scan, ScanDirection direction, + IndexScanBatch *prefetchBatch, + BatchRingItemPos *prefetchPos) +{ + if (!index_scan_pos_advance(direction, *prefetchBatch, prefetchPos)) + { + /* + * Ran out of items from prefetchBatch. Try to advance to the scan's + * next batch. + */ + if (unlikely(index_scan_batch_full(scan))) + { + /* + * Can't advance prefetchBatch because all available ring buffer + * batch slots are currently in use. Undo the most recent advance + * before returning, leaving prefetchPos in a state that's + * consistent with the work actually performed (various positional + * state assertions expect it). + */ + if (ScanDirectionIsForward(direction)) + { + Assert(prefetchPos->item == (*prefetchBatch)->lastItem + 1); + prefetchPos->item--; + } + else /* ScanDirectionIsBackward */ + { + Assert(prefetchPos->item == (*prefetchBatch)->firstItem - 1); + prefetchPos->item++; + } + + return BATCH_POS_RING_FULL; + } + + /* We have a free ring buffer slot to fit another batch */ + *prefetchBatch = tableam_util_fetch_next_batch(scan, direction, + *prefetchBatch, + prefetchPos); + if (*prefetchBatch == NULL) + { + /* + * Deliberately leave prefetchPos in "just-before-start" or + * "just-after-end" position + */ + return BATCH_POS_DONE; + } + + /* + * Have a new prefetchBatch. + * + * tableam_util_fetch_next_batch already appended the new batch to the + * ring buffer for us, but we must advance prefetchPos ourselves. + * Position prefetchPos to the start of the new batch. + */ + index_scan_pos_startbatch(direction, *prefetchBatch, prefetchPos); + + return BATCH_POS_BATCH_ADVANCED; + } + + return BATCH_POS_ADVANCED; } /* diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 8181f8862..513187448 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -186,6 +186,10 @@ typedef struct IndexScanBatchData * This allows table AMs to avoid redundant amgetbatch calls with the same * priorbatch -- the index AM might need to read additional index pages to * determine there are no more matching items beyond caller's priorbatch. + * In particular, during prefetching the read stream callback discovers + * the end-of-scan via prefetchBatch. tableam_util_fetch_next_batch() + * checks these flags so that the scan side doesn't repeat the same + * amgetbatch call when it later reaches that batch as scanBatch. */ bool knownEndBackward; bool knownEndForward; @@ -236,11 +240,14 @@ typedef struct IndexScanBatchData *IndexScanBatch; * current read position by _multiple_ batches/index pages. The further out * the table AM reads ahead like this, the further it can see into the future. * That way the table AM is able to reorder work as aggressively as desired. + * Index scans sometimes need to readahead by several dozen batches in order + * to maintain an optimal I/O prefetch distance (for reading table blocks). */ typedef struct BatchRingBuffer { /* current positions in IndexScanDescData.batchbuf[] for scan */ BatchRingItemPos scanPos; /* scan's read position */ + BatchRingItemPos prefetchPos; /* prefetching position */ BatchRingItemPos markPos; /* mark/restore position */ /* markPos's batch (not in ring buffer when markBatch != scanBatch) */ diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index f2fd5d315..419300a6b 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -52,6 +52,7 @@ extern PGDLLIMPORT int max_parallel_workers_per_gather; extern PGDLLIMPORT bool enable_seqscan; extern PGDLLIMPORT bool enable_indexscan; extern PGDLLIMPORT bool enable_indexonlyscan; +extern PGDLLIMPORT bool enable_indexscan_prefetch; extern PGDLLIMPORT bool enable_bitmapscan; extern PGDLLIMPORT bool enable_tidscan; extern PGDLLIMPORT bool enable_sort; diff --git a/src/backend/access/heap/heapam_indexscan.c b/src/backend/access/heap/heapam_indexscan.c index 234a0f34f..f6c7e7428 100644 --- a/src/backend/access/heap/heapam_indexscan.c +++ b/src/backend/access/heap/heapam_indexscan.c @@ -19,6 +19,7 @@ #include "access/indexbatch.h" #include "access/relscan.h" #include "access/visibilitymap.h" +#include "optimizer/cost.h" #include "storage/predicate.h" #include "utils/builtins.h" #include "utils/memutils.h" @@ -91,6 +92,15 @@ static void heapam_index_batch_pos_visibility(IndexScanDesc scan, IndexScanBatch batch, HeapBatchData *hbatch, BatchRingItemPos *pos); +static pg_noinline void heapam_index_dirchange_reset(IndexScanDesc scan, + IndexScanHeapData *hscan, + ScanDirection direction); +static pg_attribute_always_inline void heapam_index_consider_prefetching(IndexScanDesc scan, + IndexScanHeapData *hscan, + bool hadExistingScanBatch); +static BlockNumber heapam_index_prefetch_next_block(ReadStream *stream, + void *callback_private_data, + void *per_buffer_data); /* * Simple, single-shot TID lookup for constraint enforcement code (unique @@ -158,6 +168,10 @@ heapam_index_scan_begin(IndexScanDesc scan, uint32 flags) /* xs_lastinblock optimization state */ Assert(!hscan->xs_lastinblock); + /* Read stream state (other fields initialized by callback) */ + Assert(hscan->xs_read_stream_dir == NoMovementScanDirection); + Assert(hscan->xs_read_stream == NULL); + /* Resolve which xs_getnext_slot implementation to use for this scan */ if (scan->indexRelation->rd_indam->amgetbatch != NULL) { @@ -235,6 +249,15 @@ heapam_index_scan_rescan(IndexScanDesc scan) /* Rescans should avoid an excessive number of VM lookups */ hscan->xs_vm_items = 1; + /* Defensively do an unconditional read stream direction reset */ + hscan->xs_read_stream_dir = NoMovementScanDirection; + + if (hscan->xs_read_stream) + { + hscan->xs_paused = false; + read_stream_reset(hscan->xs_read_stream); + } + /* Reset batch ring buffer state */ if (scan->usebatchring) tableam_util_batchscan_reset(scan, false); @@ -259,6 +282,9 @@ heapam_index_scan_end(IndexScanDesc scan) if (BufferIsValid(hscan->xs_vmbuffer)) ReleaseBuffer(hscan->xs_vmbuffer); + if (hscan->xs_read_stream) + read_stream_end(hscan->xs_read_stream); + /* Free the index-only scan name-column context, if any */ if (hscan->xs_itup_cxt) MemoryContextDelete(hscan->xs_itup_cxt); @@ -288,9 +314,17 @@ heapam_index_scan_markpos(IndexScanDesc scan) void heapam_index_scan_restrpos(IndexScanDesc scan) { + IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque; + Assert(scan->usebatchring); Assert(scan->indexRelation->rd_indam->amcanmarkpos); + if (hscan->xs_read_stream) + { + hscan->xs_paused = false; + read_stream_reset(hscan->xs_read_stream); + } + tableam_util_batchscan_restore_pos(scan); } @@ -625,6 +659,15 @@ heapam_index_getnext_slot(IndexScanDesc scan, ScanDirection direction, uint8 n_visited_pages = 0; ItemPointer tid = NULL; + /* + * Changing the scan direction mid-scan requires an MVCC snapshot: with + * any other snapshot type, more than one member of a HOT chain can be + * visible, and resuming a partially-returned chain only works in the + * forward direction. All non-MVCC callers scan in one fixed direction. + */ + Assert(scan->MVCCScan || !amgetbatch || + hscan->xs_read_stream_dir == NoMovementScanDirection || + hscan->xs_read_stream_dir == direction); Assert(TransactionIdIsValid(RecentXmin)); Assert(index_only || scan->xs_visited_pages_limit == 0); @@ -804,7 +847,14 @@ heapam_index_heap_fetch(IndexScanDesc scan, IndexScanHeapData *hscan, if (BufferIsValid(hscan->xs_cbuf)) ReleaseBuffer(hscan->xs_cbuf); - hscan->xs_cbuf = ReadBuffer(rel, hscan->xs_blk); + /* + * When using a read stream, the stream will already know which block + * number comes next (though an assertion will verify a match below) + */ + if (hscan->xs_read_stream) + hscan->xs_cbuf = read_stream_next_buffer(hscan->xs_read_stream, NULL); + else + hscan->xs_cbuf = ReadBuffer(rel, hscan->xs_blk); /* * Prune page when it is pinned for the first time @@ -926,9 +976,16 @@ heapam_index_getnext_scanbatch_pos(IndexScanDesc scan, IndexScanHeapData *hscan, { BatchRingItemPos *scanPos = &scan->batchringbuf.scanPos; IndexScanBatch scanBatch; + bool hadExistingScanBatch; Assert(all_visible == NULL || scan->xs_want_itup); + /* Handle resetting the read stream when scan direction changes */ + if (hscan->xs_read_stream_dir == NoMovementScanDirection) + hscan->xs_read_stream_dir = direction; /* first call */ + else if (unlikely(hscan->xs_read_stream_dir != direction)) + heapam_index_dirchange_reset(scan, hscan, direction); + /* * Attempt to increment the position of any existing loaded scanBatch * (always fails on first call here for the scan) @@ -944,6 +1001,12 @@ heapam_index_getnext_scanbatch_pos(IndexScanDesc scan, IndexScanHeapData *hscan, all_visible); } + /* + * Determine if tableam_util_scanpos_advance just failed because there was + * no existing loaded scanBatch for it to consider + */ + hadExistingScanBatch = (scanBatch != NULL); + /* Try to advance scanBatch to the next batch (or get the first batch) */ scanBatch = tableam_util_fetch_next_batch(scan, direction, scanBatch, scanPos); @@ -954,6 +1017,15 @@ heapam_index_getnext_scanbatch_pos(IndexScanDesc scan, IndexScanHeapData *hscan, return NULL; } + /* + * We have a new scanBatch, but scanPos hasn't been advanced to it just + * yet. + * + * If it wasn't the scan's first returned batch, consider if we should + * begin prefetching now (unless prefetching is already underway). + */ + heapam_index_consider_prefetching(scan, hscan, hadExistingScanBatch); + /* * Update batchringbuf and scanPos such that the scan can continue with * our new scanBatch. @@ -962,7 +1034,27 @@ heapam_index_getnext_scanbatch_pos(IndexScanDesc scan, IndexScanHeapData *hscan, * also remove the old head batch/scanBatch from the batch ring buffer, * and release the underlying batch storage. */ - tableam_util_scanpos_nextbatch(scan, direction, scanBatch); + if (tableam_util_scanpos_nextbatch(scan, direction, scanBatch)) + { + /* A previously occupied ring buffer slot was freed */ + if (unlikely(hscan->xs_paused)) + { + /* + * heapam_index_prefetch_next_block paused the scan's read stream + * due to our running out of free batch slots. Now that we've + * freed up one such slot, we can resume the read stream (since + * there's now space for heapam_index_prefetch_next_block to store + * one more batch). + * + * Note: It's just about possible that prefetchPos was just (or is + * just about to be) invalidated with low INDEX_SCAN_MAX_BATCHES. + */ + read_stream_resume(hscan->xs_read_stream); + hscan->xs_paused = false; + } + } + + Assert(!hscan->xs_paused); /* * Set scanPos to first item for newly loaded scanBatch; return the new @@ -1090,6 +1182,13 @@ heapam_index_return_scanpos_tid(IndexScanDesc scan, IndexScanHeapData *hscan, * (important for inner index scans of anti-joins and semi-joins), and the * need to unguard batches promptly. * + * In no event will the scan be allowed to guard more than one batch at a + * time. The primary reason for this restriction is to avoid unintended + * interactions with the read stream, which has its own strategy for keeping + * the number of pins held by the backend under control. (Unguarding via + * the amunguardbatch callback often means releasing a buffer pin on an + * index page, which counts against the same shared pin limit.) + * * Once we've resolved visibility for all items in a batch, we can safely * unguard it by calling amunguardbatch. This is safe with respect to * concurrent VACUUM because the batch's guard (typically a buffer pin on the @@ -1238,3 +1337,320 @@ heapam_index_batch_pos_visibility(IndexScanDesc scan, ScanDirection direction, else hscan->xs_vm_items = scan->maxitemsbatch; } + +/* + * Handle a change in index scan direction (at the tuple granularity). + * + * Resets the read stream, since we can't rely on scanPos continuing to agree + * with the blocks that read stream already consumed using prefetchPos. + * + * Note: iff the scan _continues_ in this new direction, and actually steps + * off scanBatch to an earlier index page, tableam_util_fetch_next_batch will + * deal with it. But that might never happen; the scan might yet change + * direction again (or just end before returning more items). + */ +static pg_noinline void +heapam_index_dirchange_reset(IndexScanDesc scan, IndexScanHeapData *hscan, + ScanDirection direction) +{ + /* Reset read stream state */ + scan->batchringbuf.prefetchPos.valid = false; + hscan->xs_paused = false; + hscan->xs_read_stream_dir = direction; + + /* Reset read stream itself */ + if (hscan->xs_read_stream) + read_stream_reset(hscan->xs_read_stream); +} + +/* + * Decide whether to start a read stream for heap block prefetching during an + * index scan + */ +static pg_attribute_always_inline void +heapam_index_consider_prefetching(IndexScanDesc scan, + IndexScanHeapData *hscan, + bool hadExistingScanBatch) +{ + /* + * We delay initializing the stream until reading from the scan's second + * batch. This heuristic avoids wasting cycles on starting a read stream + * for very selective index scans. + */ + if (!hadExistingScanBatch) + return; + + /* Return early when prefetching is already in use */ + if (hscan->xs_read_stream) + return; + + Assert(!scan->batchringbuf.prefetchPos.valid); + + /* + * We also delay creating a read stream during index-only scans that + * haven't done any heap fetches yet. We don't want to waste any cycles + * on allocating a read stream until we have a demonstrated need to + * perform heap fetches. + */ + if (hscan->xs_blk == InvalidBlockNumber) + return; + + /* + * We avoid prefetching during scans where we're unable to unguard (unpin) + * each batch's buffers right away (non-MVCC snapshot scans). We are not + * prepared to sensibly limit the total number of buffer pins held. The + * read stream handles all pin resource management for us, and knows + * nothing about pins held on index pages/within batches. + */ + if (!scan->MVCCScan) + return; + + /* GUC gates whether or not we use prefetching */ + if (!enable_indexscan_prefetch) + return; + + hscan->xs_read_stream = + read_stream_begin_relation(READ_STREAM_DEFAULT, NULL, + scan->heapRelation, MAIN_FORKNUM, + heapam_index_prefetch_next_block, scan, 0); +} + +/* + * Return the next block to the read stream when performing index prefetching. + * + * The initial batch is always loaded by heapam_index_getnext_scanbatch_pos. + * We don't get called until the first read_stream_next_buffer call, when a + * heap block is requested from the scan's stream for the first time. + * + * The position of the read stream is stored in prefetchPos, which typically + * stays ahead of scanPos (the scan's read position). When we return, we + * always leave scanPos <= prefetchPos. + */ +static BlockNumber +heapam_index_prefetch_next_block(ReadStream *stream, + void *callback_private_data, + void *per_buffer_data) +{ + IndexScanDesc scan = (IndexScanDesc) callback_private_data; + IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque; + BatchRingBuffer *batchringbuf = &scan->batchringbuf; + BatchRingItemPos *scanPos PG_USED_FOR_ASSERTS_ONLY = &batchringbuf->scanPos; + BatchRingItemPos *prefetchPos = &batchringbuf->prefetchPos; + ScanDirection direction = hscan->xs_read_stream_dir; + IndexScanBatch prefetchBatch; + HeapBatchData *hbatch = NULL; + + /* The scan direction must already be established */ + Assert(scan->MVCCScan); + Assert(!hscan->xs_paused); + Assert(direction != NoMovementScanDirection); + + /* + * Handle initialization of prefetchPos: set it from the scan's current + * scanPos when it isn't already (validly) ahead of scanPos. This is + * required during the first call here for the scan (and in certain edge + * cases). See tableam_util_prefetchpos_catchup for full details. + */ + if (tableam_util_prefetchpos_catchup(scan, direction)) + { + BatchMatchingItem *item; + + /* prefetchPos has been initialized from scanPos for us */ + prefetchBatch = index_scan_batch(scan, prefetchPos->batch); + + /* + * We must avoid keeping any batch guarded for more than an instant, + * to avoid undesirable interactions with the scan's read stream. See + * comment and assertion at the top of the loop below. + */ + if (scan->xs_want_itup) + { + /* + * Index-only scan batches aren't unguarded immediately. Deal + * with that. + */ + hbatch = index_scan_batch_table_area(scan, prefetchBatch); + + /* + * The requested item can't be all-visible according to its + * batch's cached visibility information; if it were, we'd never + * have been called in the first place + */ + Assert(HEAP_BATCH_VIS_CACHED(hbatch, prefetchPos->item) && + !hbatch->batchvis[prefetchPos->item]); + + /* + * Load any visibility info not already set through scanBatch, so + * that scanBatch/prefetchBatch is unguarded right away. + * + * Note: We rely on heapam_index_batch_pos_visibility not + * overwriting the existing batchvis[] for scanPos/prefetchPos + * (plus any other items that have already been cached). + */ + hscan->xs_vm_items = scan->maxitemsbatch; /* must unguard */ + if (prefetchBatch->isGuarded) + heapam_index_batch_pos_visibility(scan, direction, + prefetchBatch, hbatch, + prefetchPos); + + /* + * Later calls to heapam_index_batch_pos_visibility will always + * unguard the batch right away, which we rely on below + */ + } + + Assert(!prefetchBatch->isGuarded); + + item = &prefetchBatch->items[prefetchPos->item]; + hscan->xs_prefetch_block = ItemPointerGetBlockNumber(&item->tableTid); + + /* + * Special case: when we return, prefetchPos won't be ahead of scanPos + * (it'll just be equal to scanPos). We're merely fetching through a + * read stream; true prefetching hasn't really started yet. + */ + Assert(index_scan_pos_cmp(scanPos, prefetchPos, direction) == 0); + + return hscan->xs_prefetch_block; + } + + /* + * We're picking up prefetching from where the last call here left off + */ + Assert(index_scan_pos_cmp(scanPos, prefetchPos, direction) <= 0); + prefetchBatch = index_scan_batch(scan, prefetchPos->batch); + if (scan->xs_want_itup) + hbatch = index_scan_batch_table_area(scan, prefetchBatch); + + /* + * Assert in passing that xs_prefetch_block matches the last item we + * returned. + * + * Note: we don't actually need a xs_prefetch_block field at all; we could + * just take the last block we returned from prefetchPos directly instead. + * But maintaining xs_prefetch_block explicitly is slightly more robust. + * It gives us a way to make sure that the last call here left prefetchPos + * in a consistent state (e.g., when the read stream had to be paused). + */ +#ifdef USE_ASSERT_CHECKING + { + BatchMatchingItem *lastitem = &prefetchBatch->items[prefetchPos->item]; + BlockNumber last_block = ItemPointerGetBlockNumber(&lastitem->tableTid); + + /* + * Note: when a previous call paused the read stream, prefetchPos + * might point to an item whose TID doesn't match last_block. This + * can only happen when the item was never returned due to it being + * all-visible. + */ + Assert(last_block == hscan->xs_prefetch_block || + (hbatch && HEAP_BATCH_VIS_CACHED(hbatch, prefetchPos->item) && + hbatch->batchvis[prefetchPos->item])); + } +#endif + + for (;;) + { + BatchMatchingItem *item; + BlockNumber prefetch_block; + + /* + * We never call amgetbatch without immediately unguarding the batch + * once prefetching begins. That way index AMs won't hold onto any + * "extra" index page pins needed as TID recycling interlock guards. + * + * This is defensive. The read stream tries to be careful about not + * pinning too many buffers, and that's harder to do reliably if there + * are variable numbers of pins taken without such care. + */ + Assert(!prefetchBatch->isGuarded); + + /* Increment prefetchPos to determine the next item to prefetch */ + switch (tableam_util_prefetchpos_advance(scan, direction, + &prefetchBatch, prefetchPos)) + { + case BATCH_POS_ADVANCED: + /* Advanced to next item in current/previous prefetchBatch */ + break; + case BATCH_POS_BATCH_ADVANCED: + /* Advanced to first item in new prefetchBatch */ + if (hbatch) + { + /* + * Extra heapam-specific step: bulk-load visibility info + * up front to unguard batch immediately + */ + Assert(scan->xs_want_itup); + + hbatch = index_scan_batch_table_area(scan, prefetchBatch); + + Assert(hscan->xs_vm_items == scan->maxitemsbatch); + if (prefetchBatch->isGuarded) + heapam_index_batch_pos_visibility(scan, direction, + prefetchBatch, + hbatch, prefetchPos); + } + break; + case BATCH_POS_DONE: + /* No more batches in this scan direction */ + return InvalidBlockNumber; + case BATCH_POS_RING_FULL: + + /* + * Edge case: Ran out of items from prefetchBatch, but can't + * advance to the scan's next batch right now (all available + * batchringbuf batch slots are currently in use). + * + * Deal with this by momentarily pausing the read stream. + * heapam_index_getnext_scanbatch_pos will resume the read + * stream later, though only after scanPos has consumed all + * remaining items from scanBatch (at which point the current + * head batch will be freed, making a slot available for + * reuse). + */ + hscan->xs_paused = true; + return read_stream_pause(stream); + } + + /* + * prefetchPos now points to the next item whose TID's heap block + * number might need to be prefetched. + * + * scanPos must be < prefetchPos when we return from this loop path. + */ + Assert(index_scan_pos_cmp(scanPos, prefetchPos, direction) < 0); + + if (hbatch) + { + Assert(scan->xs_want_itup); + Assert(HEAP_BATCH_VIS_CACHED(hbatch, prefetchPos->item)); + + if (hbatch->batchvis[prefetchPos->item]) + { + /* item is known to be all-visible -- don't prefetch */ + continue; + } + } + + item = &prefetchBatch->items[prefetchPos->item]; + prefetch_block = ItemPointerGetBlockNumber(&item->tableTid); + + if (prefetch_block == hscan->xs_prefetch_block) + { + /* + * prefetch_block matches the last prefetchPos item's TID's heap + * block number; we must not return the same prefetch_block twice + * (twice in succession) + */ + continue; + } + + /* We have a new heap block number to return to read stream */ + hscan->xs_prefetch_block = prefetch_block; + return prefetch_block; + } + + pg_unreachable(); + + return InvalidBlockNumber; +} diff --git a/src/backend/access/index/indexbatch.c b/src/backend/access/index/indexbatch.c index 51d147401..841cda6ff 100644 --- a/src/backend/access/index/indexbatch.c +++ b/src/backend/access/index/indexbatch.c @@ -5,15 +5,22 @@ * * This module provides the core infrastructure for batch-based index scans, * which allow index AMs to return multiple matching TIDs per page in a single - * call. The batch ring buffer is owned by the table AM. + * call. The batch ring buffer is owned by the table AM, typically maintained + * alongside a read stream used for prefetching table blocks. * - * The ring buffer loads batches in index key space/index scan order. + * The ring buffer loads batches in index key space/index scan order. This + * allows the table AM to maintain an adequate prefetch distance: its read + * stream callback is thereby able to request table blocks referenced by index + * pages that are well ahead of the current scan position's index page. * * Most functions here are table AM utilities (tableam_util_*), called by * table AMs during amgetbatch index scans. These manage the batch ring * buffer's lifecycle and positional state, and help with certain aspects of * resource management. The table AM uses scanPos (and its scanBatch batch) - * to return items from batches returned by amgetbatch. + * to return items from batches returned by amgetbatch. Table AMs that + * support index I/O prefetching use prefetchPos (and its prefetchBatch batch) + * by implementing a read stream callback that consumes items well ahead of + * scanPos. * * There are also some index AM utilities (indexam_util_*), called by index * AMs that implement the amgetbatch interface, to help manage resources like @@ -104,6 +111,7 @@ tableam_util_batchscan_reset(IndexScanDesc scan, bool endscan) bool markBatchFreed = false; batchringbuf->scanPos.valid = false; + batchringbuf->prefetchPos.valid = false; batchringbuf->markPos.valid = false; for (uint8 i = batchringbuf->headBatch; i != batchringbuf->nextBatch; i++) @@ -215,7 +223,12 @@ tableam_util_batchscan_mark_pos(IndexScanDesc scan) * the current scanBatch when needed. * * We just discard all batches (other than markBatch/restored scanBatch), - * except when markBatch is already the scan's current scanBatch. + * except when markBatch is already the scan's current scanBatch. We always + * invalidate prefetchPos. The table AM's prefetching state (e.g., its read + * stream) is reset by the caller (which calls this function as it resets that + * state). This approach keeps things simple for table AMs: most code that + * deals with batches is thereby able to assume that the common case where + * scan direction never changes is the only case. * * Note: This relies on the assumption that we already have a valid scanPos. * Table AMs should only call tableam_util_batchscan_reset from within their @@ -242,6 +255,14 @@ tableam_util_batchscan_restore_pos(IndexScanDesc scan) Assert(markPos->item >= markBatch->firstItem && markPos->item <= markBatch->lastItem); + /* + * Restoring a mark always requires stopping prefetching. This is similar + * to the handling table AMs implement to deal with a tuple-level change + * in the scan's direction. The read stream must have already been reset + * by the table AM caller. + */ + batchringbuf->prefetchPos.valid = false; + if (scanBatch == markBatch) { /* markBatch is already scanBatch; needn't change batchringbuf */ @@ -312,6 +333,13 @@ tableam_util_batchscan_restore_pos(IndexScanDesc scan) * to determine which batch comes next in the new scan direction. This * approach isn't particularly efficient, but it works well enough for what * ought to be a relatively rare occurrence. + * + * Caller must have reset the scan's read stream before calling here. That + * needs to happen as soon as the scan requests a tuple in whatever scan + * direction is opposite-to-current. We only deal with the case where the + * scan backs up by enough items to cross a batch boundary (when the scan + * resumes scanning in its original direction/ends before crossing a boundary, + * there isn't any need to call here). */ void tableam_util_scanbatch_dirchange(IndexScanDesc scan) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 1c575e56f..6fcb815f7 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -146,6 +146,7 @@ int max_parallel_workers_per_gather = 2; bool enable_seqscan = true; bool enable_indexscan = true; bool enable_indexonlyscan = true; +bool enable_indexscan_prefetch = true; bool enable_bitmapscan = true; bool enable_tidscan = true; bool enable_sort = true; diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index afaa058b0..ace56f7a8 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -941,6 +941,13 @@ boot_val => 'true', }, +{ name => 'enable_indexscan_prefetch', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD', + short_desc => 'Enables prefetching for index scans and index-only scans.', + flags => 'GUC_EXPLAIN', + variable => 'enable_indexscan_prefetch', + boot_val => 'true', +}, + { name => 'enable_material', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD', short_desc => 'Enables the planner\'s use of materialization.', flags => 'GUC_EXPLAIN', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index ac38cddaa..8705dd5f3 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -431,6 +431,7 @@ #enable_incremental_sort = on #enable_indexscan = on #enable_indexonlyscan = on +#enable_indexscan_prefetch = on #enable_material = on #enable_memoize = on #enable_mergejoin = on diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index fa566c9e5..8e9d8f2ac 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -5938,6 +5938,22 @@ ANY num_sync ( + enable_indexscan_prefetch (boolean) + + enable_indexscan_prefetch configuration parameter + + + + + Enables or disables prefetching for index scan and index-only scan + plan types. Prefetching can improve performance by reading table AM + pages ahead of when they are needed during index scans. The default + is on. + + + + enable_material (boolean) diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index f8f670347..eedad6bc4 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -854,9 +854,12 @@ amgetbatch (IndexScanDesc scan, The amgetbatch interface is an alternative to amgettuple that returns matching index entries in batches - rather than one at a time. By returning all matching index entries from a - single index page together, the table AM gains visibility into which table - blocks will be needed in the near future. + rather than one at a time. This enables the table access method to + optimize table block access patterns and perform I/O prefetching. + By returning all matching index entries from a single index page together, + the table AM can readahead through the index and identify which table + blocks will be needed, allowing prefetching of table AM pages during + ordered index scans. @@ -990,7 +993,9 @@ amunguardbatch (IndexScanDesc scan, to free the pins at an opportune point (at a minimum whenever amendscan is called, and typically when amrescan is called). It must also keep the number of - retained pins fixed and small. + retained pins fixed and small, to avoid exhausting the backend's buffer + pin limit (which is shared with the table AM's read stream for index scan + prefetching). @@ -1532,6 +1537,66 @@ amtranslatecmptype (CompareType cmptype, Oid opfamily, Oid opcintype); or vice versa, if its internal implementation is unsuited to one API or the other. + + Table AM Considerations for Batch Scanning + + + This section is primarily relevant to table access + method authors. + + + + When an index scan uses the amgetbatch interface, the + table AM has sole control over the IndexScanDesc's + batchringbuf, including creating, resetting, + and ending the batch ring buffer within the appropriate table AM + callbacks, and managing positional state and TID recycling interlocking + (that is, determining when to unguard each batch, which will typically + release an index page buffer pin associated with the batch). Index access + methods should not access or manipulate these fields. + src/include/access/indexbatch.h provides the + tableam_util_* utility functions that table AMs use + to manage the ring buffer and its positional state. See the + src/backend/access/heap/heapam_indexscan.c + implementation for a reference example. + + + + The scanPos field within + batchringbuf tracks which batch and item within + that batch will be returned next to the executor. The table AM must advance + scanPos as tuples are returned by + table_index_getnext_slot (using + tableam_util_scanpos_advance, plus + tableam_util_scanpos_nextbatch when crossing batch + boundaries), and must also modify this field when restoring a saved mark. + + + + The prefetchPos field tracks the position used + for I/O prefetching. It is managed within a read stream callback (using + tableam_util_prefetchpos_catchup and + tableam_util_prefetchpos_advance), allowing + the table AM to prefetch table blocks pointed to by items that are well + ahead of the current scan position. Initially + prefetchPos starts at + scanPos, but as the read stream ramps up it can + get far ahead — spanning multiple index pages if necessary to + maintain an optimal I/O prefetch distance for table block reads. A major + goal of the amgetbatch interface is to allow the + table AM to prefetch without being limited to items from the current + scanPos batch's index leaf page. + + + + For details on the TID recycling interlock during batch scans, including + the batchImmediateUnguard policy and the + amunguardbatch callback, see + . + + + + @@ -1634,7 +1699,27 @@ amtranslatecmptype (CompareType cmptype, Oid opfamily, Oid opcintype); immediately after scanning the corresponding index entry. This is expensive for a number of reasons. The amgetbatch interface, by contrast, was designed to - allow scans to be asynchronous. + allow scans to be asynchronous: by collecting batches of + TIDs from multiple index pages, the table AM can prefetch the corresponding + table blocks well ahead of the current scan position (using asynchronous + I/O when available), allowing a more efficient heap access pattern. Not + all scans end up being asynchronous in practice, but the interface is + designed to allow it. Per the above analysis, we must use the synchronous + approach for non-MVCC-compliant snapshots (even when using the + amgetbatch interface), but an asynchronous scan is + workable for plain index scans that use an MVCC snapshot. + + + + Because the table AM reads multiple index leaf pages ahead via + amgetbatch to facilitate this prefetching, a non-MVCC + scan would have to hold the TID recycling interlock across the entire + read-ahead window, since it has no heap-visibility backstop to fall back on. + That is impractical, so I/O prefetching with + amgetbatch is only possible when an MVCC-compliant + snapshot is in use. An MVCC scan either drops the interlock immediately or, + for index-only scans, holds only a bounded number of pins (one per in-flight + batch). diff --git a/doc/src/sgml/tableam.sgml b/doc/src/sgml/tableam.sgml index 9ccf5b739..4542e00b4 100644 --- a/doc/src/sgml/tableam.sgml +++ b/doc/src/sgml/tableam.sgml @@ -129,6 +129,14 @@ my_tableam_handler(PG_FUNCTION_ARGS) optional), the block number needs to provide locality. + + Table access methods must support ordered index scans using the + amgetbatch interface. See also + for details on interfacing with + amgetbatch index access methods, and managing the + scan's position. + + For crash safety, an AM can use postgres' WAL, or a custom implementation. diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 132b56a58..32bc3dd3e 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -166,6 +166,7 @@ select name, setting from pg_settings where name like 'enable%'; enable_incremental_sort | on enable_indexonlyscan | on enable_indexscan | on + enable_indexscan_prefetch | on enable_material | on enable_memoize | on enable_mergejoin | on @@ -180,7 +181,7 @@ select name, setting from pg_settings where name like 'enable%'; enable_seqscan | on enable_sort | on enable_tidscan | on -(25 rows) +(26 rows) -- There are always wait event descriptions for various types. InjectionPoint -- may be present or absent, depending on history since last postmaster start. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 7ff1daad5..db95d4566 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -266,6 +266,7 @@ BaseBackupTargetHandle BaseBackupTargetType BatchMVCCState BatchMatchingItem +BatchPosAdvanceResult BatchRingBuffer BatchRingItemPos BeginDirectModify_function -- 2.53.0