On Sun, Apr 07, 2024 at 03:00:00PM -0700, Andres Freund wrote: > Hi, > > On 2024-04-07 16:59:26 -0400, Melanie Plageman wrote: > > From 1dc2343661f3edb3b1bc4307afb0e956397eb76c Mon Sep 17 00:00:00 2001 > > From: Melanie Plageman <melanieplage...@gmail.com> > > Date: Sun, 7 Apr 2024 14:55:22 -0400 > > Subject: [PATCH v10 1/3] Make heapam_scan_analyze_next_[tuple|block] static. > > > > 27bc1772fc81 removed the table AM callbacks scan_analyze_next_block and > > scan_analzye_next_tuple -- leaving their heap AM implementations only > > called by acquire_sample_rows(). > > Ugh, I don't think 27bc1772fc81 makes much sense. But that's unrelated to this > thread. I did raise that separately > https://www.postgresql.org/message-id/20240407214001.jgpg5q3yv33ve6y3%40awork3.anarazel.de > > Unless I seriously missed something, I see no alternative to reverting that > commit.
Noted. I'll give up on this refactor then. Lots of churn for no gain. Attached v11 is just Bilal's v8 patch rebased to apply cleanly and with a few tweaks (I changed one of the loop conditions. All other changes are to comments and commit message). > > @@ -1206,11 +1357,13 @@ acquire_sample_rows(Relation onerel, int elevel, > > break; > > > > prefetch_block = BlockSampler_Next(&prefetch_bs); > > - PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, > > prefetch_block); > > + PrefetchBuffer(scan->rs_base.rs_rd, MAIN_FORKNUM, > > prefetch_block); > > } > > } > > #endif > > > > + scan->rs_cbuf = InvalidBuffer; > > + > > /* Outer loop over blocks to sample */ > > while (BlockSampler_HasMore(&bs)) > > { > > I don't think it's good to move a lot of code *and* change how it is > structured in the same commit. Makes it much harder to actually see changes / > makes git blame harder to use / etc. Yep. > > From 90d115c2401567be65bcf64393a6d3b39286779e Mon Sep 17 00:00:00 2001 > > From: Melanie Plageman <melanieplage...@gmail.com> > > Date: Sun, 7 Apr 2024 15:28:32 -0400 > > Subject: [PATCH v10 2/3] Use streaming read API in ANALYZE > > > > The ANALYZE command prefetches and reads sample blocks chosen by a > > BlockSampler algorithm. Instead of calling Prefetch|ReadBuffer() for > > each block, ANALYZE now uses the streaming API introduced in b5a9b18cd0. > > > > Author: Nazir Bilal Yavuz > > Reviewed-by: Melanie Plageman > > Discussion: > > https://postgr.es/m/flat/CAN55FZ0UhXqk9v3y-zW_fp4-WCp43V8y0A72xPmLkOM%2B6M%2BmJg%40mail.gmail.com > > --- > > src/backend/commands/analyze.c | 89 ++++++++++------------------------ > > 1 file changed, 26 insertions(+), 63 deletions(-) > > That's a very nice demonstration of how this makes good prefetching easier... Agreed. Yay streaming read API and Bilal! > > From 862b7ac81cdafcda7b525e02721da14e46265509 Mon Sep 17 00:00:00 2001 > > From: Melanie Plageman <melanieplage...@gmail.com> > > Date: Sun, 7 Apr 2024 15:38:41 -0400 > > Subject: [PATCH v10 3/3] Obsolete BlockSampler_HasMore() > > > > A previous commit stopped using BlockSampler_HasMore() for flow control > > in acquire_sample_rows(). There seems little use now for > > BlockSampler_HasMore(). It should be sufficient to return > > InvalidBlockNumber from BlockSampler_Next() when BlockSample_HasMore() > > would have returned false. Remove BlockSampler_HasMore(). > > > > Author: Melanie Plageman, Nazir Bilal Yavuz > > Discussion: > > https://postgr.es/m/flat/CAN55FZ0UhXqk9v3y-zW_fp4-WCp43V8y0A72xPmLkOM%2B6M%2BmJg%40mail.gmail.com > > The justification here seems somewhat odd. Sure, the previous commit stopped > using BlockSampler_HasMore in acquire_sample_rows - but only because it was > moved to block_sampling_streaming_read_next()? It didn't stop using it. It stopped being useful. The reason it existed, as far as I can tell, was to use it as the while() loop condition in acquire_sample_rows(). I think it makes much more sense for BlockSampler_Next() to return InvalidBlockNumber when there are no more blocks -- not to assert you don't call it when there aren't any more blocks. I didn't want to change BlockSampler_Next() in the same commit as the streaming read user and we can't remove BlockSampler_HasMore() without changing BlockSampler_Next(). - Melanie
>From 3cb43693c04554f5d46e0dc9156bef36af642593 Mon Sep 17 00:00:00 2001 From: Melanie Plageman <melanieplage...@gmail.com> Date: Sun, 7 Apr 2024 18:17:01 -0400 Subject: [PATCH v11 1/2] Use streaming read API in ANALYZE The ANALYZE command prefetches and reads sample blocks chosen by a BlockSampler algorithm. Instead of calling [Prefetch|Read]Buffer() for each block, ANALYZE now uses the streaming API introduced in b5a9b18cd0. Author: Nazir Bilal Yavuz Reviewed-by: Melanie Plageman, Andres Freund Discussion: https://postgr.es/m/flat/CAN55FZ0UhXqk9v3y-zW_fp4-WCp43V8y0A72xPmLkOM%2B6M%2BmJg%40mail.gmail.com --- src/backend/access/heap/heapam_handler.c | 20 +++--- src/backend/commands/analyze.c | 85 +++++++----------------- src/include/access/heapam.h | 5 +- 3 files changed, 39 insertions(+), 71 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 58de2c82a70..d9f053022f2 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -1055,16 +1055,16 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, } /* - * Prepare to analyze block `blockno` of `scan`. The scan has been started + * Prepare to analyze the next block in the a read stream. Returns false if + * the stream is exhausted and true otherwise. The scan must have 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 -heapam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, - BufferAccessStrategy bstrategy) +bool +heapam_scan_analyze_next_block(TableScanDesc scan, ReadStream *stream) { HeapScanDesc hscan = (HeapScanDesc) scan; @@ -1077,11 +1077,15 @@ heapam_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, * doing much work per tuple, the extra lock traffic is probably better * avoided. */ - hscan->rs_cblock = blockno; - hscan->rs_cindex = FirstOffsetNumber; - hscan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, - blockno, RBM_NORMAL, bstrategy); + hscan->rs_cbuf = read_stream_next_buffer(stream, NULL); + if (!BufferIsValid(hscan->rs_cbuf)) + return false; + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); + + hscan->rs_cblock = BufferGetBlockNumber(hscan->rs_cbuf); + hscan->rs_cindex = FirstOffsetNumber; + return true; } /* diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 2fb39f3ede1..58f6ad96136 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -1102,6 +1102,20 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr) return stats; } +/* + * Streaming read callback returning the next BlockNumber as chosen by the + * BlockSampling algorithm. + */ +static BlockNumber +block_sampling_streaming_read_next(ReadStream *stream, + void *user_data, + void *per_buffer_data) +{ + BlockSamplerData *bs = user_data; + + return BlockSampler_HasMore(bs) ? BlockSampler_Next(bs) : InvalidBlockNumber; +} + /* * acquire_sample_rows -- acquire a random sample of rows from the heap * @@ -1154,10 +1168,7 @@ acquire_sample_rows(Relation onerel, int elevel, TableScanDesc scan; BlockNumber nblocks; BlockNumber blksdone = 0; -#ifdef USE_PREFETCH - int prefetch_maximum = 0; /* blocks to prefetch if enabled */ - BlockSamplerData prefetch_bs; -#endif + ReadStream *stream; Assert(targrows > 0); @@ -1170,13 +1181,6 @@ acquire_sample_rows(Relation onerel, int elevel, randseed = pg_prng_uint32(&pg_global_prng_state); nblocks = BlockSampler_Init(&bs, totalblocks, targrows, randseed); -#ifdef USE_PREFETCH - prefetch_maximum = get_tablespace_maintenance_io_concurrency(onerel->rd_rel->reltablespace); - /* Create another BlockSampler, using the same seed, for prefetching */ - if (prefetch_maximum) - (void) BlockSampler_Init(&prefetch_bs, totalblocks, targrows, randseed); -#endif - /* Report sampling block numbers */ pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_TOTAL, nblocks); @@ -1187,60 +1191,19 @@ acquire_sample_rows(Relation onerel, int elevel, scan = heap_beginscan(onerel, NULL, 0, NULL, NULL, SO_TYPE_ANALYZE); slot = table_slot_create(onerel, NULL); -#ifdef USE_PREFETCH - - /* - * If we are doing prefetching, then go ahead and tell the kernel about - * the first set of pages we are going to want. This also moves our - * iterator out ahead of the main one being used, where we will keep it so - * that we're always pre-fetching out prefetch_maximum number of blocks - * ahead. - */ - if (prefetch_maximum) - { - for (int i = 0; i < prefetch_maximum; i++) - { - BlockNumber prefetch_block; - - if (!BlockSampler_HasMore(&prefetch_bs)) - break; - - prefetch_block = BlockSampler_Next(&prefetch_bs); - PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, prefetch_block); - } - } -#endif + stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE, + vac_strategy, + scan->rs_rd, + MAIN_FORKNUM, + block_sampling_streaming_read_next, + &bs, + 0); /* Outer loop over blocks to sample */ - while (BlockSampler_HasMore(&bs)) + while (heapam_scan_analyze_next_block(scan, stream)) { - BlockNumber targblock = BlockSampler_Next(&bs); -#ifdef USE_PREFETCH - BlockNumber prefetch_targblock = InvalidBlockNumber; - - /* - * Make sure that every time the main BlockSampler is moved forward - * that our prefetch BlockSampler also gets moved forward, so that we - * always stay out ahead. - */ - if (prefetch_maximum && BlockSampler_HasMore(&prefetch_bs)) - prefetch_targblock = BlockSampler_Next(&prefetch_bs); -#endif - vacuum_delay_point(); - heapam_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. - */ - 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)) { /* @@ -1290,6 +1253,8 @@ acquire_sample_rows(Relation onerel, int elevel, ++blksdone); } + read_stream_end(stream); + ExecDropSingleTupleTableSlot(slot); heap_endscan(scan); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 48936826bcc..f84dbe629fe 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -413,9 +413,8 @@ 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_block(TableScanDesc scan, + ReadStream *stream); extern bool heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, double *liverows, double *deadrows, -- 2.40.1
>From e7de413aedc33a77f9758fd37872f2eb2b6478f5 Mon Sep 17 00:00:00 2001 From: Melanie Plageman <melanieplage...@gmail.com> Date: Sun, 7 Apr 2024 18:19:38 -0400 Subject: [PATCH v11 2/2] Obsolete BlockSampler_HasMore() A previous commit stopped using BlockSampler_HasMore() for flow control in acquire_sample_rows(). There seems little use now for BlockSampler_HasMore(). It should be sufficient to return InvalidBlockNumber from BlockSampler_Next() when BlockSample_HasMore() would have returned false. Remove BlockSampler_HasMore(). Author: Melanie Plageman, Nazir Bilal Yavuz Reviewed-by: Andres Freund Discussion: https://postgr.es/m/flat/CAN55FZ0UhXqk9v3y-zW_fp4-WCp43V8y0A72xPmLkOM%2B6M%2BmJg%40mail.gmail.com --- src/backend/commands/analyze.c | 4 +--- src/backend/utils/misc/sampling.c | 10 +++------- src/include/utils/sampling.h | 1 - 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 58f6ad96136..602d0f35e7c 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -1111,9 +1111,7 @@ block_sampling_streaming_read_next(ReadStream *stream, void *user_data, void *per_buffer_data) { - BlockSamplerData *bs = user_data; - - return BlockSampler_HasMore(bs) ? BlockSampler_Next(bs) : InvalidBlockNumber; + return BlockSampler_Next(user_data); } /* diff --git a/src/backend/utils/misc/sampling.c b/src/backend/utils/misc/sampling.c index 933db06702c..6e2bca9739c 100644 --- a/src/backend/utils/misc/sampling.c +++ b/src/backend/utils/misc/sampling.c @@ -54,12 +54,6 @@ BlockSampler_Init(BlockSampler bs, BlockNumber nblocks, int samplesize, return Min(bs->n, bs->N); } -bool -BlockSampler_HasMore(BlockSampler bs) -{ - return (bs->t < bs->N) && (bs->m < bs->n); -} - BlockNumber BlockSampler_Next(BlockSampler bs) { @@ -68,7 +62,9 @@ BlockSampler_Next(BlockSampler bs) double p; /* probability to skip block */ double V; /* random */ - Assert(BlockSampler_HasMore(bs)); /* hence K > 0 and k > 0 */ + /* Return if no remaining blocks or no blocks to sample */ + if (K <= 0 || k <= 0) + return InvalidBlockNumber; if ((BlockNumber) k >= K) { diff --git a/src/include/utils/sampling.h b/src/include/utils/sampling.h index be48ee52bac..fb5d6820a24 100644 --- a/src/include/utils/sampling.h +++ b/src/include/utils/sampling.h @@ -38,7 +38,6 @@ typedef BlockSamplerData *BlockSampler; extern BlockNumber BlockSampler_Init(BlockSampler bs, BlockNumber nblocks, int samplesize, uint32 randseed); -extern bool BlockSampler_HasMore(BlockSampler bs); extern BlockNumber BlockSampler_Next(BlockSampler bs); /* Reservoir sampling methods */ -- 2.40.1