From 010df6f6c3f9ab07061ee13e2126b2aa4ca128bd Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Mon, 8 Apr 2024 02:02:18 +0300 Subject: [PATCH v1] Turn back the block-level API for relation analyze And keep the new API as well. --- src/backend/access/heap/heapam_handler.c | 29 +++----- src/backend/access/table/tableamapi.c | 3 + src/backend/commands/analyze.c | 35 +++++---- src/include/access/heapam.h | 9 --- src/include/access/tableam.h | 91 +++++++++++++++++++++++- src/include/commands/vacuum.h | 5 +- 6 files changed, 125 insertions(+), 47 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 58de2c82a70..088da910a11 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -51,6 +51,7 @@ static TM_Result heapam_tuple_lock(Relation relation, ItemPointer tid, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, uint8 flags, TM_FailureData *tmfd); + static void reform_and_rewrite_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap, Datum *values, bool *isnull, RewriteState rwstate); @@ -1054,15 +1055,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, pfree(isnull); } -/* - * Prepare to analyze block `blockno` of `scan`. The scan has been started - * with SO_TYPE_ANALYZE option. - * - * This routine holds a buffer pin and lock on the heap page. They are held - * until heapam_scan_analyze_next_tuple() returns false. That is until all the - * items of the heap page are analyzed. - */ -void +static bool heapam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, BufferAccessStrategy bstrategy) { @@ -1082,19 +1075,12 @@ heapam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, hscan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, blockno, RBM_NORMAL, bstrategy); LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); + + /* in heap all blocks can contain tuples, so always return true */ + return true; } -/* - * Iterate over tuples in the block selected with - * heapam_scan_analyze_next_block(). If a tuple that's suitable for sampling - * is found, true is returned and a tuple is stored in `slot`. When no more - * tuples for sampling, false is returned and the pin and lock acquired by - * heapam_scan_analyze_next_block() are released. - * - * *liverows and *deadrows are incremented according to the encountered - * tuples. - */ -bool +static bool heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, double *liverows, double *deadrows, TupleTableSlot *slot) @@ -2698,9 +2684,10 @@ static const TableAmRoutine heapam_methods = { .relation_copy_data = heapam_relation_copy_data, .relation_copy_for_cluster = heapam_relation_copy_for_cluster, .relation_vacuum = heap_vacuum_rel, + .scan_analyze_next_block = heapam_scan_analyze_next_block, + .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple, .index_build_range_scan = heapam_index_build_range_scan, .index_validate_scan = heapam_index_validate_scan, - .relation_analyze = heapam_analyze, .free_rd_amcache = NULL, .relation_size = table_block_relation_size, diff --git a/src/backend/access/table/tableamapi.c b/src/backend/access/table/tableamapi.c index 55b8caeadf2..4aefdc3c385 100644 --- a/src/backend/access/table/tableamapi.c +++ b/src/backend/access/table/tableamapi.c @@ -81,6 +81,9 @@ GetTableAmRoutine(Oid amhandler) Assert(routine->relation_copy_data != NULL); Assert(routine->relation_copy_for_cluster != NULL); Assert(routine->relation_vacuum != NULL); + Assert(routine->relation_analyze != NULL || + (routine->scan_analyze_next_block != NULL && + routine->scan_analyze_next_tuple != NULL)); Assert(routine->index_build_range_scan != NULL); Assert(routine->index_validate_scan != NULL); diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 2fb39f3ede1..6e3be0d491d 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -1103,15 +1103,15 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr) } /* - * acquire_sample_rows -- acquire a random sample of rows from the heap + * acquire_sample_rows -- acquire a random sample of rows from the table * * Selected rows are returned in the caller-allocated array rows[], which * must have at least targrows entries. * The actual number of rows selected is returned as the function result. - * We also estimate the total numbers of live and dead rows in the heap, + * We also estimate the total numbers of live and dead rows in the table, * and return them into *totalrows and *totaldeadrows, respectively. * - * The returned list of tuples is in order by physical position in the heap. + * The returned list of tuples is in order by physical position in the table. * (We will rely on this later to derive correlation estimates.) * * As of May 2004 we use a new two-stage method: Stage one selects up @@ -1133,7 +1133,7 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr) * look at a statistically unbiased set of blocks, we should get * unbiased estimates of the average numbers of live and dead rows per * block. The previous sampling method put too much credence in the row - * density near the start of the heap. + * density near the start of the table. */ static int acquire_sample_rows(Relation onerel, int elevel, @@ -1184,7 +1184,7 @@ acquire_sample_rows(Relation onerel, int elevel, /* Prepare for sampling rows */ reservoir_init_selection_state(&rstate, targrows); - scan = heap_beginscan(onerel, NULL, 0, NULL, NULL, SO_TYPE_ANALYZE); + scan = table_beginscan_analyze(onerel); slot = table_slot_create(onerel, NULL); #ifdef USE_PREFETCH @@ -1214,6 +1214,7 @@ acquire_sample_rows(Relation onerel, int elevel, /* Outer loop over blocks to sample */ while (BlockSampler_HasMore(&bs)) { + bool block_accepted; BlockNumber targblock = BlockSampler_Next(&bs); #ifdef USE_PREFETCH BlockNumber prefetch_targblock = InvalidBlockNumber; @@ -1229,19 +1230,29 @@ acquire_sample_rows(Relation onerel, int elevel, vacuum_delay_point(); - heapam_scan_analyze_next_block(scan, targblock, vac_strategy); + block_accepted = table_scan_analyze_next_block(scan, targblock, vac_strategy); #ifdef USE_PREFETCH /* * When pre-fetching, after we get a block, tell the kernel about the * next one we will want, if there's any left. + * + * We want to do this even if the table_scan_analyze_next_block() call + * above decides against analyzing the block it picked. */ if (prefetch_maximum && prefetch_targblock != InvalidBlockNumber) PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, prefetch_targblock); #endif - while (heapam_scan_analyze_next_tuple(scan, OldestXmin, &liverows, &deadrows, slot)) + /* + * Don't analyze if table_scan_analyze_next_block() indicated this + * block is unsuitable for analyzing. + */ + if (!block_accepted) + continue; + + while (table_scan_analyze_next_tuple(scan, OldestXmin, &liverows, &deadrows, slot)) { /* * The first targrows sample rows are simply copied into the @@ -1291,7 +1302,7 @@ acquire_sample_rows(Relation onerel, int elevel, } ExecDropSingleTupleTableSlot(slot); - heap_endscan(scan); + table_endscan(scan); /* * If we didn't find as many tuples as we wanted then we're done. No sort @@ -1363,12 +1374,12 @@ compare_rows(const void *a, const void *b, void *arg) } /* - * heapam_analyze -- implementation of relation_analyze() table access method - * callback for heap + * default_analyze -- implementation of relation_analyze() table access method + * callback for block table access methods */ void -heapam_analyze(Relation relation, AcquireSampleRowsFunc *func, - BlockNumber *totalpages, BufferAccessStrategy bstrategy) +default_analyze(Relation relation, AcquireSampleRowsFunc *func, + BlockNumber *totalpages, BufferAccessStrategy bstrategy) { *func = acquire_sample_rows; *totalpages = RelationGetNumberOfBlocks(relation); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 48936826bcc..be630620d0d 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -412,15 +412,6 @@ extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple); extern bool HeapTupleIsSurelyDead(HeapTuple htup, struct GlobalVisState *vistest); -/* in heap/heapam_handler.c*/ -extern void heapam_scan_analyze_next_block(TableScanDesc scan, - BlockNumber blockno, - BufferAccessStrategy bstrategy); -extern bool heapam_scan_analyze_next_tuple(TableScanDesc scan, - TransactionId OldestXmin, - double *liverows, double *deadrows, - TupleTableSlot *slot); - /* * To avoid leaking too much knowledge about reorderbuffer implementation * details this is implemented in reorderbuffer.c not heapam_visibility.c diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index be198fa3158..09aa2c119af 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -668,6 +668,41 @@ typedef struct TableAmRoutine struct VacuumParams *params, BufferAccessStrategy bstrategy); + /* + * Prepare to analyze block `blockno` of `scan`. The scan has been started + * with table_beginscan_analyze(). See also + * table_scan_analyze_next_block(). + * + * The callback may acquire resources like locks that are held until + * table_scan_analyze_next_tuple() returns false. It e.g. can make sense + * to hold a lock until all tuples on a block have been analyzed by + * scan_analyze_next_tuple. + * + * The callback can return false if the block is not suitable for + * sampling, e.g. because it's a metapage that could never contain tuples. + * + * XXX: This obviously is primarily suited for block-based AMs. It's not + * clear what a good interface for non block based AMs would be, so there + * isn't one yet. + */ + bool (*scan_analyze_next_block) (TableScanDesc scan, + BlockNumber blockno, + BufferAccessStrategy bstrategy); + + /* + * See table_scan_analyze_next_tuple(). + * + * Not every AM might have a meaningful concept of dead rows, in which + * case it's OK to not increment *deadrows - but note that that may + * influence autovacuum scheduling (see comment for relation_vacuum + * callback). + */ + bool (*scan_analyze_next_tuple) (TableScanDesc scan, + TransactionId OldestXmin, + double *liverows, + double *deadrows, + TupleTableSlot *slot); + /* see table_index_build_range_scan for reference about parameters */ double (*index_build_range_scan) (Relation table_rel, Relation index_rel, @@ -992,6 +1027,19 @@ table_beginscan_tid(Relation rel, Snapshot snapshot) return rel->rd_tableam->scan_begin(rel, snapshot, 0, NULL, NULL, flags); } +/* + * table_beginscan_analyze is an alternative entry point for setting up a + * TableScanDesc for an ANALYZE scan. As with bitmap scans, it's worth using + * the same data structure although the behavior is rather different. + */ +static inline TableScanDesc +table_beginscan_analyze(Relation rel) +{ + uint32 flags = SO_TYPE_ANALYZE; + + return rel->rd_tableam->scan_begin(rel, NULL, 0, NULL, NULL, flags); +} + /* * End relation scan. */ @@ -1725,6 +1773,42 @@ table_relation_vacuum(Relation rel, struct VacuumParams *params, rel->rd_tableam->relation_vacuum(rel, params, bstrategy); } +/* + * Prepare to analyze block `blockno` of `scan`. The scan needs to have been + * started with table_beginscan_analyze(). Note that this routine might + * acquire resources like locks that are held until + * table_scan_analyze_next_tuple() returns false. + * + * Returns false if block is unsuitable for sampling, true otherwise. + */ +static inline bool +table_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, + BufferAccessStrategy bstrategy) +{ + return scan->rs_rd->rd_tableam->scan_analyze_next_block(scan, blockno, + bstrategy); +} + +/* + * Iterate over tuples in the block selected with + * table_scan_analyze_next_block() (which needs to have returned true, and + * this routine may not have returned false for the same block before). If a + * tuple that's suitable for sampling is found, true is returned and a tuple + * is stored in `slot`. + * + * *liverows and *deadrows are incremented according to the encountered + * tuples. + */ +static inline bool +table_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, + double *liverows, double *deadrows, + TupleTableSlot *slot) +{ + return scan->rs_rd->rd_tableam->scan_analyze_next_tuple(scan, OldestXmin, + liverows, deadrows, + slot); +} + /* * table_index_build_scan - scan the table to find tuples to be indexed * @@ -1842,8 +1926,11 @@ static inline void table_relation_analyze(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages, BufferAccessStrategy bstrategy) { - relation->rd_tableam->relation_analyze(relation, func, - totalpages, bstrategy); + if (relation->rd_tableam->relation_analyze) + relation->rd_tableam->relation_analyze(relation, func, + totalpages, bstrategy); + else + default_analyze(relation, func, totalpages, bstrategy); } /* ---------------------------------------------------------------------------- diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 9514f8b2fd8..d00f3c19abf 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -393,9 +393,8 @@ extern void parallel_vacuum_main(dsm_segment *seg, shm_toc *toc); extern void analyze_rel(Oid relid, RangeVar *relation, VacuumParams *params, List *va_cols, bool in_outer_xact, BufferAccessStrategy bstrategy); -extern void heapam_analyze(Relation relation, AcquireSampleRowsFunc *func, - BlockNumber *totalpages, - BufferAccessStrategy bstrategy); +extern void default_analyze(Relation relation, AcquireSampleRowsFunc *func, + BlockNumber *totalpages, BufferAccessStrategy bstrategy); extern bool std_typanalyze(VacAttrStats *stats); -- 2.39.3 (Apple Git-145)